MCP Integration
The MCP integration turns your Key0 seller into an MCP server. Agents connect over Streamable HTTP, discover your plans, and pay for access tokens using the x402 protocol — all without leaving the tool-call flow.Enable MCP
Setmcp: true in your SellerConfig. The Express router automatically mounts all MCP routes alongside the existing A2A and x402 HTTP endpoints.
Routes
The integration mounts four routes on the Express router:| Route | Method | Purpose |
|---|---|---|
/.well-known/mcp.json | GET | MCP discovery document (name, description, transport URL) |
/mcp | POST | Streamable HTTP transport — handles all tool calls |
/mcp | GET | Returns 405 (SSE not supported in stateless mode) |
/mcp | DELETE | Returns 405 (session management not supported in stateless mode) |
Tools
The MCP server exposes two tools:| Tool | Gated? | Purpose |
|---|---|---|
discover_plans | Free | Browse the plan catalog: plan IDs, prices (USDC), wallet address, chain ID |
request_access | x402 | Purchase an access token for a plan |
discover_plans
Takes no arguments. Returns the full plan catalog as JSON:request_access
TakesplanId (required) and resourceId (optional, defaults to "default").
When called without payment, it returns a PaymentRequired response. When called with a valid x402 payment, it settles on-chain and returns an access grant.
Payment Flow
There are two paths to complete a payment, depending on the client’s capabilities.- Path A: HTTP x402 (Current Clients)
- Path B: Native x402 MCP (Future Clients)
This is the path used by current MCP clients like Claude Desktop, Claude Code, and Cursor. The agent uses a companion payment tool (such as Coinbase’s
payments-mcp) to settle via HTTP.Step-by-step:- Agent calls
discover_plansto get the catalog - Agent calls
request_access({ planId: "basic" })with no payment attached - Server returns
isError: truewith a PaymentRequired response containingx402PaymentUrlandpaymentInstructions - Agent calls
make_http_request_with_x402(frompayments-mcp) to POST to thex402PaymentUrl payments-mcpsigns an EIP-3009 authorization off-chain and sends it as aPAYMENT-SIGNATUREheader- The
/x402/accessendpoint settles the payment on-chain and returns an AccessGrant with a JWT
The
content[0].text field contains x402PaymentUrl and human-readable paymentInstructions so that LLM agents can parse the next step even without structured content support.Error Handling
| Error | Response |
|---|---|
| Plan not found | isError: true with Key0Error JSON (TIER_NOT_FOUND) |
Malformed _meta["x402/payment"] | isError: true with Key0Error JSON (Zod validation details) |
| Payment failed / settlement error | isError: true with structuredContent containing error message and accepts[] array for retry |
| Already redeemed tx | Cached AccessGrant returned (idempotent — no double-charge) |
Already-redeemed transactions return the original AccessGrant. The
requestId is derived deterministically from the payment signature, so retries with the same payment always resolve to the same challenge.Connecting from Claude Code
Add your MCP server to.mcp.json in your project root:
discover_plans and request_access tools automatically.
Testing with curl
List available tools:discover_plans:
Why Stateless?
The MCP transport creates a freshMcpServer and StreamableHTTPServerTransport for every request. This is intentional:
- Tools are pure request/response. There is no streaming, subscriptions, or server-initiated messages.
- All state lives in external stores. ChallengeStore and SeenTxStore (Redis or Postgres) hold challenge state and double-spend records.
- Horizontal scaling. No sticky sessions, no in-memory state, no affinity requirements. Put it behind any load balancer.
- No memory leaks. Each server instance is created, used, and closed within a single request lifecycle.

