What this guide covers
The code-level steps to take an MCP server that serves tools for free and start charging for the premium ones, settled in USDC on Base. This is the technical "how to add" guide. For the business framing (what to charge, which tools to gate), see how-to-monetize-mcp-server. For the broader reference, the MCP integration page.
One thing to set straight first, because the mental model matters. Payment is enforced at the HTTP layer, not per MCP tool name. You run your server over the Streamable HTTP transport, and you mount an x402 adapter in front of the route. The adapter answers 402 Payment Required, and on the caller's retry it verifies the payment and settles before your handler runs. That is the whole shape.
Prerequisites
Have these ready:
- An MCP server built with the official Model Context Protocol SDK, exposed over the Streamable HTTP transport on Fastify or Express.
- A Blockchain0x account. The server is the payee, so you need its wallet address (the
payToAddress) and apaymentRequestIdfor the priced route. - A
sk_test_API key for this walkthrough. - Node 18+. The SDK targets
>=18.
Set the server's env:
export B0X_API_KEY=sk_test_... # sk_test_ -> Base Sepolia
export B0X_PAYTO_ADDRESS=0x... # the server wallet that receives USDC
export B0X_PRICE_REQUEST_ID=pr_... # payment-request id for the priced routeInstall the package
Two real packages: the client and the x402 server adapter.
npm install @blockchain0x/node @blockchain0x/x402@blockchain0x/node gives you the client the adapter uses to settle. @blockchain0x/x402 ships the server adapters at ./server/fastify and ./server/express. There is no MCP-specific package, and you do not need one. The adapter gates the route your MCP transport already uses.
Mount the x402 plugin
Put the adapter in front of the route your MCP server handles. The pricing table is keyed by METHOD path.
Fastify:
import Fastify from "fastify";
import { createClient } from "@blockchain0x/node";
import { createX402Plugin } from "@blockchain0x/x402/server/fastify";
const sdk = createClient({ apiKey: process.env.B0X_API_KEY! }); // sk_test_ -> Base Sepolia
const app = Fastify();
await app.register(createX402Plugin, {
sdk,
defaultNetwork: "testnet",
pricing: {
"POST /mcp/premium": {
amountUsdc: "0.005",
payToAddress: process.env.B0X_PAYTO_ADDRESS!,
paymentRequestId: process.env.B0X_PRICE_REQUEST_ID!,
},
},
});
// Your existing MCP-over-HTTP handler is unchanged.
app.post("/mcp/premium", async (req, reply) => {
return handleMcpRequest(req.body); // your streamable-HTTP handler
});
await app.listen({ port: 8080 });Express:
import express from "express";
import { createClient } from "@blockchain0x/node";
import { createX402Middleware } from "@blockchain0x/x402/server/express";
const sdk = createClient({ apiKey: process.env.B0X_API_KEY! });
const app = express();
app.use(
createX402Middleware({
sdk,
defaultNetwork: "testnet",
pricing: {
"POST /mcp/premium": {
amountUsdc: "0.005",
payToAddress: process.env.B0X_PAYTO_ADDRESS!,
paymentRequestId: process.env.B0X_PRICE_REQUEST_ID!,
},
},
}),
);
app.post("/mcp/premium", express.json(), (req, res) => {
res.json(handleMcpRequest(req.body));
});That is the integration. The adapter intercepts POST /mcp/premium, and your handler only runs once payment is verified.
What the 402 looks like
When an unpaid caller hits the priced route, the adapter returns a real x402 body. This is the wire format, not an invented shape:
{
"version": 1,
"resource": "POST /mcp/premium",
"accepts": [
{
"scheme": "exact-usdc",
"network": "testnet",
"chainId": "eip155:84532",
"payToAddress": "0x...",
"amountWeiUsdc": "5000",
"paymentRequestId": "pr_...",
"maxAgeSeconds": 300
}
]
}amountWeiUsdc is 0.005 USDC expressed in 6-decimal base units, so 5000. A paying client reads the requirement, pays the payToAddress in USDC, and retries the same request with an X-Payment: exact-usdc:<base64> header. The adapter verifies that header and, if it checks out, passes the request to your handler. From the agent's side it is one tool call that took a beat longer.
Free and paid tools side by side
Because pricing is per route, free and paid tools coexist by living on different paths. Keep your discovery and cheap tools on an unpriced route, and put the premium ones behind a priced one.
await app.register(createX402Plugin, {
sdk,
defaultNetwork: "testnet",
pricing: {
"POST /mcp/premium": { amountUsdc: "0.01", payToAddress: process.env.B0X_PAYTO_ADDRESS!, paymentRequestId: process.env.B0X_PRICE_REQUEST_ID! },
},
});
// /mcp/free is not in the pricing table, so it is never gated.
app.post("/mcp/free", async (req) => handleMcpRequest(req.body));
app.post("/mcp/premium", async (req) => handleMcpRequest(req.body));This is the honest granularity. If you want ten tools at ten prices, that is ten routes, not ten decorators on one path. Most servers only gate one or two premium routes, so this stays simple in practice.
Verify with a paid client
The fastest end-to-end check is to call the priced route from a client that holds a Blockchain0x test wallet and speaks x402 (the pay-side createX402Client from @blockchain0x/x402/client does this automatically). Start your server, point the client at /mcp/premium, and watch the 402, the settle, and the 200 in your logs.
For a quick smoke test without a wallet, curl the route with no X-Payment header and confirm you get the 402 body above. That proves the adapter is mounted and quoting the right price before you wire a real payer.
When a payment does fail, the adapter tells you why rather than returning a vague error. A missing header reads as header_missing, a corrupt one as header_malformed, a payment that does not match the quoted price or network as requirement_mismatch, and a settle call the chain rejected as settle_rejected. Log that reason on every non-2xx. In the first week it is the single fastest way to tell "the client never paid" apart from "the client paid the wrong amount", and the two have completely different fixes.
Test on Base Sepolia
Run the whole loop on testnet first. With defaultNetwork: "testnet" and a sk_test_ key, settlement happens on Base Sepolia (eip155:84532) with the same shapes as mainnet. The paying client funds its wallet with test USDC from a public Base Sepolia faucet, then calls your route.
One honest caveat: settlement is in active rollout, and the verify path calls paymentRequests.settle, which can return a 503 on a network whose chain adapter is not live yet. Handle a non-2xx from the adapter as "payment not confirmed" rather than assuming success. The test-agent-payments guide has the full sandbox flow, and production pricing is on the pricing page.
Common pitfalls
Four mistakes that cost an afternoon in week one.
Gating the whole server. Putting every route in the pricing table means clients pay just to list your tools. Keep discovery and cheap tools on an unpriced path. Gate only the premium routes.
Trying to price per tool name on one path. The adapter keys on METHOD path. Ten tools on one JSON-RPC route get one price. Split premium tools onto their own routes if you need separate prices, and do not try to read the tool name inside the pricing table.
Shipping a stdio-only server and expecting payment. There is no HTTP layer to gate on pure stdio. Expose the Streamable HTTP transport for any server that needs to charge.
Booting with the wrong key prefix. A server that starts with sk_test_ in production quotes Base Sepolia requirements that real wallets will not pay. The server stamps the network from the key, so check the prefix at boot and refuse to start on a mismatch.
What to ship today
The shortest path from "free MCP server" to "paid MCP server":
- Make sure your server runs over the Streamable HTTP transport.
npm install @blockchain0x/node @blockchain0x/x402.- Register
createX402Plugin(orcreateX402Middleware) with one priced route. - Curl the route with no payment, confirm the 402, then pay it from a test wallet.
That is the integration. Adding a second priced route later is one more entry in the pricing table. For the decision side (what to charge and which tools to gate), continue with how-to-monetize-mcp-server or mcp-server-monetization.