MCP
This page helps you connect a shopper's AI assistant or your own assistant to your store over MCP.
Litestore lets AI assistants talk to your store over MCP. It has two MCP route handlers, and each one trusts its caller differently. One serves a shopper's assistant, and the other serves the operator's assistant.
We kept them apart on purpose. Shoppers and store owners want different things from an assistant, and the owner's tool list would be wrong for an anonymous shopper's assistant.
Both endpoints speak JSON-RPC 2.0 over POST. They use stateless MCP streamable-http with protocol version 2025-06-18.
Both answer the same four methods:
initializetools/listtools/callping
| Endpoint | Who uses it | Auth | What can it change? |
|---|---|---|---|
/api/mcp | Shoppers' assistants | None, but rate limited by IP | It fills a cart, and a person checks out |
/api/mcp/admin | The operator's assistant | A Bearer ServiceApiKey with the admin:mcp scope | It only proposes changes, and a person approves them in the admin |
The shopper endpoint
app/api/mcp/route.ts offers eight tools:
get_store_infosearch_productsget_product_detailscreate_cartadd_to_cartview_cartget_checkout_urlget_order_status
A GET on the same URL returns the transport details and the tool names. /.well-known/ucp advertises the endpoint, so agents can find it without being told where it is.
The tools reuse the storefront's code
Each tool is a thin wrapper around code the storefront already uses:
searchProducts- the PDP queries
cartService- the guest order lookup
This keeps the assistant and the website in agreement. An agent sees what a browser sees, including the channel's catalog, live stock and current sale prices, because it runs the same code.
The same goes for what is visible. get_product_details applies the storefront's own visibility rule. It returns "Product not found" for any product that isn't Published or that is hidden.
Payment stays in human hands
The carts are real Cart rows, keyed by a randomUUID session id. The agent builds the same cart the storefront would build.
The agent can't pay for it, though. get_checkout_url returns a /cart/claim?session=… link. The link opens the cart in the shopper's browser, and checkout runs there with all the normal rules.
Nothing in this route charges anyone.
Refusals come back as results
When the store refuses something, such as an out-of-stock item, a variant that isn't eligible or input that fails validation, the tool returns a result with isError: true. It doesn't return a JSON-RPC fault.
The model reads these two differently. A JSON-RPC fault tells it the call broke. A tool result with isError: true tells it the store said no and why, so the model can act on the reason and try something else.
curl -X POST https://yourstore.com/api/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'The operator endpoint
app/api/mcp/admin/route.ts gives an outside client, such as Claude Desktop, access to agentToolRegistry. The assistant inside the admin uses the same registry.
You can ask your assistant about revenue and cohorts, what needs shipping, which stock is low or which drafts are waiting. The store answers from live data.
Every call acts as the store owner. Litestore serves a single merchant and API keys aren't tied to a user, so the key has no person of its own to act as.
On every request, the route finds the oldest owner account that isn't banned. It then rebuilds that account's permission context through the one standard builder. Because the key doesn't store the permissions, they can't drift away from the owner's account.
Step 1: Create a key
In the admin, open Settings → Developer and create an API key with the Operator MCP (admin:mcp) scope.
Keys start with lsk_. Litestore stores them as a SHA-256 hash and shows you each key exactly once.
Step 2: Connect your client
Point any MCP client at the endpoint and send the key as a bearer token:
{
"mcpServers": {
"my-store": {
"url": "https://yourstore.com/api/mcp/admin",
"headers": { "Authorization": "Bearer lsk_..." }
}
}
}A GET without a key returns 401 with these same instructions. You can use that to check the URL before you set up the client.
Step 3: Ask
tools/list returns every available tool. Each tool's zod schema is converted to JSON Schema, and its description states its mode:
(read-only)
(PROPOSES a change — a human approves it in the admin before it runs)Read tools answer right away.
Tools in any other mode stop at callAgentTool. It returns status: "needs_approval" with a pending action and an approval token signed by the server. It also creates the same approval card the in-admin assistant creates.
The tool's run only fires from executeApprovedAgentAction, after you click Approve and the token checks out.
The model has no confirm flag it can set. A boolean the model controls can never prove that a person approved. The server has to sign the token and a person has to redeem it. Otherwise the model could simply claim approval and skip the step.
One gate, everywhere
The operator endpoint doesn't re-implement any of these rules.
The in-admin chat loop and this route both call callAgentTool in server/ai/agent-tool-kit.ts. So there is exactly one piece of code that decides who may use a tool and what happens when a tool changes something:
- If a tool is listed in
ai_disabled_tools(Settings → AI), Litestore drops it fromtools/listand refuses it ontools/call, the same way in both places. - The store-wide
ai_max_autonomyceiling applies the same way in both places too. It defaults toread, so a fresh store shows only read tools over MCP. - Litestore derives permissions from the owner account again on every call. If the acting user lacks permission for a tool, the tool returns
forbidden. - Tools in protected domains can only ever propose changes, because of how they are built.
Litestore checks that last rule when it builds the tools, not when they are called. EXECUTE_FORBIDDEN lists the protected permissions:
financial processing
the danger zone
refunds:edit
refunds:delete
collabs:editIf any tool declares mode: "execute" for one of these, building the tool map throws an error. A tool that would let an assistant move money without a person can't be registered at all, so nobody has to remember to check for it.
Security notes
Treat an admin:mcp key like an admin password
Whoever holds the key can ask it business questions. You can revoke the key in the same panel where you created it. Revoking takes effect on the next call, because validateApiKey reads isActive from the database every time.
- Both endpoints are rate limited by IP through
rateLimits.publicApi. If a key leaks, the limit slows the caller down while you revoke it. - The shopper endpoint never shows draft or hidden products. It applies the same visibility rules as the storefront.
- The shopper endpoint sends permissive CORS headers, so assistants running in a browser can reach it. The operator endpoint doesn't send them and expects a client running on a server.
We split CORS this way because of who calls each endpoint. A shopper's assistant may well run in a browser. The operator's assistant holds a key that should never be in a browser.
Next Steps
AI Content
Learn which AI features actually ship — SEO meta, tags, category and attribute suggestions, email and social drafts, and the task assistant — and which provider keys turn them on.
Configuration
Learn which environment variables env.ts requires, which ones are optional, and what each optional group configures.