Accept x402 payments on a remote Streamable HTTP MCP server
Streamable HTTP gives every tool call a real HTTP request boundary — which is exactly where a 402 belongs. Here's how to gate a remote MCP server without breaking discovery.
x402 is an HTTP protocol, so it needs an HTTP surface to live on. A stdio MCP server running on a user's laptop never produces a request you can put a 402 in front of. A remote server speaking Streamable HTTP does: every JSON-RPC message arrives as a POST to your MCP endpoint, and that request boundary is where payment gating happens.
This guide walks through where to put the gate, which MCP methods must stay free, and what the paid round trip looks like on the wire.
Gate tools/call, not the whole endpoint
The temptation is to put a payment wall in front of the entire /mcp route. Don't. An agent has to complete initialize and call tools/list before it knows what your server offers — if those requests cost money, no client will ever get far enough to pay you.
The correct shape: inspect the JSON-RPC body of each POST, let lifecycle and discovery methods through free, and require payment only when the method is tools/call (optionally scoped to specific tool names). Discovery stays frictionless; execution is what's priced.
The paid round trip on the wire
An unpaid tools/call POST gets back HTTP 402 with a payment-requirements payload: amount, asset (USDC), network (Base), and the address to pay. The agent's client signs a USDC transfer authorization for that exact amount and retries the same request with an X-PAYMENT header. Your server hands the header to a facilitator for verification, settlement clears on Base in about two seconds, and only then does the tool handler execute.
Because payment completes before the handler runs, there is nothing to claw back and no chargeback path. Each settled call produces an Ed25519-signed receipt you can log against the request ID.
Implementation: a method-aware gate
With the TypeScript MCP SDK and an Express host, the gate is a small middleware in front of your StreamableHTTPServerTransport handler.
import { requirePayment } from "@loomal/sdk";
const FREE = new Set(["initialize", "tools/list", "ping", "notifications/initialized"]);
app.post("/mcp", (req, res, next) => {
if (FREE.has(req.body?.method)) return next();
return requirePayment({ price: "$0.02" })(req, res, next);
}, handleMcpRequest); // your StreamableHTTPServerTransport handlerSessions and payments are orthogonal
Streamable HTTP uses an Mcp-Session-Id header to correlate a client's requests, and it's tempting to treat a paid call as buying the session. Resist that. Price per call, not per session: sessions can be long-lived, reconnected, or shared by an agent loop making hundreds of calls, and a session-scoped payment turns your per-call price into an accidental flat rate.
Keep the gate stateless — every tools/call either carries a valid X-PAYMENT header or gets a 402. The session header keeps doing its job (protocol state) and the payment header does its own (paying for this call).
Get it listed where agents look
A payable endpoint nobody can find earns nothing. List the server on Loomal so agents querying the index get your URL, your tool list, and your per-call price in one machine-readable answer. The minimum price is $0.01 per call, and the platform fee is 5% on settled transactions — currently waived.
FAQ
Why can't I do this on a stdio MCP server?
stdio servers run as a local subprocess of the client and communicate over pipes — there's no HTTP request to attach a 402 status or an X-PAYMENT header to. If your server is stdio-only today, migrating to Streamable HTTP is the prerequisite step before any x402 work.
Do I need blockchain code to verify payments?
No. Your server's job is plain HTTP: return 402 with payment requirements, then forward the X-PAYMENT header to a facilitator that verifies the signature and settles the USDC transfer on Base. You never construct transactions or run a node.
What about the SSE stream Streamable HTTP can open?
Gating happens on the POST that initiates the request, before any response — streamed or not — is produced. Once a call is paid and the handler runs, whether the result arrives as a single JSON body or over an SSE stream makes no difference to the payment flow.
Can different tools on one server have different prices?
Yes. The gate sees the JSON-RPC body, so it knows which tool tools/call is invoking and can quote a price per tool — a cheap lookup at $0.01 and an expensive analysis tool at $0.25 can live on the same endpoint.
Your server is remote. Make it earn.
Connect your Streamable HTTP server and start quoting prices on the 402.