Sell an MCP server to people
Charge for MCP tools today, without waiting for clients to learn how to pay — identify the user, draw on what they bought, and send them somewhere to top up.
No MCP client in wide use lets a model attach a payment to a tool call. Claude, ChatGPT, and Cursor give a model tools, not a wallet — by design, and it is the right design.
So do not ask the model to pay. Ask who is calling. Every remote MCP client signs its user in, which is all a merchant needs: the caller has an account, the account has credit or a subscription, and the call draws on it. Nothing in this guide waits on a payment protocol, a wallet, or a client feature that does not exist yet.
The finished example is examples/mcp/src/account-server.ts.
1. Identify the caller
A remote MCP server authenticates with OAuth, and the SDK hands the verified token to every tool call as extra.authInfo. Turn it into a Tollstile principal:
const principal = (extra) => {
const account = accounts[extra.authInfo?.token ?? ""]; // your own table
return account === undefined ? null : { id: account };
};authInfo is set by whatever verified the request — the SDK's bearer-auth middleware on Express, or transport.handleRequest(request, { authInfo }) on a Web-standard server. If you have not wired auth yet, follow MCP's authorization spec first: without it, everyone is the same anonymous caller.
2. Charge the account, not the request
paidTool(
server,
"forecast",
{ description: "Tomorrow's weather. $0.01 from your balance.", inputSchema: { city: z.string() } },
toll.price("$0.01", {
access: [subscriber({ active: (user) => subscriptions.has(user.id) }), credits({ balance })],
}),
({ city }) => ({ content: [{ type: "text", text: `Tomorrow in ${city}: clear` }] }),
{ principal },
);Subscribers pass free; everyone else draws $0.01 from their balance, reserved before the handler and committed after. Your customers bought that balance the ordinary way — one Stripe checkout for $20 — so no card is charged per call and no fee is paid per call.
No payPerCall(), on purpose
Leaving payPerCall() out of the access list means there is no way into this tool except an account. A caller holding a perfectly valid payment proof is refused with access_denied before any rail is consulted. Add it back only when you do want unattended agents paying their own way.
3. Send them somewhere to pay
Out of credit is not a failure the model can fix. Left alone, it reaches the user as "the weather tool returned an error".
{
principal,
checkout: (denial) =>
denial.error.code === "access_denied"
? { url: "https://weather.example/account/credits", message: "Add credit to keep using the weather tools." }
: null,
}A client that declared capabilities.elicitation.url shows its user that link and stops. Every other client gets the denial with { url, message } in _meta["tollstile/checkout"], so your own agent, a proxy, or a log can still find it. Details: Checkout.
4. Watch what the ledger says
const { count, total } = await ledger.spendSince("acct_amy", new Date(Date.now() - 86_400_000));Every draw is a charge row: who, which tool, how much, when. That is the invoice line, the usage page, and the support answer to "what did I pay for", without a second metering system.
When to add per-call payment
Add payPerCall() and a rail when the caller is a program with its own money — your own agent fleet, a customer's backend, a marketplace client built to pay. Then a 402 is answered by the caller instead of by a person, and everything above still holds for the humans.
If those calls are expensive, ask before charging: Approval.
What this does not solve
- A client that hides errors. If a client renders a denial as "tool failed" and drops
_meta, the user sees less than you sent. Checkout is a request, not a guarantee. - Account-less callers. Anonymous access still needs a rail, and the client-side ecosystem for that is early. See Consent.
- Sign-up. Tollstile never takes custody and sells nothing; the page you send people to is yours.
Charge for MCP tool calls
Put a price on MCP tools with @tollstile/mcp. Build it on the test rail, call it from an MCP client, then accept x402 stablecoin payments.
Monetize an API with x402
Charge per request in USDC with x402 on a Hono API. Build on the test rail, switch to x402 exact on Base Sepolia, and use upto for variable prices.