Skip to content

feat(x402): add the MCP transport to the buyer skill - #25

Open
estmcmxci wants to merge 4 commits into
MetaMask:mainfrom
estmcmxci:feat/x402-mcp-transport
Open

feat(x402): add the MCP transport to the buyer skill#25
estmcmxci wants to merge 4 commits into
MetaMask:mainfrom
estmcmxci:feat/x402-mcp-transport

Conversation

@estmcmxci

Copy link
Copy Markdown

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 same mm-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's PaymentRequired challenge and prints the payment options, read-only. Accepts the whole JSON-RPC response, the tool result, or the bare PaymentRequired object (via --challenge — pass - to read stdin — or --challenge-file); checks structuredContent, content[0].text, and _meta["x402/error"] (see below).
  • mcp-sign --confirm — validates and signs one offered option with mm wallet sign-typed-data and prints the PaymentPayload for 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:

  • The MCP session (and any OAuth on it) stays the caller's — the script signs, the caller delivers. That keeps it transport-honest and dependency-free.
  • The transport-agnostic core from feat: add x402 buyer payment support #16 (select / validate / build_typed_data / sign_typed_data / build_payment) is reused untouched; --confirm, --asset/--network disambiguation, and all validation guards carry over. The HTTP path is behavior-unchanged (the accepts[] normalization was extracted to a shared _normalize_accepts(), keeping the v1 maxAmountRequired fallback in one place).
  • v2 only: the MCP transport is not defined for x402 v1; v1 challenges are refused with a clear error.
  • Fail-closed: mcp-sign refuses 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, string resource) 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, stdlib unittest, no network and no mm calls) — the core assertion is that the built payload equals the spec's _meta["x402/payment"] object exactly, plus fixtures captured verbatim from a live Cloudflare withX402 run. Docs: MCP sections in references/x402.md and workflows/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.type is 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's crossmint-agentic-finance reference demo wires both sides of it).

Reading Cloudflare's real server code turned up two concrete divergences from the spec text, both now handled:

  • The challenge lives in _meta["x402/error"], not structuredContent (there's no structuredContent key at all in Cloudflare's response).
  • The retry's _meta["x402/payment"] is read via JSON.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.
  • Full mainnet round-trip, real money: ran Cloudflare's own official x402-mcp example server locally, unmodified, configured for Base mainnet + the CDP facilitator. mcp-inspect/mcp-sign correctly 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 — tx 0x7f7fe537…bcd6064, $0.01 Base USDC, independently confirmed via eth_getTransactionReceipt (status 0x1, ERC-20 Transfer + EIP-3009 AuthorizationUsed logs both present).
  • The signer core is separately proven on the HTTP transport too: mainnet vs an independent seller (tx) and Base Sepolia (tx).

Field notes from building x402 payers with mm (why some choices look the way they do)

  • v1 wire uses maxAmountRequired where v2 uses amount; miss the fallback and you sign a null value. _normalize_accepts() handles both in one place for both transports.
  • Never blind-take accepts[0]: real sellers offer multiple rails (e.g. Exa lists a Solana rail alongside Base USDC). The existing --asset/--network selection carries over to MCP unchanged.
  • Real MCP-branded x402 sellers we probed (found via CDP's Bazaar index) all gate at the HTTP layer regardless of protocol underneath — worth knowing if you're deciding where to spend interop effort next.

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.

Comment thread skills/metamask-agent-wallet/workflows/x402-pay.md Outdated
Comment thread skills/metamask-agent-wallet/SKILL.md Outdated
Comment thread .gitignore Outdated
Comment thread skills/metamask-agent-wallet/references/x402.md Outdated
Comment thread skills/metamask-agent-wallet/references/x402.md Outdated
Comment thread skills/metamask-agent-wallet/workflows/x402-pay.md Outdated
estmcmxci added a commit to estmcmxci/metamask-agent-skills that referenced this pull request Jul 16, 2026
- 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>
@estmcmxci

Copy link
Copy Markdown
Author

@AyushBherwani1998 revised, ready for follow up.

@AyushBherwani1998 AyushBherwani1998 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the contribution. Can you please resolve the conflicts, bump up SKILLs version by minor version, and update changelog.

@estmcmxci
estmcmxci force-pushed the feat/x402-mcp-transport branch from 6898366 to 1ef35cb Compare August 3, 2026 19:49
estmcmxci added a commit to estmcmxci/metamask-agent-skills that referenced this pull request Aug 3, 2026
- 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>
estmcmxci added a commit to estmcmxci/metamask-agent-skills that referenced this pull request Aug 3, 2026
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>
@estmcmxci

Copy link
Copy Markdown
Author

Thanks @AyushBherwani1998! All three addressed — rebased onto main (now at v5.3.0) and pushed as 1ef35cb.

Conflicts. Three, all in files main had also moved:

  • SKILL.md description — took your updated list (Aave V3 → DeFi earn/yield vaults) and grafted the x402-MCP clause onto it.
  • SKILL.md routing table — kept the four new mm earn rows alongside the MCP row.
  • references/x402.md intro — kept the MCP wording but dropped the bold, since main is de-emphasising bold in that file. I checked the other four de-bolds in x402.md survived the auto-merge.

Version. metamask-agent-wallet 6.3.06.4.0. I left cliVersion at 5.3.0 — this PR adds skill capability rather than covering a new CLI release. Happy to bump it too if you'd rather they move together.

Changelog. CHANGELOG.md landed on main after I branched, so this is a new ## [6.4.0] — targets CLI v5.3.0 entry in the existing Added/Changed style.

Re-verified after the rebase: mcp-inspect on a Cloudflare-style _meta["x402/error"] challenge still parses it, resolves USDC decimals, and reports the option as eligible. Socket checks are green.

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.

estmcmxci and others added 4 commits August 6, 2026 10:03
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>
@estmcmxci
estmcmxci force-pushed the feat/x402-mcp-transport branch from 1ef35cb to c97290f Compare August 6, 2026 14:07
@estmcmxci

Copy link
Copy Markdown
Author

Rebased onto the v6.0.0 main — conflicts resolved, pushed as c97290f.

Re-versioned to match the new baseline: skill 7.0.0 → 7.1.0, cliVersion 6.0.0. My earlier entry had claimed 6.4.0, which you've since used for CLI v5.4.0, so the CHANGELOG entry moved to ## [7.1.0] — targets CLI v6.0.0 above [7.0.0].

Two things picked up from the v6.0.0 pass while resolving:

  • The x402 payments confirmation row and the SKILL description now follow the de-parenthesised phrasing, so "resource (URL or MCP tool)" reads "resource URL or MCP tool".
  • Checked x402_pay.py against the breaking flag renames — it only calls chains list, token assets --asset-ids, wallet address, and sign-typed-data --chain-id, none of which changed, so no code change was needed.

Re-verified after the rebase: mcp-inspect still parses a Cloudflare-style _meta["x402/error"] challenge and resolves it to 0.007 USDC on eip155:8453. Socket checks green.

Ready for another look whenever suits. Happy to rebase again if more of the launch changes land.

@AyushBherwani1998 AyushBherwani1998 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @chaitanyapotti can you PTAL?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the x402 MCP transport to the buyer skill

2 participants