r402-mcp
MCP transport for the x402 payment protocol, part of the
r402 workspace.
Design
Binds the official Rust MCP SDK rmcp
(modelcontextprotocol/rust-sdk), the same role as:
| Language | Official MCP SDK |
|---|---|
| Go | github.com/modelcontextprotocol/go-sdk/mcp |
| TypeScript | @modelcontextprotocol/sdk |
| Rust (this crate) | rmcp |
Protocol behaviour is pinned to:
specs/transports-v2/mcp.md- Go
go/mcp/*(primary control-flow pin) - TypeScript
@x402/mcp(secondary)
V1 payment paths are omitted (r402 is V2-only).
Constants (must match foundation)
| Name | Value |
|---|---|
MCP_PAYMENT_REQUIRED_CODE |
402 (i32) |
MCP_PAYMENT_META_KEY |
"x402/payment" |
MCP_PAYMENT_RESPONSE_META_KEY |
"x402/payment-response" |
Payment-required tool results set both structuredContent and
content[0].text (JSON of the same PaymentRequired object).
Dependency
[]
= "0.14"
= { = "0.15", = ["server"] } # or "client" / "full"
# optional umbrella:
# r402 = { version = "0.15", features = ["mcp", "server"] }
| Feature | Surface |
|---|---|
server |
PaymentWrapper + encode helpers |
client |
X402McpClient + McpToolCaller / PaymentSigner |
full |
server + client + telemetry |
Server usage
Requires a Facilitator
(local chain crate or HTTP facilitator). Wire the wrapper around your tool
handler; this crate does not own the MCP transport loop — plug invoke /
wrap into whatever hosts rmcp (stdio, HTTP, etc.).
use Arc;
use ;
use ;
use ;
// 1. Facilitator that can verify + settle (your implementation / HTTP client)
let facilitator = new;
let resource_server = new;
// 2. Advertise one or more payment options for this tool
let accepts = vec!;
let config = try_new?;
let wrapper = try_new?;
// 3a. One-shot: wrap a single tools/call
async
// 3b. Cloneable handler for routers (Go Wrap equivalent)
let paid = wrapper.wrap;
// router.register("get_weather", paid);
Control flow (Go PaymentWrapper.Wrap):
- Extract
_meta["x402/payment"] - Match
accepts→ verify (tool-level 402 on failure) - Hooks → business handler
- On tool success → settle →
_meta["x402/payment-response"] - Settlement failure uses the same dual-format payment-required body (R5)
Client usage
You implement two thin adapters:
McpToolCaller— one method: call MCPtools/call(typically anrmcpclient session).PaymentSigner— turn aPaymentRequiredchallenge into a signedPaymentPayload(scheme clients / your wallet glue).
use PaymentRequired;
use ;
use ;
use Map;
let client = new
.with_options;
let out = client.call_tool.await?;
assert!;
// out.result — tool content; out.payment_response — settle meta if present
Free function: call_paid_tool(caller, signer, name, args).
Hooks (ClientHooks): on_payment_required (abort / supply payload),
on_payment_requested (approve/deny), on_before_payment, on_after_payment.
Encode helpers (shared)
| Helper | Role |
|---|---|
payment_required_tool_result |
dual-format 402 challenge |
settlement_failed_tool_result |
R5 dual-format settle failure |
extract_payment_required |
prefer structuredContent, else text |
attach_payment_to_params / extract_payment_from_params |
_meta["x402/payment"] |
attach_settle_response / extract_settle_response |
_meta["x402/payment-response"] |
create_tool_resource_url |
mcp://tool/{name} or custom |
Known intentional gaps vs foundation
| Item | Notes |
|---|---|
| V1 | Not supported |
SEP-1036 / JSON-RPC -32042 |
TS-only path; not in Go pin |
| Paid retry still 402 | Returns McpClientError::StillRequired (stricter than Go) |
| Live e2e example binary | Not shipped; use unit tests under src/{server,client,encode}.rs |
License
Dual-licensed under MIT and Apache-2.0.