MCP: connect an agent
Forge speaks MCP at /mcp. There is nothing to deploy and no proxy to
configure: the tool list is the registry, and the gate behind it is the kernel’s own.
The thing to understand before anything else: the address decides the store. /mcp on a storefront’s
hostname is that store — resolved from the host, the same way the storefront decides which shop to render
and the certificate decides which name it answers for. Not an environment variable, not a setting on your
key, not an argument on a tool.
The two questions the endpoint asks
Section titled “The two questions the endpoint asks”Only two, and they are answered in this order:
| You connected to… | with no Bearer | with a tenant Bearer |
|---|---|---|
| a store’s hostname | the public face — the purchase journey and the public reads | that key’s tools plus the journey (telesales, agent-on-behalf) |
| a hostname no store claims | 401 | that key’s tools; no journey |
The Bearer does not change which store it is — only what you may do there.
1. An agent with no credential at all
Section titled “1. An agent with no credential at all”This is the case worth reading twice, because it is the product promise: a shopper’s agent does not hold — and must never hold — a key belonging to the merchant.
claude mcp add --transport http shop https://shop.example/mcpNo header. No token. Speaking to it by hand:
curl -X POST https://shop.example/mcp \ -H "content-type: application/json" \ -H "accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{ "protocolVersion":"2025-06-18","capabilities":{}, "clientInfo":{"name":"my-agent","version":"1"}}}'2. Turn the address into a store id — this step is not optional
Section titled “2. Turn the address into a store id — this step is not optional”There is an asymmetry here and you will trip on it if nobody says it out loud:
- Commands take no
store. The host already decided; a store is configuration of the server, never an argument of a tool. - Public reads do take
store, because the same read serves every shop.
So an agent that was handed only a URL starts by converting one into the other:
{ "method": "tools/call", "params": { "name": "read.store.by_host", "arguments": { "host": "shop.example" } } }{ "store_id": "sto_01ABCDEFGHJKMNPQRSTVWXYZ01" }Now it can browse:
{ "name": "read.products", "arguments": { "store": "sto_…", "limit": 5, "projection": "feed" } }3. Buy
Section titled “3. Buy”The journey, in order. None of these takes a store:
cart.create → cart.add_line → cart.set_buyer → cart.set_delivery → cart.set_payment_method → checkout.place_ordercheckout.place_order returns the order. That is a real order, in a real store, placed by something holding
no credential — exactly as a browser does it, because it is what a browser does, over a different
transport.
4. Where the agent stops, and why
Section titled “4. Where the agent stops, and why”payment.initiate is a tool and you can call it. It returns next_action: null.
That is not a bug and not an oversight: the command claims the intent and opens the attempt, but it does not call the provider. Talking to a payment provider is third-party I/O, isolated in an adapter outside the kernel’s transaction — so it is not a command, and what is not a command is not a tool.
The agent closes the order over MCP and pays over the public HTTP route
/v1/payment/commands/payment.initiate, or follows the next_action that route returns.
This limit is measured, not assumed: a test asserts that null. The day a tool exists for the provider
invoke, that test fails and forces whoever built it to update this page.
5. With a key, on the same address
Section titled “5. With a key, on the same address”Add a Bearer and the store does not change — the powers do. The key’s own tools appear beside the journey,
which is the telesales case: an agent acting for the merchant, building a cart and attaching it to a
customer’s account with cart.assign_customer.
claude mcp add --transport http forge https://shop.example/mcp \ --header "Authorization: Bearer $FORGE_TOKEN"The tool list is the key’s scopes. A key carrying three read scopes sees reads and nothing else — not a longer list it must be trusted not to call. Issue a key with write scopes and the write tools appear; revoke a scope and they leave. See issue & rotate API keys.
Every call lands in the audit trail under the key’s name, refusals included.
6. Running it locally
Section titled “6. Running it locally”The stdio binary has no request to read a host from, so it takes the store id directly:
FORGE_MCP_STORE=sto_… forge-mcpWith or without FORGE_MCP_TOKEN — the same two questions, answered the same way. To convert your hostname
into that id: forge read store.by_host --input '{"host": "shop.example"}'.
What this does not open
Section titled “What this does not open”Nothing. The anonymous face answers exactly what /v1/cart/* and /v1/read/* already answer to any
browser — same commands, same dispatcher in public mode, and the same per-IP caps on the endpoints that carry
one, with a key and without one. What changed is the transport, and who has to hold a credential in order
to shop: nobody.
⚠️ That last parity is wired, not inherited, and the distinction is worth your attention if you run your own instance. The obvious reasoning — “it is the same dispatcher, so it is the same everything” — is wrong, and it is wrong in the direction that costs you: a second transport over a face inherits the face’s commands, not its guards. The HTTP caps are middleware on a route; a tool call never passes through that route. They are on this face because they were deliberately carried onto it.
- Surfaces — the faces this page mirrors.
- Issue & rotate API keys — for the half that does need a key.