feat(x402): add the MCP transport to the buyer skill - #25
Conversation
- Split the MCP paid-tool-call flow out of x402-pay.md into its own workflows/x402-mcp.md, so each workflow file is a single linear path instead of branching between HTTP and MCP mid-file - Reword the caller/session line to third person while moving it - Match References-table wording for the MCP row to the Workflows table - Drop the .gitignore and scripts/test_x402_pay.py added by this branch; script tests aren't an established convention here yet - Move the Cloudflare Agents SDK interop note into a new ## Notes section at the end of references/x402.md, out of the main MCP transport walkthrough, and fix the now-stale "see below" pointer - Make references/x402.md's ## Confirmation section state explicitly when to use inspect/pay vs mcp-inspect/mcp-sign instead of listing the MCP commands as a parenthetical alternative Addresses all 6 review comments from AyushBherwani1998 on PR MetaMask#25. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@AyushBherwani1998 revised, ready for follow up. |
AyushBherwani1998
left a comment
There was a problem hiding this comment.
LGTM, thanks for the contribution. Can you please resolve the conflicts, bump up SKILLs version by minor version, and update changelog.
6898366 to
1ef35cb
Compare
- Split the MCP paid-tool-call flow out of x402-pay.md into its own workflows/x402-mcp.md, so each workflow file is a single linear path instead of branching between HTTP and MCP mid-file - Reword the caller/session line to third person while moving it - Match References-table wording for the MCP row to the Workflows table - Drop the .gitignore and scripts/test_x402_pay.py added by this branch; script tests aren't an established convention here yet - Move the Cloudflare Agents SDK interop note into a new ## Notes section at the end of references/x402.md, out of the main MCP transport walkthrough, and fix the now-stale "see below" pointer - Make references/x402.md's ## Confirmation section state explicitly when to use inspect/pay vs mcp-inspect/mcp-sign instead of listing the MCP commands as a parenthetical alternative Addresses all 6 review comments from AyushBherwani1998 on PR MetaMask#25. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review follow-up on PR MetaMask#25: bump metamask-agent-wallet from 6.3.0 to 6.4.0 (minor — the MCP transport is an additive capability) and record the change in CHANGELOG.md. cliVersion stays 5.3.0; this PR adds skill capability, not coverage of a new CLI release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks @AyushBherwani1998! All three addressed — rebased onto Conflicts. Three, all in files
Version. Changelog. Re-verified after the rebase: One heads-up — the push dismissed your approval as stale, so it'll need a fresh one when you get a chance. Sorry for the round trip; resolving the conflicts required new commits either way. |
x402 v2 defines three transports (http, mcp, a2a); MetaMask#16 landed the HTTP payer. This adds the MCP transport per specs/transports-v2/mcp.md (proposed in MetaMask#23): two new subcommands in x402_pay.py, pure stdlib. - mcp-inspect parses a paid tool's PaymentRequired challenge (JSON-RPC response, tool result, or bare object; structuredContent preferred, content[0].text fallback) and prints the options read-only. - mcp-sign --confirm validates and signs one offered option with `mm wallet sign-typed-data` and prints the PaymentPayload to place, as a raw JSON object, in the retried call's _meta["x402/payment"]. Settlement returns in _meta["x402/payment-response"]. The MCP session stays the caller's: the script signs, the caller delivers. The transport-agnostic core from MetaMask#16 (select/validate/ build_typed_data/sign_typed_data/build_payment) is reused untouched; accepts[] normalization is extracted to _normalize_accepts (shared by both transports, keeping the v1 maxAmountRequired fallback in one place). v2 only: the MCP transport is not defined for x402 v1. Conformance tests (scripts/test_x402_pay.py, stdlib unittest, no network and no mm calls) use the spec's examples verbatim; the core assertion is that the built payload equals the spec's _meta["x402/payment"] object exactly. Docs: MCP sections in references/x402.md and workflows/x402-pay.md, plus SKILL.md routing, validation, and confirmation rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q6hVwjEbcFMiGCx1Ku74tY
Verified live against cloudflare/agents' official x402-mcp example (and confirmed with a full mainnet round-trip: real Base USDC, the unmodified example server, CDP facilitator, tx 0x7f7fe537...bcd6064) that Cloudflare's Agents SDK diverges from the MCP transport spec text two ways: - The PaymentRequired challenge lives in _meta["x402/error"], not structuredContent (there is no structuredContent key at all). parse_mcp_challenge now checks it explicitly instead of relying on the content[].text fallback to catch it by coincidence. - The retry's _meta["x402/payment"] is read via JSON.parse(atob(token)) — a base64-encoded string, not the inline object the spec shows. mcp-sign now also emits paymentBase64 alongside payment, with a note on which to use for which server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q6hVwjEbcFMiGCx1Ku74tY
- Split the MCP paid-tool-call flow out of x402-pay.md into its own workflows/x402-mcp.md, so each workflow file is a single linear path instead of branching between HTTP and MCP mid-file - Reword the caller/session line to third person while moving it - Match References-table wording for the MCP row to the Workflows table - Drop the .gitignore and scripts/test_x402_pay.py added by this branch; script tests aren't an established convention here yet - Move the Cloudflare Agents SDK interop note into a new ## Notes section at the end of references/x402.md, out of the main MCP transport walkthrough, and fix the now-stale "see below" pointer - Make references/x402.md's ## Confirmation section state explicitly when to use inspect/pay vs mcp-inspect/mcp-sign instead of listing the MCP commands as a parenthetical alternative Addresses all 6 review comments from AyushBherwani1998 on PR MetaMask#25. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review follow-up on PR MetaMask#25: bump metamask-agent-wallet from 7.0.0 to 7.1.0 (minor — the MCP transport is an additive capability) and record the change in CHANGELOG.md. cliVersion stays 6.0.0; this PR adds skill capability, not coverage of a new CLI release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1ef35cb to
c97290f
Compare
|
Rebased onto the v6.0.0 Re-versioned to match the new baseline: skill 7.0.0 → 7.1.0, Two things picked up from the v6.0.0 pass while resolving:
Re-verified after the rebase: Ready for another look whenever suits. Happy to rebase again if more of the launch changes land. |
AyushBherwani1998
left a comment
There was a problem hiding this comment.
LGTM. @chaitanyapotti can you PTAL?
Closes #23 (proposed and scoped there). Extends #16 by @basgys, which landed the x402 HTTP payer — this adds the second of x402 v2's three transports, MCP, per the spec (
specs/transports-v2/mcp.md), so an agent can pay x402-gated MCP tools with the samemm-backed signer.What's in it
Two new subcommands in
scripts/x402_pay.py— pure stdlib, zero new dependencies:mcp-inspect— parses the paid tool'sPaymentRequiredchallenge and prints the payment options, read-only. Accepts the whole JSON-RPC response, the tool result, or the barePaymentRequiredobject (via--challenge— pass-to read stdin — or--challenge-file); checksstructuredContent,content[0].text, and_meta["x402/error"](see below).mcp-sign --confirm— validates and signs one offered option withmm wallet sign-typed-dataand prints thePaymentPayloadfor the retried tool call's_meta["x402/payment"]— both as the spec's raw JSON object (payment) and as a base64 string (paymentBase64), since real servers disagree on which one they want (see below).Design notes:
select/validate/build_typed_data/sign_typed_data/build_payment) is reused untouched;--confirm,--asset/--networkdisambiguation, and all validation guards carry over. The HTTP path is behavior-unchanged (the accepts[] normalization was extracted to a shared_normalize_accepts(), keeping the v1maxAmountRequiredfallback in one place).mcp-signrefuses a challenge without a resource URL before signing (a v2 payload must forward the resource, so signing first would burn a real authorization on a payload the facilitator rejects). Malformed offers (extra: null, stringresource) produce the JSON error contract, not tracebacks. stdin is read only via an explicit--challenge -, so a forgotten flag errors immediately instead of blocking under an agent harness.Also: conformance tests (
scripts/test_x402_pay.py, stdlibunittest, no network and nommcalls) — the core assertion is that the built payload equals the spec's_meta["x402/payment"]object exactly, plus fixtures captured verbatim from a live CloudflarewithX402run. Docs: MCP sections inreferences/x402.mdandworkflows/x402-pay.md, plus SKILL.md routing/validation/confirmation rows.Where this actually gets used
The x402 MCP transport, as documented, is early — real sellers we found gate payment with a plain HTTP 402/header regardless of protocol underneath, and CDP's own public discovery index can't even describe an in-band MCP resource (its schema's
input.typeis hardcoded to"http"). The one substantial real implementation of the spec's actual in-band mechanism is Cloudflare's Agents SDK (agents/x402,withX402/paidTool) — official, documented, and already used in production-shaped builds (Crossmint'scrossmint-agentic-financereference demo wires both sides of it).Reading Cloudflare's real server code turned up two concrete divergences from the spec text, both now handled:
_meta["x402/error"], notstructuredContent(there's nostructuredContentkey at all in Cloudflare's response)._meta["x402/payment"]is read viaJSON.parse(atob(token))— a base64-encoded string, not the inline object the spec shows.Verified
python3 scripts/test_x402_pay.py→ 33/33, including fixtures captured from a live Cloudflare run.x402-mcpexample server locally, unmodified, configured for Base mainnet + the CDP facilitator.mcp-inspect/mcp-signcorrectly parsed the real challenge and produced a payload the server accepted;mm's TEE wallet signed a real EIP-3009 authorization; the retry settled for real — tx0x7f7fe537…bcd6064, $0.01 Base USDC, independently confirmed viaeth_getTransactionReceipt(status 0x1, ERC-20 Transfer + EIP-3009AuthorizationUsedlogs both present).Field notes from building x402 payers with mm (why some choices look the way they do)
maxAmountRequiredwhere v2 usesamount; miss the fallback and you sign a null value._normalize_accepts()handles both in one place for both transports.accepts[0]: real sellers offer multiple rails (e.g. Exa lists a Solana rail alongside Base USDC). The existing--asset/--networkselection carries over to MCP unchanged.A possible follow-up (kept out of scope here, as discussed in #23): the
next_action/handoff convention some sellers use (Coinbase payments-mcp style), which wraps a standard exact-EVM offer — could ship with a redacted capture as a test fixture.cc @basgys — this extends your #16; happy to adjust naming/scope.