MCP Server
TakeTheme runs a Model Context Protocol (MCP) server so AI assistants — ChatGPT, Claude, Cursor, GitHub Copilot, Codex, 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, authenticate it once, and the assistant discovers the available tools itself.
POST https://api.taketheme.com/mcp
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 | 33 — commerce/analytics reads plus the builder suite; see the Tool Reference |
| Authentication | A static API key header, or OAuth 2.1 with dynamic client registration |
| Tenancy | One credential = one store. A credential cannot address any other store. |
Quick start
Two ways in. If your client has a “connect an app” button — ChatGPT, claude.ai — it will run an OAuth flow: it registers itself, the merchant approves once in the dashboard, and no key ever changes hands. Jump to Assistants that connect themselves.
Everything else takes an API key.
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 in this section 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" }
}
}
}
Codex CLI — ~/.codex/config.toml:
[mcp_servers.taketheme]
url = "https://api.taketheme.com/mcp"
bearer_token_env_var = "TAKETHEME_API_KEY"
Export TAKETHEME_API_KEY before launching Codex, or register the server with:
codex mcp add taketheme --url https://api.taketheme.com/mcp \
--bearer-token-env-var TAKETHEME_API_KEY
Codex's remote-MCP support is newer than its stdio support and has shipped behind experimental_use_rmcp_client = true in some builds. If url is ignored or rejected, bridge with mcp-remote as below.
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 connector UI runs an OAuth flow rather than taking a key — see Assistants that connect themselves below. The Responses API above stays the right choice for programmatic use.
Assistants that connect themselves
ChatGPT — Settings → Connectors → add a custom connector in developer mode, URL https://api.taketheme.com/mcp, authentication OAuth. There is no key and no client ID to paste: ChatGPT discovers the authorization server, registers itself, and sends the merchant to the TakeTheme dashboard to approve. Approve write access if you want the builder tools — a read-only approval leaves every one of them refused.
claude.ai — adding the same URL as a custom connector without configuring a header takes the OAuth path too.
Either way the connection appears under Settings → MCP in the dashboard, where the merchant can revoke it. See OAuth for the full flow, the scopes, and how to drive it by hand.
Deep-research connectors require the server to expose tools literally named search and fetch. This server names its tools for what they do (search_products, get_order, …), so it works as a developer-mode connector but will not appear as a deep-research source.
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, plus OAuth access tokens:
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.
Clients that run an authorization flow present an OAuth access token in the same Authorization: Bearer header. Those tokens are audience-locked to /mcp and rejected on every REST route.
The credential determines the store. storeId is never a tool argument: it is injected server-side from the authenticated key or token, so no prompt — and no prompt injection — can point a tool at another merchant's data.
| Situation | Response |
|---|---|
| No credential | 401 — AUTHENTICATION_REQUIRED with WWW-Authenticate header |
| Bearer token that is neither a TakeTheme key nor an OAuth token | 403 |
| Unknown, revoked, or expired key | 401 |
| Expired OAuth access token | 401 — JWT_EXPIRED; refresh it |
| Revoked OAuth token | 401 — TOKEN_REVOKED |
OAuth token presented on a non-/mcp route | 403 — ACCESS_DENIED |
| Valid credential | 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": "get_store_metrics",
"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 | Missing credential (WWW-Authenticate header returned), or unknown, revoked, or expired API key |
403 | Bearer token format invalid or not authorized |
405 | GET /mcp (stateless server) or unsupported HTTP methods (DELETE, PUT, PATCH) |
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 300 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 300 calls is roughly 100–150 questions per day.
HTTP rate limits. The standard API rate limits also apply to /mcp. Connector requests carrying an API key or Bearer token are credential-bucketed (600 req/min), avoiding shared egress IP bottlenecks. 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": 300,
"usedToday": 7,
"remainingToday": 293,
"usedThisMonth": 194,
"resetAt": "2026-07-28T00:00:00.000Z"
}
Security model
- One credential, one store. Tenant scoping comes from the authenticated key or OAuth token, 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 — or disconnecting an OAuth client from Settings → MCP — cuts the connection off at the next request.
- OAuth tokens cannot leave
/mcp. They are audience-locked, so a token that leaks can't be replayed against the REST API. - 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. A human reviews and publishes. The single exception is
builder_create_menu, which creates a new menu live because menus aren't revisioned — safe because a brand-new menu renders nowhere until something references its handle, and that reference is itself a staged edit. Existing menus cannot be edited by any tool. - 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 only ever stage drafts, except
builder_create_menuwhich creates new menus live. - 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).
- Over OAuth,
get_review_summaryis unreachable: it needs theREVIEWSscope, which neithermcp:readnormcp:writegrants. Use an API key for review data. - No tools named
search/fetch, so the server can't serve as a ChatGPT deep-research source.
Next
- Tool Reference — every tool, its arguments, its scope, and what it returns
- OAuth — the authorization flow, scopes, tokens, and revocation
- Scopes Reference — the full resource + action model
- API Keys — creating, restricting, and rotating keys