How to Monetize an MCP Server: Paid Tools with x402
The Model Context Protocol solved distribution for tools. Any assistant can now call your code. What it never solved is getting paid for it, because every payment system on the web assumes a human with a credit card completes a signup form. This guide fixes that: you will price a tool, make it answer unpaid requests with a real 402 Payment Required challenge, verify and settle in USDC on Solana, publish it where agents look for paid capabilities, and test the buyer side from Claude.
Everything below runs on the same rails three.ws uses in production, and every endpoint quoted here is live while you read this. No sandbox, no test tokens, no mocks.
What you will build
- Why per-call payment fits agent tools
- The x402 handshake, seven steps
- Two shapes for a paid MCP server
- Step 1: choose work worth charging for
- Step 2: price it in atomic units
- Step 3: make the endpoint paid
- Step 4: charge inside a tool call
- Step 5: verify it against a real 402
- Step 6: get discovered
- Step 7: test the buyer side
- Production rules that keep you honest
- FAQ
Why per-call payment fits agent tools
A subscription is a bet that a customer will use a service repeatedly and predictably. An autonomous agent is the opposite: it discovers a tool the moment it needs one, calls it two or three times, and may never return. Three properties make per-call payment the natural fit.
- Real marginal cost. If a tool burns GPU time, model tokens, RPC calls, or paid upstream data, every call costs you money. Charging per call is the only pricing that tracks the cost honestly.
- No signup a machine can complete. API keys assume an account, an email confirmation, and a dashboard visit. An agent has a wallet, not an inbox. A payment protocol it can satisfy in one round trip is the difference between being callable and being skipped.
- Price is discoverable before purchase. The 402 challenge states the price, the asset, the chain, and the recipient. A caller can read the price and decide, which is exactly what a spending limit needs to work.
The x402 handshake, seven steps
x402 takes the HTTP status code that has been reserved and unused since 1997 and turns it into a working payment rail. The whole protocol:
- The client requests your resource with no payment attached.
- You answer
402with a JSON body whoseaccepts[]lists every payment you will take: scheme, network, asset, amount, pay-to address, timeout. - The client picks one accept and signs a payment for it (an SPL transfer on Solana, a gasless EIP-3009 authorization on Base).
- The client retries the same request with the signed payload in an
X-PAYMENTheader. - You verify the payment with a facilitator's
/verify. Nothing has moved yet. - You do the work. Only now.
- You settle with the facilitator's
/settle, then return200with the receipt inX-PAYMENT-RESPONSE.
The ordering in steps 5 to 7 is the whole design. Verify before you work, so you never do unpaid work. Settle after the work succeeds, so a failure moves no money and the buyer can retry the same payment. Get this backwards and you will either be farmed for free compute or charge for errors.
Two shapes for a paid MCP server
There are two places the payment can live, and picking the wrong one is the most common early mistake.
| Shape | Where payment happens | Choose it when |
|---|---|---|
| A. Paid HTTP endpoint | An ordinary HTTPS route answers 402 and settles the X-PAYMENT header. |
Your capability is useful to any caller: agents, scripts, backends, MCP clients through a buyer-side payer. This is the default. You may never need to ship an MCP server at all. |
| B. Paid MCP tool | Inside tools/call. The payment travels in _meta["x402/payment"] and the receipt comes back in _meta["x402/payment-response"]. |
The value is the tool ergonomics themselves: schemas, prompts, and a conversation-native surface, and you want per-tool prices inside one connector. |
Most teams should build A first and add B when a client base asks for it. Shape A is a smaller surface, is testable with curl, and is what discovery indexes. Shape B is worth it when you run several priced tools behind one connector, which is how Granite over MCP exposes five metered models with a free explainer tool in front of them.
Prerequisites
- Node 18 or newer. The server package uses global
fetchand Web Crypto. - A public HTTPS URL. Buyer libraries refuse to send signed payloads to plaintext origins, so localhost works for unit tests only.
- A Solana address to receive USDC. You do not need SOL in it. Settlement is facilitator-broadcast, and the fee payer is not you.
- A piece of work worth money. The next step is about being honest with yourself here.
Step 1: choose work worth charging for
A paid endpoint that wraps a free public API is not a business, it is a toll booth on an open road, and agents route around it as soon as one of them reads the docs. Put a candidate through three tests before writing code.
- Can it be done client-side? If the calling model can do it in-context, it will, and it should. Charge for what requires your data, your credentials, your hardware, or your index.
- Does each call cost you something? Marginal cost is what makes per-call pricing defensible instead of extractive.
- Is the output structured? The buyer is a language model. JSON it can reason over is worth more than prose it has to parse.
Three that pass, all running on three.ws today: model inspection (fetch a glTF or GLB, return vertex and material counts plus optimization advice), agent reputation lookup (aggregate on-chain review history), and ticker availability (check a symbol against the live registry). Each needs something the caller does not have locally.
Step 2: price it in atomic units
Prices are strings of atomic units, never floats. USDC has six decimals, so:
| Price | Atomic units | Fits |
|---|---|---|
| $0.001 | '1000' | Cheap lookups, validation, metadata reads |
| $0.01 | '10000' | A small model call, a parse, a summary |
| $0.05 | '50000' | Multi-step inference, a forecast, a render |
| $0.25 | '250000' | Heavy GPU work, long generation |
For calibration, here is what the live Granite tool tier charges per call: chat $0.02, code $0.025, embeddings $0.005, document analysis $0.04, time-series forecast $0.05. Agents are price sensitive in aggregate, so the practical ceiling for an impulse call with no prior relationship sits somewhere near a nickel. Above that, expect the caller to check a spend cap first, and make sure your 402 metadata explains what the money buys.
Step 3: make the endpoint paid
The seller side is one wrapper. Install the merchant package:
npm install @three-ws/x402-server
Then wrap the handler. Unpaid requests get the challenge, paid ones run your code:
import { paid } from '@three-ws/x402-server';
export default paid(
{
price: '10000', // $0.01 in 6-decimal USDC atomics
asset: 'usdc',
payTo: { solana: 'YOUR_SOLANA_ADDRESS' },
description: 'Document summarization',
serviceName: 'Summarize',
tags: ['nlp', 'summary'],
},
async (req, res) => {
const { text } = req.body;
res.json({ summary: await summarize(text) });
},
);
That is a complete paid API. The wrapper builds the v2 challenge envelope, mirrors it into the PAYMENT-REQUIRED header for header-only clients, verifies the retry against a facilitator, runs your handler, settles, and attaches the receipt. It is framework agnostic: mount it as Express or Connect middleware, a Fastify hook, or a bare Node handler.
Two chains, one config
Add a second lane and the challenge advertises both accepts. The buyer picks whichever chain it holds funds on. Solana leads when both are present.
import { paid } from '@three-ws/x402-server';
export default paid(
{
price: '50000', // $0.05
asset: 'usdc',
payTo: {
solana: 'YOUR_SOLANA_ADDRESS',
base: '0xYourBaseAddress',
},
network: ['solana', 'base'],
maxTimeoutSeconds: 60,
onSettled: (receipt) => recordSale(receipt),
},
async (req, res, payment) => {
const result = await render(req.body.prompt);
res.json({ result, paidBy: payment.payer });
},
);
Two details worth internalizing. The handler's third argument, payment, exists only on a paid call and carries { payer, network, accept, amount }, which is your per-caller analytics key and your rate-limit key. And throwing from the handler skips settlement entirely: no funds move, the buyer gets a 500, and the same signed payment can be retried. Let your errors throw. Do not catch them into a fake success.
Response ordering
By default the wrapper buffers your response, settles, then flushes the 200 with the receipt attached, so the buyer never receives the goods before payment lands. For responses you cannot buffer, a large file or an SSE stream, set streaming: true and settlement runs first, before your handler writes a byte.
Taking a platform fee without marking up the buyer
If you host other people's tools, feeBps splits your cut out of the listed price rather than adding to it. feeSplit() computes it as floor(price × bps / 10000), and returns nothing when the fee would round to zero atomic units, in which case the creator receives the full amount. The buyer's total is whatever the challenge said. Surprise billing is how you lose a marketplace.
paid({
price: '50000',
asset: 'usdc',
payTo: { solana: 'CREATOR_SOLANA_ADDRESS' },
feeBps: 250, // 2.5%, capped at 1000 (10%)
feeTo: 'YOUR_TREASURY_ADDRESS',
}, handler);
Step 4: charge inside a tool call
For shape B the payment rides in the MCP message itself. An unpaid tools/call returns a payment-required envelope quoting the price and the receiving address. The client signs, retries with the payload in _meta["x402/payment"], and the settlement receipt comes back in _meta["x402/payment-response"]. x402-capable clients run that loop without asking the user anything.
Do not hand-roll that envelope. The @x402/mcp transport package builds it, and one resource server per process handles verification and settlement for every paid tool you register:
import { HTTPFacilitatorClient, x402ResourceServer } from '@x402/core/server';
import { declareDiscoveryExtension } from '@x402/extensions/bazaar';
import { createPaymentWrapper, createToolResourceUrl } from '@x402/mcp';
import { registerExactSvmScheme } from '@x402/svm/exact/server';
const NETWORK_SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
const resourceServer = new x402ResourceServer([
new HTTPFacilitatorClient({ url: 'https://facilitator.payai.network' }),
]);
registerExactSvmScheme(resourceServer, {});
await resourceServer.initialize();
const resourceUrl = createToolResourceUrl('summarize_document');
const accepts = await resourceServer.buildPaymentRequirementsFromOptions(
[{
scheme: 'exact',
network: NETWORK_SOLANA_MAINNET,
payTo: process.env.X402_PAY_TO_SOLANA,
price: '$0.02',
maxTimeoutSeconds: 60,
extra: { name: 'USDC', decimals: 6 },
}],
{ resourceUrl },
);
const wrap = createPaymentWrapper(resourceServer, {
accepts,
resource: { url: resourceUrl, description, mimeType: 'application/json' },
extensions: declareDiscoveryExtension({ toolName: 'summarize_document', description, transport: 'stdio', inputSchema }),
});
server.registerTool('summarize_document', schema, wrap(async (args) => ({
content: [{ type: 'text', text: await summarize(args.text) }],
})));
The wrapper answers an unpaid call with the challenge, verifies the retry, runs your handler, settles, and attaches the receipt. declareDiscoveryExtension is the part people skip: it is what puts your tool in the catalogs agents search when they go shopping.
Ship one free tool alongside the paid ones. A tool named something like getting_started that returns your price list and explains the payment flow costs you nothing and removes the guesswork for a client that has never paid an MCP tool before. Every paid connector we run has one, and it is consistently the first tool a new caller invokes.
A complete, runnable version of this shape (one free tool, one paid tool, 15 tests, real SSRF-guarded work) is in examples/paid-mcp-server, and the step-by-step build is the tutorial Monetize an MCP server.
Step 5: verify it against a real 402
Before trusting your own implementation, look at a working one. This is a live production endpoint, and the response below is what it returns right now to an unpaid request:
curl -s 'https://three.ws/api/x402/model-check?url=https://three.ws/avatars/cesium-man.glb'
{
"x402Version": 2,
"error": "X-PAYMENT header is required",
"accepts": [
{
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"amount": "1000",
"payTo": "wwwwwDxFWRn7grgr3Esrsg5C6NvDoDHSA4gaCffccrU",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"maxTimeoutSeconds": 60,
"resource": "https://three.ws/api/x402/model-check",
"extra": { "name": "USDC", "decimals": 6, "feePayer": "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW" }
}
]
}
Four things to copy from it. The status is 402, not 400 or 403, because buyer libraries branch on the code. The network is a CAIP-2 identifier, not a friendly name. The Solana accept carries extra.feePayer, which is mandatory on that lane and the single most common omission. And the same price is offered on more than one chain so a caller with the wrong balance is not turned away.
Now check your own endpoint the same way, and confirm the unhappy paths: a malformed X-PAYMENT header returns a fresh 402 rather than a 500, an underpayment is rejected, and a handler that throws leaves the buyer's balance untouched.
Step 6: get discovered
A paid endpoint nobody can find earns nothing. Three surfaces matter, in this order.
- Facilitator discovery. Your challenge metadata (
serviceName,description,tags,iconUrl) is what catalogs read. Fill all of it. A row that just says "paid endpoint" is a row no agent picks. - The bazaar. three.ws/api/mcp-bazaar is an MCP server over the merged live facilitator catalog, with
search_services,browse_services, andget_service. Its listings come from live discovery, never a cached copy, so a correct challenge is all it takes to appear. - The MCP Registry. If you shipped shape B, publish a
server.jsonmanifest so clients can install your connector by name.
One nuance about facilitators: PayAI is free, community run, and covers both Solana and Base. Coinbase CDP settlements additionally get indexed by catalogs that autonomous agents query. The protocol lets you advertise several networks in one challenge, so running both is normal and costs nothing architecturally.
Step 7: test the buyer side
The last mile is proving an assistant can find, price, and buy your tool without a human in the loop. Give Claude a self-custodial x402 wallet:
claude mcp add x402 --env SOLANA_SECRET_KEY=<base58> -- npx -y @three-ws/x402-mcp
That connector exposes four tools: x402_wallet (address and live balances), find_services (search the bazaar), inspect_endpoint (read a 402's requirements without paying), and pay_and_call (pay in USDC and return the result). The first three need no key at all, which makes inspect_endpoint the fastest way to see your own challenge exactly as a buyer sees it.
Note how the spending controls are layered, because this is the pattern to imitate if you build your own payer. pay_and_call is annotated as destructive so the client prompts first. The server probes the 402 and refuses before any money moves if the price exceeds MAX_PAY_USD, which defaults to one dollar. And with REQUIRE_CONFIRM on, the call refuses until it is reissued with confirm: true. Three independent brakes, none of which trust the seller's quoted price.
Production rules that keep you honest
- Verify, work, settle. In that order, every time. Any other order either gives away work or charges for failures.
- Never mark up the listed price. Fees come out of what the challenge advertised. The buyer's total must equal the quote.
- Make retries safe. A settled payment plus a failed response is the case that generates support mail. Key your work on the payment payload so a retry returns the same result instead of charging twice.
- Log every settlement.
onSettledgives you the receipt with network, payer, and transaction. That is your ledger, your fraud signal, and your revenue report. - Rate limit by payer. Payment is not authorization. A funded wallet can still hammer you.
- Return real errors. Quota exhausted, upstream down, bad input: say so with a status the caller can act on. An agent that gets a clear
503retries later. One that gets a vague200full of nonsense stops trusting the tool.
FAQ
Do buyers need an account with me?
No. That is the point. A wallet with USDC and an x402-capable client is the entire onboarding. There is no signup, no API key issuance, and no billing relationship to maintain.
Which chain should I settle on?
Solana first. Fees are negligible relative to a fraction-of-a-cent call, confirmation is fast enough to sit inside a request, and it is where the agent-payment volume is. Add Base as a second accept so EVM-funded callers are not excluded, and let the buyer choose.
What stops an agent from paying for something worthless?
Buyer-side caps, which is why every serious payer enforces them independently of the seller. A price ceiling per call, an explicit confirmation step, and a pre-payment probe of the 402 mean the wallet holder sets the risk, not the merchant.
Can I charge different prices for different tools in one MCP server?
Yes. In shape B the price is per tool call, so one connector can hold a free explainer tool, a half-cent lookup, and a five-cent inference call. Quote the price in each tool's own challenge.
What happens if my handler crashes after the payment is verified?
Nothing moves. Verification is not settlement. A thrown error skips settlement, the buyer keeps their funds, and the signed payment stays valid for a retry inside its timeout window.
Is this only for AI tools?
No. Any HTTP endpoint can take payment this way: data lookups, file conversion, rendering, scraping, geocoding. Agents are simply the first buyers with a wallet and no patience for signup forms.
Further reading
- x402 endpoints reference: every paid route on the platform, with prices and payloads.
- Tutorial: build a paid x402 endpoint your agent calls: the long-form walkthrough, from scaffold to production.
- Tutorial: pay for an x402 service: the buyer side, end to end.
- The x402 bazaar MCP server: how discovery works and what it indexes.
- MCP on three.ws: connectors, transports, and OAuth.
- Why x402 is Stripe for agent payments: the argument behind the protocol.
Packages used here: @three-ws/x402-server (seller), @three-ws/x402-mcp (buyer), and @three-ws/x402-fetch (a paying fetch for your own clients). Source for all three is in the three.ws repository.