Building an MCP server that AI agents pay for autonomously (Claude → MCP → x402 USDC)
The default model for MCP servers is "expose your tools, trust the caller." That's fine for personal Claude Desktop tools that hit local SQLite, but it doesn't work for paid data — the moment your tool calls a real upstream API, somebody has to pay for the upstream. We took a third path: instead of static API keys baked into the MCP config, our MCP tools speak x402, and Claude (or any MCP-aware agent) signs a $0.008 USDC payment per call. No accounts. No keys. The agent gets exactly what it asked for, the upstream gets paid, and the human is out of the loop.
Highlights
- No static API keys — users provision a wallet (USDC float on Base) and that's it
- Per-call USDC settlement — agent signs autonomously, no human in the loop
- Payment plumbing is invisible to the LLM — it just calls
scan_addressand gets JSON back - Natural budget boundary — when the wallet runs out, calls fail clean
Why most MCP servers can't be paid for
If you've installed an MCP server in Claude Desktop, you know the drill: static API key in env vars, rate limits to track, billing surprises at month-end. The agent might call your tool 50 times in a session and neither the agent nor the user notice. This works for genuinely free tools (local Postgres, filesystem, GitHub). It works poorly for tools that wrap an upstream that charges per call.
We wanted something better: the agent should pay for what it calls, in real money, at the moment of the call.
What x402 is, in 4 lines
x402 is the HTTP 402 status code wired up to stablecoin payments. The exchange:
- Agent calls
GET /api/risk-score?address=0xabc - Server replies
HTTP 402with a price ($0.008 USDC), an asset (USDC on Base), a recipient, and a payload to sign - Agent's wallet signs an EIP-3009
transferWithAuthorizationfor that exact amount, base64-encodes it, and replays the request withX-PAYMENT: <signed payload> - Server verifies the signature, runs the handler, settles the USDC on-chain via the Coinbase CDP facilitator, and returns the response with
X-PAYMENT-RESPONSE: <on-chain receipt>
Sub-second on the protocol level. A few seconds for the on-chain settle on Base. From the agent's perspective: a 402, a retry with payment, then a 200.
Why MCP + x402 fit naturally
MCP (Model Context Protocol) is the standard for exposing tools to LLMs. Tools get a schema, a name, and a callable; the model decides when to invoke them. The runtime path:
LLM
├─→ MCP client
│ └─→ MCP server (chainanalyzer-mcp)
│ ├─→ HTTP GET /api/risk-score ← 402 + price
│ ├─→ wallet.sign(EIP-3009) ← per-call payment
│ ├─→ HTTP GET /api/risk-score ← retry with X-PAYMENT
│ │ [verify → handler → settle]
│ └─→ 200 OK + risk JSON
└─← result returned to LLM x402 fits inside "MCP server runs handler". The MCP tool implementation makes an HTTP call to the upstream. The upstream returns 402. The MCP server has access to a wallet keypair (configured once, owned by the user). It signs the payment, replays, gets the data, returns it to the LLM. The LLM never sees the payment plumbing — it just sees "I called chainanalyzer.scan_address and got a JSON response."
The user setup is less than the API-key version, because there's no signup. Their wallet is their account.
What changed in the MCP server
Our @chainanalyzer/mcp-server used to require an API key. The new version (paid mode) uses the user's wallet:
{
"mcpServers": {
"chainanalyzer": {
"command": "npx",
"args": ["-y", "@chainanalyzer/mcp-server"],
"env": {
"CHAINANALYZER_X402_PRIVATE_KEY": "0x...",
"CHAINANALYZER_X402_NETWORK": "base"
}
}
}
} The private key is the user's payment wallet (must hold USDC on Base). The MCP server uses x402-fetch under the hood to handle the 402 dance:
import { wrapFetchWithPayment } from "x402-fetch"
import { createWalletClient, http, privateKeyToAccount } from "viem"
import { base } from "viem/chains"
const account = privateKeyToAccount(process.env.CHAINANALYZER_X402_PRIVATE_KEY)
const wallet = createWalletClient({ account, chain: base, transport: http() })
const fetch402 = wrapFetchWithPayment(fetch, wallet)
// Inside the MCP tool handler:
const res = await fetch402(
`https://chain-analyzer.com/api/v1/x402/address/${addr}/risk-score?chain=${chain}`,
{ method: "POST" }
)
return await res.json() // automatically retried with payment From the model's perspective, the tool either returned data (paid) or returned a "wallet has insufficient USDC" error (out of money). The latter becomes a natural budget signal — Claude tells the user "your scan budget is exhausted" instead of silently doing nothing.
Gotchas we hit
1. MCP is stdio JSON; HTTP doesn't fit that model
Most MCP servers communicate over stdio. We had to make sure outbound HTTP calls didn't clobber the stdio framing — a separate HTTP client, explicit JSON serialization before yielding back to MCP. Never let raw HTTP bytes leak into the protocol stream.
2. EIP-3009 validAfter / validBefore need real clock skew
The facilitator rejects payments whose timestamps drift too far from network time. If the user's machine clock is 2 minutes off (laptop just woken up, NTP not yet caught up), every signature gets rejected. We added 60s of validAfter tolerance and a 5-minute validBefore window so this rarely bites in practice.
3. Self-payment guard
If the user happened to set payTo to the same address as their signing key, every payment would be a self-loop and the facilitator would reject it. We catch this client-side now and throw immediately with a useful error.
4. Small-balance UX
A $5 USDC float buys 625 calls at $0.008 each — enough for a meaningful Claude session, but if the user is doing batch screening (looping over hundreds of addresses), the float drains and the next call fails. We added a chainanalyzer.check_my_balance tool the LLM can call before kicking off a batch, returning the wallet's USDC balance and the projected number of calls available.
What it costs the user
- Wallet setup: free (any Base-compatible wallet)
- Initial funding: $5 USDC on Base + ~$0.50 of ETH for occasional gas (gas is cheap on L2)
- Per call: $0.008 USDC + negligible Base gas (the facilitator pays gas, the user pays the USDC)
- No subscription, no monthly minimum
Compared to our cheapest Pro plan at $19.99/month: under ~2,500 scans/month, x402 is strictly cheaper. Above that, Pro wins (and gets unmetered AI Analysis). For agentic workloads specifically, x402 is the obvious answer — agents don't have credit cards, agents don't sign up for SaaS, but agents do have wallets.
Try it
npx -y @chainanalyzer/mcp-server …with the wallet env vars set as above. See the chainanalyzer-mcp GitHub repo for the full README, including a 90-second setup video.
For the underlying x402 endpoint set, see our companion post: Shipping a $0.008-per-call AML API on Coinbase x402 Bazaar.
Why this matters beyond crypto
The pattern — MCP tool wraps an x402-priced HTTP API, agent has a wallet, calls cost real money — works for anything an agent might want to buy. Image generation. Flight prices. Address risk data. Translation. The hard parts are 1) actually shipping a working x402 endpoint (see our other writeup for the five things that broke), and 2) wrapping it cleanly in an MCP server so the agent doesn't see the payment plumbing.
The autonomy story for AI agents is currently bottlenecked by economics: agents can browse, reason, plan — but they can't transact. MCP + x402 closes that loop in a way that doesn't require the agent to have a personal credit card or the user to hand it one. The agent has a wallet. The wallet has USDC. That's enough.