MCP
Connect a shopper's AI assistant or your own assistant to the store over MCP.
AI assistants can connect to the store over MCP. You use the shopper endpoint when a customer's assistant should browse and fill a cart, and the operator endpoint when your own assistant should answer questions about the business.
Endpoints
Litestore has two MCP route handlers, and each one authenticates its caller differently. Shoppers and store owners need different things from an assistant, and the owner's tool list would be wrong for an anonymous shopper's assistant.
| 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 |
Both endpoints speak JSON-RPC 2.0 over POST. They use stateless MCP streamable-http with protocol version 2025-06-18, and both answer the same four methods: initialize, tools/list, tools/call and ping.
Shopper endpoint
app/api/mcp/route.ts offers eight tools. 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.
| Tool | What the assistant uses it for |
|---|---|
get_store_info | Reads basic facts about the store. |
search_products | Searches the catalog. |
get_product_details | Reads one product. |
create_cart | Starts a cart. |
add_to_cart | Adds an item to the cart. |
view_cart | Reads the cart. |
get_checkout_url | Gets a link the shopper opens to check out. |
get_order_status | Looks up an order. |
curl -X POST https://yourstore.com/api/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Shared storefront code
Each tool is a thin wrapper around code the storefront already uses: searchProducts, the PDP queries, the cart functions in modules/checkout/ (resolveCart, addCartLine) and the guest order lookup. An agent therefore receives the same data as a browser, including the channel's catalog, live stock and current sale prices, because it runs the same code.
Visibility works the same way. get_product_details applies the storefront's own visibility rule and returns "Product not found" for any product that isn't Published or that is hidden.
Payment
The carts are real Cart rows, keyed by a randomUUID session id. The agent builds the same cart the storefront would build, but it can't pay for it.
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
When the store rejects 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.
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. Ask your assistant about revenue and cohorts, which orders need shipping, which stock is low or which drafts are waiting, and the store answers from live data.
Every call acts as the admin who created the key. Each ServiceApiKey records its creator in createdById, and on every request the route reads that user fresh: current role, permission overrides and ban state, rebuilt through the one standard permission builder. A key whose creator is gone, banned, no longer an admin or without ai:view is refused, and proposals land in that admin's own approval queue. The key doesn't store the permissions, so they can't drift away from the account. Every tools/call is written to the activity log with the tool, a hash of its arguments, the outcome, the duration, the key and the user.
API 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 each key exactly once.
Client setup
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, which is a quick way to check the URL before you set up the client.
Requests
tools/list returns every available tool. Litestore converts each tool's zod schema to JSON Schema, and each description states the tool's mode:
(read-only)
(PROPOSES a change: you approve it on the Litestore admin home within 5 minutes before it runs)Read tools answer right away. Tools in any other mode stop at callAgentTool, which records the proposal as an AgentRun row (tool and parameters stored on the server) and returns status: "needs_approval" with the pending action and the row's proposalId. The approval card in the admin is rendered from that row.
Admin approval
The tool's run only fires from approveAgentProposal in modules/assistant/admin/approval-actions.ts, after you click Approve. It re-checks the capability, your permission and the stored parameters, then claims the row with a compare-and-set (proposed → executed), so each proposal runs at most once, and only the stored parameters run. A proposal expires after five minutes.
The model has no confirm flag it can set. A boolean the model controls can never prove that a person approved. The server stores the proposal and a person has to approve that row by its id in the admin. Otherwise the model could simply claim approval and skip the step.
Approval gate
The operator endpoint doesn't re-implement any of these rules. The in-admin chat loop and this route both call callAgentTool in modules/assistant/agent-tool-kit.ts, so exactly one piece of code determines who may use a tool and what happens when a tool changes something.
| Rule | What Litestore does |
|---|---|
ai_max_autonomy (Settings → AI) | Applies the store-wide ceiling in both places. A tool above the ceiling is left out of tools/list entirely. It defaults to read, so a fresh store shows only read tools over MCP. |
| Permissions | Derives permissions from the key's creator again on every call. If the acting user lacks permission for a tool, the tool returns forbidden. |
| Protected domains | Lets tools in protected domains only propose changes, because of how Litestore builds them. |
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. Litestore can't register a tool that would let an assistant move money without a person, so code review doesn't need a separate check for it.
Security notes
Treat an admin:mcp key like an admin password
Whoever holds the key can ask it business questions. 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.