Repository navigation
fix(x402): read paidTool payments from MCP SDK v2 handler context - #2494
Merged
Merged
Conversation
🦋 Changeset detectedLatest commit: 010da63 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
✅ agents import sizes: no significant changes ( |
paidTool read the payment token only from the SDK v1 handler context
(extra._meta and extra.requestInfo.headers). SDK v2 passes
{ sessionId, mcpReq, http }, with _meta at ctx.mcpReq._meta and the
request at ctx.http.req, so every paid call on a v2 McpServer returned
PAYMENT_REQUIRED.
The header fallback was also broken on v1: it looked up
"PAYMENT-SIGNATURE" / "X-PAYMENT" with exact case, but every SDK
transport lowercases header names, so only _meta payments worked.
Normalize both context shapes in a small readPaymentToken helper and
match header names case-insensitively. withX402 now also accepts a v2
McpServer in its types; on a v2 server the paidTool callback receives
the v2 ServerContext. withX402 is overloaded with the v1 overload last,
so generic v1 callers and ReturnType<typeof withX402> keep the v1 types.
Fixes #2415
ben-reitz
force-pushed
the
fix/x402-mcp-sdk-v2-payment
branch
from
October 5, 2026 21:15
0d4c5d9 to
010da63
Compare
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2415
Summary
paidToolread the payment from SDK v1 fields only, so on an SDK v2 server it never found one and every call returnedPAYMENT_REQUIRED. The header lookup also used exact-case names. Every SDK transport lowercases header names, so header payments never matched on v1 either.The order is unchanged:
_meta, thenPAYMENT-SIGNATURE, thenX-PAYMENT.Types:
withX402gets two overloads. The v1 overload is last, so v1 callers keep exactly the types they have on main, including generic wrappers andReturnType<typeof withX402>.The v2 types are type-only imports from
@modelcontextprotocol/server, which is already a required peer dependency, so there's no runtime import.Evidence
src/x402-tests/sdk-context.test.tsuses real v1 and v2McpServers and clients over in-memory and HTTP transports; only the facilitator is mocked. The payment cases check that the exact token the client sent reachesverifyPaymentandsettlePayment.11 failed | 4 passed. Every v2 payment case and every v1 header case failed withexpected 'PAYMENT_REQUIRED' to be undefined.After: 17/17 pass, and x402 overall 65/65.
mcpReq._meta, ignoring v2 HTTP headers, droppingX-PAYMENT, or moving headers ahead of_metaeach fail tests.src/tests-d/x402.test-d.tspins the types: v2 callback context, a direct v1 call, a generic v1 wrapper, andReturnType<typeof withX402>. Against a single conditional signature, the last two fail with TS2345/TS7031; with the overloads they compile.src/tests/mcp620/620;pnpm run checkpasses.Merge Danger
Door: two-way
Blast Radius: x402 servers
Only
paidTool's token lookup changes at runtime. Requests that carried a payment but gotPAYMENT_REQUIREDnow go on to verify and settle it, which is the intended behaviour. v1 types are unchanged.Follow-ups, not in this PR:
Access-Control-Allow-Headerslists don't includePAYMENT-SIGNATUREorX-PAYMENT. Browser clients sending payment headers needcorsOptions.headersset. Our ownwithX402Clientuses_meta, so it isn't affected.inputRequired(...): the callback type rejects it, but if a tool bypasses that, payment settles on the first round. Multi-round payment needs its own design.