Reproduction
Using released @apify/mcpc 0.5.1 against an x402 MCP server:
- Connect a funded Base wallet with
--x402 exact.
- Call a payment-required MCP tool.
- The server returns a valid payment-required tool result.
- mcpc signs the requested payment and retries.
- The retry receives the same payment-required result. No settlement occurs.
The failure reproduces against https://api.utilia.ink/base/mcp with the solana_priority_fees tool at $0.002 USDC. The wallet balance remains unchanged, so this is a safe pre-settlement failure.
Root cause
BridgeProcess.handlePaymentRequiredRetry() caches paymentSignatureBase64 and invokes the original SDK tool call again. The fetch middleware only injects params._meta["x402/payment"] when RequestInit.body is a string. The MCP SDK transport supplies a non-string body, so the cached payment is not inserted into the MCP request metadata. The HTTP PAYMENT-SIGNATURE header alone is not sufficient for servers following the x402 MCP transport contract.
Expected behavior
Decode the signed payload and attach it to params._meta["x402/payment"] on the single retry. Preserve existing caller metadata and apply the same behavior to regular, task, and detached tool calls. The payment must be attempted at most once per tool invocation.
Reproduction
Using released
@apify/mcpc0.5.1 against an x402 MCP server:--x402 exact.The failure reproduces against
https://api.utilia.ink/base/mcpwith thesolana_priority_feestool at $0.002 USDC. The wallet balance remains unchanged, so this is a safe pre-settlement failure.Root cause
BridgeProcess.handlePaymentRequiredRetry()cachespaymentSignatureBase64and invokes the original SDK tool call again. The fetch middleware only injectsparams._meta["x402/payment"]whenRequestInit.bodyis a string. The MCP SDK transport supplies a non-string body, so the cached payment is not inserted into the MCP request metadata. The HTTPPAYMENT-SIGNATUREheader alone is not sufficient for servers following the x402 MCP transport contract.Expected behavior
Decode the signed payload and attach it to
params._meta["x402/payment"]on the single retry. Preserve existing caller metadata and apply the same behavior to regular, task, and detached tool calls. The payment must be attempted at most once per tool invocation.