If you've built MCP servers for Claude Code, you know the distribution problem: anyone who installs your server gets unlimited free access. There's no clean way to charge per call without rolling your own auth + billing.
x402 solves this at the HTTP layer. Here's how to build a pay-per-call MCP server where every tool call costs USDC — settled instantly on Base.
What is x402?
x402 is an HTTP payment protocol that revives the abandoned 402 Payment Required status code. The flow:
- Client requests a resource
- Server returns
402with aPAYMENT-REQUIREDheader containing payment details (price, wallet, network) - Client signs a USDC transfer using EIP-3009 (no gas needed from the client)
- Client retries with a
PAYMENT-SIGNATUREheader - Coinbase facilitator validates and settles on-chain
- Server returns the resource
No API keys. No billing infrastructure. No subscriptions. Just HTTP + crypto.
The Stack
@modelcontextprotocol/sdk — MCP server
@x402/core — x402 client protocol
@x402/evm — EVM payment signing
viem — wallet/account management
Building the Payment Client
The key is wiring up the x402 client correctly. The v2 API (current as of @x402/core 2.11) looks like this:
const { x402Client, x402HTTPClient } = require("@x402/core/client");
const { ExactEvmScheme } = require("@x402/evm/exact/client");
const { toClientEvmSigner } = require("@x402/evm");
const { privateKeyToAccount } = require("viem/accounts");
function buildHttpClient() {
const key = process.env.WALLET_PRIVATE_KEY;
const pk = key.startsWith("0x") ? key : "0x" + key;
const account = privateKeyToAccount(pk);
const signer = toClientEvmSigner(account);
const coreClient = new x402Client().register("eip155:*", new ExactEvmScheme(signer));
return new x402HTTPClient(coreClient);
}
Important: pass
accounttotoClientEvmSigner, notwalletClient. In viem v2,walletClient.addressis undefined — the address lives onaccount.address.
Making a Paid Request
The http client doesn't have a .fetch() method in v2. You handle the 402 flow manually:
async function paidFetch(httpClient, url) {
const res = await fetch(url);
if (res.status !== 402) {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// Extract payment requirements from header
let body;
try { body = await res.clone().json(); } catch (_) {}
const paymentRequired = httpClient.getPaymentRequiredResponse(
(name) => res.headers.get(name),
body
);
// Sign and send payment
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
const paidRes = await fetch(url, {
headers: httpClient.encodePaymentSignatureHeader(paymentPayload),
});
const raw = await paidRes.text();
if (!paidRes.ok) throw new Error(`Payment rejected (${paidRes.status}): ${raw}`);
return JSON.parse(raw);
}
Wiring it into an MCP Tool
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
const { CallToolRequestSchema, ListToolsRequestSchema } = require("@modelcontextprotocol/sdk/types.js");
const BASE_URL = "https://your-x402-endpoint.com";
const TOOLS = [{
name: "get_data",
description: "Get premium data. Costs $0.01 USDC.",
inputSchema: { type: "object", properties: {} }
}];
async function main() {
const httpClient = buildHttpClient();
const server = new Server(
{ name: "my-mcp", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
try {
const data = await paidFetch(httpClient, `${BASE_URL}/api/data`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
} catch (e) {
return { content: [{ type: "text", text: "Error: " + e.message }], isError: true };
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
Building the Server Side
On the server, use @x402/express with paymentMiddleware:
const { paymentMiddleware } = require("@x402/express");
const { x402ResourceServer, HTTPFacilitatorClient } = require("@x402/core/server");
const { ExactEvmScheme } = require("@x402/evm/exact/server");
const facilitator = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator/" });
const server = new x402ResourceServer(facilitator);
server.register("eip155:8453", new ExactEvmScheme());
app.use(paymentMiddleware({
"GET /api/data": {
accepts: {
scheme: "exact",
price: "$0.01",
network: "eip155:8453",
payTo: "0xYourWalletAddress"
},
description: "Premium data endpoint"
}
}, server));
app.get("/api/data", (req, res) => {
res.json({ result: "your data here" });
});
Every request to /api/data now requires a $0.01 USDC payment on Base before the route handler fires.
The CDP Bazaar Bonus
If you add @x402/extensions/bazaar to your server, Coinbase's Bazaar protocol automatically indexes your service on agentic.market. Autonomous agents with funded wallets can discover and pay for your endpoint without any human involvement.
const { declareDiscoveryExtension } = require("@x402/extensions/bazaar");
app.use(paymentMiddleware({
"GET /api/data": {
accepts: { scheme: "exact", price: "$0.01", network: "eip155:8453", payTo: "0x..." },
extensions: { ...declareDiscoveryExtension({ output: { example: { result: "..." } } }) }
}
}, server));
Publishing
// package.json
{
"name": "my-mcp",
"bin": { "my-mcp": "index.js" },
"dependencies": {
"@modelcontextprotocol/sdk": "^1.10.1",
"@x402/core": "^2.11.0",
"@x402/evm": "^2.11.0",
"viem": "^2.0.0"
}
}
npm publish --access public
Users install with:
{
"mcpServers": {
"my-mcp": {
"command": "npx",
"args": ["my-mcp"],
"env": { "WALLET_PRIVATE_KEY": "0x..." }
}
}
}
A Working Example
I built this for coinopai-mcp — 6 tools covering agent automation prompts and crypto signals, all pay-per-call via x402.
npm install coinopai-mcp — source on GitHub at clawdbotworker/coinopai-mcp.
The whole server is under 150 lines. x402 does the hard part.
If you build something with this pattern, I'd genuinely like to see it. Drop a link in the comments — I read them all.
United States
NORTH AMERICA
Related News
How Braze’s CTO is rethinking engineering for the agentic area
11h ago
Amazon Employees Are 'Tokenmaxxing' Due To Pressure To Use AI Tools
22h ago
KDE Receives $1.4 Million Investment From Sovereign Tech Fund
2h ago
Instagram’s new ‘Instants’ feature combines elements from Snapchat and BeReal
2h ago
Six Claude Code Skills That Close the AI Agent Feedback Loop
2h ago