MCP Server
TakeTheme runs a Model Context Protocol (MCP) server so AI assistants — Claude, Cursor, GitHub Copilot, Gemini, an agent you wrote yourself, or anything else that speaks MCP — can read your store directly: sales figures, orders, products, customers, and reviews.
Instead of writing an integration against the REST API, you point an MCP client at one endpoint, give it an API key, and the assistant discovers the available tools itself.
POST https://api.taketheme.com/mcp
The MCP server authenticates with API keys, which are available on Pro and Scale plans. See API Keys.
Commerce and analytics tools are read-only. The builder tools can edit your storefront — but every write lands on a draft that only a human can publish, a restore point is taken before each write, and an agent is refused while a person is actively editing that draft. The live store is never written by a tool. See Builder tools below.
What you get
| Server name | taketheme-commerce |
| Transport | Streamable HTTP (stateless) |
| Protocol version | 2025-06-18 |
| Capabilities | tools + resources (the component catalog) — no prompts, sampling, or server-initiated notifications |
| Tools | 31 — commerce/analytics reads plus the builder suite; see the Tool Reference |
| Tenancy | One API key = one store. A key cannot address any other store. |
Quick start
1. Create an API key
In the dashboard, go to Settings → API Keys and create a key with READ on the resources you want the assistant to reach — typically ANALYTICS, ORDERS, PRODUCTS, CUSTOMERS, CATEGORIES, REVIEWS, and STORE_SETTINGS. For the builder tools, add THEME with READ and WRITE — reads need the former, and staging edits on a draft needs the latter. Each tool checks its own scope at call time, so a narrower key simply means fewer tools succeed. The Tool Reference lists the scope each tool needs.
2. Connect a client
Every client below needs the same two things: the URL https://api.taketheme.com/mcp and the header Authorization: Bearer tt_YOUR_API_KEY. If yours isn't listed, look for wherever it configures a remote (HTTP / "streamable HTTP") MCP server with custom headers — that's all this server requires.
Assistants and IDEs
Claude Code
claude mcp add --transport http taketheme https://api.taketheme.com/mcp \
--header "Authorization: Bearer tt_YOUR_API_KEY"
claude.ai (custom connector)
Add a custom connector pointing at https://api.taketheme.com/mcp, and configure a static header:
Authorization: Bearer tt_YOUR_API_KEY
Cursor — ~/.cursor/mcp.json for every project, or .cursor/mcp.json for one:
{
"mcpServers": {
"taketheme": {
"url": "https://api.taketheme.com/mcp",
"headers": { "Authorization": "Bearer tt_YOUR_API_KEY" }
}
}
}
VS Code (GitHub Copilot agent mode) — .vscode/mcp.json:
{
"servers": {
"taketheme": {
"type": "http",
"url": "https://api.taketheme.com/mcp",
"headers": { "Authorization": "Bearer tt_YOUR_API_KEY" }
}
}
}
Gemini CLI — ~/.gemini/settings.json:
{
"mcpServers": {
"taketheme": {
"httpUrl": "https://api.taketheme.com/mcp",
"headers": { "Authorization": "Bearer tt_YOUR_API_KEY" }
}
}
}
Claude Desktop, Windsurf, and other stdio-only clients
Bridge the remote server with mcp-remote:
{
"mcpServers": {
"taketheme": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.taketheme.com/mcp",
"--header",
"Authorization: Bearer tt_YOUR_API_KEY"
]
}
}
}
Agents you build yourself
OpenAI Responses API — the hosted MCP tool lets OpenAI connect to the server for you:
import os
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5",
tools=[{
"type": "mcp",
"server_label": "taketheme",
"server_url": "https://api.taketheme.com/mcp",
"headers": {"Authorization": f"Bearer {os.environ['TAKETHEME_API_KEY']}"},
"require_approval": "never",
}],
input="How did the store do last week, and what's running low on stock?",
)
print(response.output_text)
Because the tool is hosted, your key travels to OpenAI on every request and OpenAI's servers — not your machine — open the connection. Use a dedicated, minimally scoped key.
LangChain / LangGraph — via langchain-mcp-adapters, which turns the tools into LangChain tools:
import os
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"taketheme": {
"transport": "http", # older releases call this "streamable_http"
"url": "https://api.taketheme.com/mcp",
"headers": {"Authorization": f"Bearer {os.environ['TAKETHEME_API_KEY']}"},
}
})
tools = await client.get_tools()
For a direct SDK connection, see Using the MCP SDKs below.
ChatGPT's built-in connector UI expects the server to speak OAuth, which this one doesn't — see Current limitations. Use the Responses API above, or bridge with mcp-remote.
3. Ask something
"How did the store do last week compared to the week before, and what's running low on stock?"
The assistant will call get_store_metrics and get_low_stock_products and answer from the results.
Authentication
The server accepts the same credentials as the REST API:
Authorization: Bearer tt_YOUR_API_KEY
tt-api-key: tt_YOUR_API_KEY
Use the Authorization form with MCP clients — most of them can only set standard headers. Both forms resolve to the same key.
The key determines the store. storeId is never a tool argument: it is injected server-side from the authenticated key, so no prompt — and no prompt injection — can point a tool at another merchant's data.
| Situation | Response |
|---|---|
| No credential | 403 — INVALID_TOKEN |
| Bearer token that isn't a TakeTheme key | 403 |
| Unknown, revoked, or expired key | 401 |
| Valid key | 200, JSON-RPC response |
Authentication failures are ordinary HTTP errors, not JSON-RPC errors — they happen before the protocol layer runs.
Calling the server directly
Any HTTP client works. Two headers matter:
Content-Type: application/jsonAccept: application/json, text/event-stream— both media types, per the MCP spec
curl -sS -X POST https://api.taketheme.com/mcp \
-H "Authorization: Bearer $TAKETHEME_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Omitting text/event-stream from Accept returns 406 Not Acceptable before your request reaches the server. If your first hand-written request fails with a 406, this is why.
Responses come back as a Server-Sent Events frame containing one JSON-RPC message:
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[ ... ]}}
Calling a tool:
curl -sS -X POST https://api.taketheme.com/mcp \
-H "Authorization: Bearer $TAKETHEME_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_store_metrics",
"arguments": { "period": "last_7_days", "compareToPrevious": true }
}
}'
Tool results are JSON, transported as MCP text content:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"period\": \"the last 7 days\",\n \"currency\": \"EGP\",\n \"metrics\": { \"total_sales\": 48200, \"total_orders\": 316 }\n}"
}
]
}
}
Parse result.content[0].text as JSON to get the payload documented in the Tool Reference.
Using the MCP SDKs
TypeScript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://api.taketheme.com/mcp"),
{
requestInit: {
headers: { Authorization: `Bearer ${process.env.TAKETHEME_API_KEY}` },
},
},
);
const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);
const { tools } = await client.listTools();
const result = await client.callTool({
name: "get_top_products",
arguments: { period: "last_30_days", sortBy: "revenue", limit: 5 },
});
Python
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
url = "https://api.taketheme.com/mcp"
headers = {"Authorization": f"Bearer {os.environ['TAKETHEME_API_KEY']}"}
async with streamablehttp_client(url, headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
result = await session.call_tool(
"get_top_products",
{"period": "last_30_days", "sortBy": "revenue", "limit": 5},
)
Protocol behaviour
Stateless. The server issues no session id and keeps no per-client state. There is no Mcp-Session-Id on responses, and every request is independent — which is what lets the API scale horizontally without pinning your client to one instance. If you send an Mcp-Session-Id header it is used only to correlate log lines for the request.
initialize is supported but not required. Proper clients perform the handshake and get back the server info and capabilities; a tools/list or tools/call sent without it is answered normally.
GET /mcp returns 405. In stateful deployments GET opens the server→client SSE stream. This server is stateless, so there is nothing to attach to, and it answers with a JSON-RPC error rather than holding your connection open:
{
"jsonrpc": "2.0",
"error": { "code": -32000, "message": "Method not allowed: this server is stateless." },
"id": null
}
The tool list is identical for every store. Tools are never hidden based on your key's scopes. A tool you aren't entitled to still appears in tools/list and returns a structured refusal when called — which tells your assistant why something isn't available instead of silently omitting it.
Errors
There are two distinct layers.
Transport errors (HTTP / JSON-RPC)
Something went wrong before or beneath the tool call.
| Status | Meaning |
|---|---|
401 | Unknown, revoked, or expired API key |
403 | Missing credential, or a bearer token that isn't a TakeTheme key |
405 | GET /mcp — this server is stateless |
406 | Accept header missing text/event-stream |
429 | HTTP rate limit — see Rate Limits |
500 | JSON-RPC internal error (-32603) |
Tool errors
A tool that couldn't run returns a normal 200 with isError: true and a JSON body your assistant can read and act on. This is deliberate: a permission problem is information the assistant should relay to you, not a crashed request.
{
"isError": true,
"content": [
{
"type": "text",
"text": "{\n \"error\": \"permission_denied\",\n \"message\": \"This API key's ORDERS scope doesn't allow this action.\",\n \"details\": { \"requiredPermission\": \"ORDERS\" }\n}"
}
]
}
error | What happened |
|---|---|
unknown_capability | No tool by that name |
not_on_surface | The tool exists but isn't exposed over MCP |
invalid_arguments | Arguments failed schema validation — message names the offending field |
permission_denied | Your key lacks the required scope, or lacks READ on it |
plan_upgrade_required | Your plan doesn't include this capability |
store_not_writable | The store is read-only or suspended (write tools only) |
rate_limited | Daily MCP tool-call limit reached for this store |
execution_failed | The operation failed server-side |
Partially available data
Analytics tools never report a confident zero when the analytics backend is degraded. If some figures couldn't be computed, the payload carries a _degraded note naming them, and the affected fields are omitted rather than returned as 0:
{
"period": "the last 30 days",
"currency": "EGP",
"metrics": { "total_orders": 412 },
"_degraded": {
"reason": "Some analytics could not be loaded right now. Treat the affected figures as unavailable — do not report them as zero.",
"unavailableFields": ["total_sales", "aov"]
}
}
Treat those fields as unknown, not as zero.
Usage limits
Two limits apply independently.
Daily tool calls. Each store may make 30 MCP tool calls per day, resetting at 00:00 UTC. Only successful calls count. Exceeding the limit returns a tool error with error: "rate_limited".
One question typically costs two or three tool calls, so 30 calls is roughly 10–15 questions per day.
HTTP rate limits. The standard API rate limits also apply to /mcp. See Rate Limits.
To check consumption, call the usage summary endpoint and look for the mcp-tool-call row (requires MARKETING READ scope):
curl -X GET "https://api.taketheme.com/api/v1/ai/usage/summary" \
-H "tt-api-key: $TAKETHEME_API_KEY"
{
"feature": "mcp-tool-call",
"limit": 30,
"usedToday": 7,
"remainingToday": 23,
"usedThisMonth": 194,
"resetAt": "2026-07-28T00:00:00.000Z"
}
Security model
- One key, one store. Tenant scoping comes from the authenticated key, never from a tool argument.
- Scopes are enforced per call, with the same resource + action model as the REST API. A
READ-only key cannot reach a write tool even on a resource it can see. - Payloads are narrowed on purpose. Order reads return line items and totals but not buyer contact details, IPs, or risk scores; nothing goes into an assistant's context that doesn't need to be there.
- Revocation is immediate. Revoking the key in the dashboard cuts the connection off at the next request.
- Treat the key as a credential. Anyone holding it can read everything its scopes allow. Use a dedicated, minimally scoped key per assistant, and rotate it if it leaks.
Builder tools
Beyond commerce reads, the server exposes the storefront builder: an assistant can read and edit pages, theme settings, and Custom Liquid components, discover the full component catalog, and — where the preview service is enabled — see its work as screenshots and diff a draft against a captured reference design.
The safety model is uniform across every builder write:
- Drafts only. Writes stage on a draft; writing to the live store is refused, always. A human reviews and publishes.
- A restore point first. Every write snapshots the draft before changing it, so anything an agent does can be rolled back from the builder's restore points.
- People outrank agents. While a person is actively editing a draft in the builder, agent writes to that draft are refused with
DRAFT_UI_LOCKED. - The component catalog is served, not guessed. Assistants discover valid section types, their settings, nesting rules, and worked examples through dedicated tools — the same contract the builder itself renders from.
See the Tool Reference for the full suite.
Current limitations
- Commerce data is read-only — order/product/customer tools do not mutate; storefront writes exist but only ever stage drafts.
- Preview/screenshot tools require the preview browser service to be enabled on the deployment; without it they answer
PREVIEW_UNAVAILABLEand everything else works. - No
prompts;toolsandresourcesonly. - No server→client streaming, notifications, or sampling (a consequence of stateless mode).
- No OAuth — authentication is a static API key header.
Next
- Tool Reference — every tool, its arguments, its scope, and what it returns
- Scopes Reference — the full resource + action model
- API Keys — creating, restricting, and rotating keys