Skip to main content
Glama
catena-oss

x402-mcp-demo

by catena-oss

x402-mcp-demo

一个MCP服务器,其工具调用通过x402进行计量和收费,外加一个付费代理参考客户端,让任何标准MCP客户端都能使用付费工具而无需知道x402的存在。结算使用Base Sepolia测试网上的真实USDC,资金进入Catena沙箱账户。

flowchart LR
  CL["Standard MCP client<br/>Claude Code, Inspector"] -->|stdio JSON-RPC| PX["Paying proxy<br/>holds the wallet, spend cap"]
  PX -->|Streamable HTTP + x402| SV["Paid MCP server<br/>gate in front of the handler"]
  SV -->|verify then settle| F[Facilitator]
  F -->|USDC| CA[(Catena sandbox account)]

  classDef pay stroke-width:2px
  class PX,SV pay

工作原理

x402挑战位于MCP Streamable HTTP传输的HTTP层,在JSON-RPC框架之下,因此MCP协议本身不受影响,标准客户端保持兼容。

  • initialize、tools/list和免费的pricing工具不产生任何费用。

  • 对premium_market_signal的tools/call会触发一个带有x402 v2挑战(精确方案)的402响应。代理支付该挑战,协调器将资金结算到配置的payTo地址,然后才返回成功的工具结果。中间件顺序是不变规则:未支付的调用永远不会到达工具处理程序;MCP HTTP 4xx会取消结算。

  • 当代理的累计金额超过PROXY_SPEND_CAP_USD时,代理会在支付之前拒绝付费调用。该上限是配置项,绝不从工具参数中推导,因此通过提示注入的工具调用无法提高该上限。

逐次调用的完整流程(包括结算被取消的情况)请参见docs/architecture.md。

Related MCP server: x402 MCP Proxy

设置

需要Node >= 22.13(参见.nvmrc)和pnpm。

corepack enable
pnpm install
cp .env.example .env
# SELLER_PAY_TO_ADDRESS: your Catena sandbox account's base-sepolia USDC
#   deposit address, from app.catena.com
# BUYER_EVM_PRIVATE_KEY: a testnet wallet the proxy pays from. Fund it with
#   Base Sepolia USDC at https://faucet.circle.com (select Base Sepolia).
#   USDC only; no ETH is needed, transfers are gasless EIP-3009.

当配置缺失或无效时,两个入口点均以退出码2退出;当所需的依赖项不可达时(服务器的协调器,代理的上游MCP服务器),以退出码1退出。

演示:一条命令完成整个循环

pnpm demo

启动付费服务器(连接公共x402协调器),通过付费代理驱动标准MCP客户端,并打印:免费发现,然后付费工具调用将0.001美元测试网USDC结算到Catena存款地址。

亲自查看402

在一个终端中运行pnpm server,然后在不付费的情况下请求付费工具。服务器通过/healthz返回其价格和付费工具名称,代理在启动时也会探测该端点:

curl -s http://localhost:4040/healthz
{"status":"ok","paidTool":"premium_market_signal","price":"$0.001"}

挑战本身位于PAYMENT-REQUIRED响应头中,而不是在响应体中(响应体为{}),因此需要解码该头来读取:

curl -si -X POST http://localhost:4040/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"premium_market_signal","arguments":{"topic":"usdc"}}}' \
  | grep -i '^payment-required:' | tr -d '\r' | cut -d' ' -f2 | base64 -d
{"x402Version":2,"error":"Payment required","resource":{"url":"http://localhost:4040/mcp","description":"One invocation of the premium_market_signal MCP tool","mimeType":""},"accepts":[{"scheme":"exact","network":"eip155:84532","amount":"1000","asset":"0x036CbD53842c5426634e7929541eC2318f3dCF7e","payTo":"0x000000000000000000000000000000000000dEaD","maxTimeoutSeconds":300,"extra":{"name":"USDC","version":"2"}}]}

去掉| grep ...可以看到状态行:HTTP/1.1 402 Payment Required。工具从未运行,因此没有发生结算。

从Claude Code(标准客户端)使用

在一个终端中运行付费服务器(pnpm server),然后在.mcp.json中将代理注册为普通的stdio MCP服务器:

{
  "mcpServers": {
    "paid-market-signal": {
      "command": "pnpm",
      "args": ["--dir", "/path/to/x402-mcp-demo", "proxy"]
    }
  }
}

代理从本仓库的.env文件中读取BUYER_EVM_PRIVATE_KEY和UPSTREAM_MCP_URL,因此无需将密钥放入.mcp.json。(.mcp.json在此处已被git忽略;如果您复制此设置,请保持相同做法。)

Claude Code会列出两个工具并正常调用它们;代理在后台支付402。MCP Inspector的工作方式相同:npx @modelcontextprotocol/inspector pnpm proxy。

测试

pnpm test针对一个进程内服务器(带有记录型假协调器)运行服务器和代理测试套件:无需网络,无需资金。每个涉及资金的路径不变规则都有对应的测试,如果被破坏则会失败。

不变规则

测试

发现和免费工具不产生任何费用

无需任何支付即可提供initialize、tools/list和免费工具

未支付的付费工具调用在运行前会收到402

在工具运行前,以402挑战拒绝未支付的付费工具调用

付费调用恰好结算一次

客户端支付后运行付费工具,且发现功能在之后仍然免费

通过代理时发现功能也保持免费

通过代理保持免费接口的免费性

标准客户端无需知道x402即可支付

透明地支付付费工具并返回其结果

JSON-RPC批处理被拒绝,不会逐项门控

直接拒绝JSON-RPC批处理请求(失败关闭)

MCP HTTP 4xx取消结算

当付费调用返回MCP HTTP 4xx时不进行结算

通知(无id)永远不会被收费

不对无id的通知形式的付费tools/call收费

无法解析的请求体被拒绝,不进行定价

拒绝以text/plain形式发送的付费tools/call,不解析也不收费

每次付费调用对应一次上游执行

对付费调用发送两次(402后重试支付),但只结算一次

只签署固定网络上的USDC

拒绝未签名的策略外挑战(错误网络、错误资产)

支出上限在任何支付之前生效

在任何支付之前,拒绝超出支出上限的调用

并发调用不能都低于上限

限制并发付费调用:在单次调用上限下,两次并发调用中只有一次能结算

范围

仅限公共接口:MCP TypeScript SDK、公共x402包和协调器,以及作为接收方的Catena沙箱账户。版本和限制:docs/architecture.md。

许可证

MIT

Related MCP Connectors

Related MCP Servers