Skip to content

feat(mcp): handle McpError(-32042) for MCP payment challenges#1728

Open
tenequm wants to merge 4 commits intocoinbase:mainfrom
cascade-protocol:feat/mcp-handle-32042-payment-error
Open

feat(mcp): handle McpError(-32042) for MCP payment challenges#1728
tenequm wants to merge 4 commits intocoinbase:mainfrom
cascade-protocol:feat/mcp-handle-32042-payment-error

Conversation

@tenequm
Copy link
Copy Markdown
Contributor

@tenequm tenequm commented Mar 20, 2026

Problem

The MCP TypeScript SDK's McpServer catch block discards error.data for all McpError codes except -32042 (UrlElicitationRequired from SEP-1036). This is tracked in modelcontextprotocol/typescript-sdk#774.

MCP servers that use McpError(-32042) for payment challenges (which SEP-1036 explicitly covers as a use case) can deliver PaymentRequired data intact through the SDK. However, x402MCPClient only parses payment info from isError tool results - it doesn't catch thrown -32042 exceptions, so these payment challenges are silently lost.

Solution

Teach x402MCPClient to handle both delivery mechanisms:

  1. Existing: isError tool results with PaymentRequired in structuredContent or content[0].text (backwards-compatible with all current x402 MCP servers)
  2. New: Thrown McpError(-32042) with PaymentRequired in error.data (directly or namespaced under error.data.x402)

This is additive - no breaking changes. Existing servers using isError results continue to work unchanged.

Changes

src/types/mcp.ts

  • Add JSONRPC_PAYMENT_REQUIRED_CODE (-32042) constant with SEP-1036 / SDK#774 context
  • Update isPaymentRequiredError() type guard to accept both 402 and -32042 error codes, including the error.data.x402 namespaced format

src/client/x402MCPClient.ts

  • Wrap callTool() and getToolPaymentRequirements() in try/catch to intercept thrown -32042 errors
  • Add extractPaymentRequiredFromError() private method that uses the isPaymentRequiredError type guard, then extracts PaymentRequired from the correct location

test/unit/client.test.ts

  • 17 new test cases covering: direct -32042 data, namespaced error.data.x402, non-payment error re-throwing, retry with payment metadata, approval flow, hooks, and getToolPaymentRequirements()

Why -32042?

SEP-1036 defines -32042 for flows where "the server needs the client to provide something before proceeding" and explicitly includes payment as a use case. It is the only custom error code the MCP TypeScript SDK propagates with error.data intact through McpServer's tool handler. Using it for payment challenges is the standard-compliant path forward until SDK#774 is resolved.

Testing

  • All 87 unit tests pass (70 existing + 17 new)
  • TypeScript, ESLint, Prettier all clean
  • MCP client and server examples type-check (server advanced.ts has pre-existing unrelated errors on main)

tenequm added 3 commits March 20, 2026 21:28
The MCP TypeScript SDK's McpServer catch block discards error.data for
all McpError codes except -32042 (UrlElicitationRequired from SEP-1036).
This is the only error code that survives the McpServer round-trip with
data intact, as tracked in:
modelcontextprotocol/typescript-sdk#774

MCP servers using McpError(-32042) for payment challenges (per SEP-1036
which explicitly covers payment flows) need x402MCPClient to catch these
thrown errors in addition to parsing isError tool results.

Changes:
- Add JSONRPC_PAYMENT_REQUIRED_CODE (-32042) constant
- Update isPaymentRequiredError() to handle both 402 and -32042 codes
- Add extractPaymentRequiredFromError() to x402MCPClient for extracting
  PaymentRequired from thrown McpError exceptions
- Wrap callTool() and getToolPaymentRequirements() with try/catch to
  handle thrown -32042 errors alongside existing isError result parsing
- Support both direct error.data and namespaced error.data.x402 formats
- Add 17 new test cases covering all -32042 paths
Eliminate duplicated validation logic in extractPaymentRequiredFromError
by using the isPaymentRequiredError() type guard as the gate, then
extracting PaymentRequired from the validated error shape. Removes the
JSONRPC_PAYMENT_REQUIRED_CODE import from the client module since the
code check is now delegated to the type guard.
@cb-heimdall
Copy link
Copy Markdown

cb-heimdall commented Mar 20, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 20, 2026

@tenequm is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

Bortlesboat added a commit to Bortlesboat/x402 that referenced this pull request Mar 30, 2026
Port TypeScript PR coinbase#1728 to Python SDK. When an MCP server throws
-32042 (SEP-1036 UrlElicitationRequired) with payment requirements
in error.data, the client now catches and processes payment
automatically.

Adds JSONRPC_PAYMENT_REQUIRED_CODE constant, updates error extraction
utilities, and wraps both async and sync client call_tool methods.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants