Skip to content

feat: settle MCP payments in the payment-aware fetch#567

Open
parvahuja wants to merge 1 commit into
wevm:mainfrom
parvahuja:parv/payment-aware-fetch
Open

feat: settle MCP payments in the payment-aware fetch#567
parvahuja wants to merge 1 commit into
wevm:mainfrom
parvahuja:parv/payment-aware-fetch

Conversation

@parvahuja

@parvahuja parvahuja commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Compose Transport.http() from protocol handlers for Payment-Auth, x402, and MCP-over-HTTP challenges behind the default payment-aware fetch.
  • Collect all offers from a payment-required response and route the selected retry credential through the protocol that produced its challenge.
  • Keep parsed JSON-RPC handling available through Transport.mcp() while MCP-over-HTTP stays inside the HTTP fetch transport.

Validation

  • Lint passes.
  • Typecheck passes.
  • Focused client transport tests pass locally.
  • Live localnet MCP payment integration passes locally.

@pkg-pr-new

pkg-pr-new Bot commented Jun 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/mppx@567

commit: e8df79b

@parvahuja parvahuja force-pushed the parv/payment-aware-fetch branch from 6423f4a to bd3001d Compare June 18, 2026 06:17
@@ -0,0 +1,14 @@
const mcpChallenge = Symbol.for('mppx.mcp.challenge')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

intentionally modeling this as a "challenge brand" similar to how we treat x402 within normal http transport

Comment thread src/client/Transport.ts Outdated
* this transport only adapts the HTTP body. The body is read only when the *request* is a
* JSON-RPC message, so non-MCP traffic (e.g. a streaming LLM call) is never read.
*/
export function http() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think longer term we need to break out / refactor the different transports, since they are getting overloaded and now are misnomers. But since fetch and others default to the http transport, the most minimal change rn is to just overload this transport even more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What do you envision here?

Technically http is the right transport, but today we do mix multiple protocols (rest, mcp, etc...) inside the code which is a bit gnarly

IMO it may make sense to have a pluggable "adapter" so that you can add new protocols to a transport easily?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah exactly. Like http is the right transport, but then we also have an mcp transport (which is a protocol, not a transport). Agree with the pluggable adapter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This would get rid of the challenge branding stuff too. We would just attempt to dispatch the request/response to all the adapters greedily choose the first one that fits. Should we bundle those changes in this PR?

@parvahuja parvahuja marked this pull request as ready for review June 18, 2026 06:27
@parvahuja parvahuja force-pushed the parv/payment-aware-fetch branch 2 times, most recently from 141429a to e28e494 Compare June 18, 2026 22:42
@parvahuja parvahuja marked this pull request as draft June 18, 2026 22:42
@parvahuja parvahuja force-pushed the parv/payment-aware-fetch branch from e28e494 to 0337a82 Compare June 18, 2026 22:59
Comment thread src/client/internal/adapters/Mcp.ts Outdated
@@ -0,0 +1,79 @@
import * as Mcp from '../../../Mcp.js'
import type { Adapter, Transport } from '../../Transport.js'
import { paymentRequiredStatus } from './Shared.js'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

instead of adapters can we call these protocols?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

e.g. src/client/internal/protocols

we should also comment what these are -- it's not super apparent unless you are familiar with convos on this pr

Comment thread src/client/internal/adapters/Mpp.ts Outdated
*/
export function adapter(): Adapter {
return {
name: 'mpp',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

given that we ar pulling from shard anyway, can we make these names constants?

@parvahuja parvahuja force-pushed the parv/payment-aware-fetch branch 9 times, most recently from 6134ccf to bfcd9c1 Compare June 19, 2026 23:26
Transport.http() now handles MCP-over-HTTP as a third HTTP payment flavor: a JSON-RPC -32042 error in a normal 200 body (often text/event-stream), credential carried in _meta. The -32042/_meta protocol is delegated to the existing Transport.mcp() codec; http() only adapts the HTTP body, and only reads it when the request is a JSON-RPC message. MCP challenges are routed at attach time via a Symbol brand mirroring x402. Transport.mcp() is unchanged and transport stays the single pluggable seam; non-breaking.
@parvahuja parvahuja force-pushed the parv/payment-aware-fetch branch from bfcd9c1 to e8df79b Compare June 19, 2026 23:35
@parvahuja parvahuja marked this pull request as ready for review June 20, 2026 00:30

@brendanjryan brendanjryan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One comment, but otherwise LGTM

: undefined
}

async function parseSseJsonRpcResponse(response: Response): Promise<Mcp.Response | undefined> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does the MCP SDK not have utils for doing this? this feels pretty fragile

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MCP SDK does not afaik... We could add a new dependency like eventsource-parser if we wanted get rid of some of this code wdyt?

...message,
params: {
...message.params,
['_meta']: { ...message.params?.['_meta'], [Mcp.credentialMetaKey]: parsed },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👍

@tempoxyz-bot

tempoxyz-bot commented Jun 20, 2026

Copy link
Copy Markdown

👁️ Cyclops Security Review

e8df79b

🧭 Audit failed · mode=normal · workers 0/2 done (0 left, 2 failed) · verify pending 0

Worker Engine Latest Status Status
pr-567-w1 claude-opus-4-8 🚨 Iteration 3 · Verify Failed
pr-567-w2 gpt-5.5 ⏰ Iteration 3 · Audit Timed out

Findings

# Finding Severity Status
1 MCP payment-aware fetch pays JSON-RPC responses the MCP SDK would ignore Medium Iteration 1 · Verify
2 Fetch-level MCP settlement bypasses McpClient approval hooks Medium Iteration 2 · Verify
3 MCP-over-HTTP body pre-read in isPaymentRequired deadlocks the upstream MCP SDK's streaming SSE consumer (client hang + unbounded memory) Medium Iteration 2 · Verify
4 Over-broad MCP JSON-RPC detection drags non-MCP RPC traffic (viem/Ethereum RPC) into the auto-pay flow via the global fetch polyfill Medium Iteration 3 · Verify
⚙️ Controls
  • 🚀 Keep only 1 remaining iteration per worker after the current work finishes.
  • 👀 Keep only 2 remaining iterations per worker after the current work finishes.
  • ❤️ Let only worker 1 continue; other workers skip queued iterations.
  • 😄 Let only worker 2 continue; other workers skip queued iterations.
  • 🎉 End faster by skipping queued iterations and moving toward consolidation.
  • 😕 Stop active workers/verifiers now and start consolidation immediately.

📜 26 events

🔍 pr-567-w1 iter 1/3 [audit-general.md]
🔍 pr-567-w2 iter 1/3 [audit-ripple.md]
🚨 pr-567-w2 iter 1 — finding
🚨 Finding: MCP payment-aware fetch pays JSON-RPC responses the MCP SDK would ignore (Medium)
🔍 pr-567-w2 iter 2/3 [audit-ripple.md]
🔬 Verifying: MCP payment-aware fetch pays JSON-RPC responses the MCP SDK would ignore
📋 Verify: MCP payment-aware fetch pays JSON-RPC responses the MCP SDK would ignore → ✅ Verified
🚨 pr-567-w2 iter 2 — finding
🚨 Finding: Fetch-level MCP settlement bypasses McpClient approval hooks (Medium)
🔍 pr-567-w2 iter 3/3 [audit-invariants.md]
🔬 Verifying: Fetch-level MCP settlement bypasses McpClient approval hooks
📋 Verify: Fetch-level MCP settlement bypasses McpClient approval hooks → ✅ Verified
pr-567-w1 iter 1 — timeout
🔍 pr-567-w1 iter 2/3 [audit-ripple.md]
🚨 pr-567-w1 iter 2 — finding
🚨 Finding: MCP-over-HTTP body pre-read in isPaymentRequired deadlocks the upstream MCP SDK's streaming SSE consumer (client hang + unbounded memory) (Medium)
🔍 pr-567-w1 iter 3/3 [audit-ripple.md]
🔬 Verifying: MCP-over-HTTP body pre-read in isPaymentRequired deadlocks the upstream MCP SDK's streaming SSE consumer (client hang + unbounded memory)
📋 Verify: MCP-over-HTTP body pre-read in isPaymentRequired deadlocks the upstream MCP SDK's streaming SSE consumer (client hang + unbounded memory) → ✅ Verified
🚨 pr-567-w1 iter 3 — finding
🚨 Finding: Over-broad MCP JSON-RPC detection drags non-MCP RPC traffic (viem/Ethereum RPC) into the auto-pay flow via the global fetch polyfill (Medium)
🔬 Verifying: Over-broad MCP JSON-RPC detection drags non-MCP RPC traffic (viem/Ethereum RPC) into the auto-pay flow via the global fetch polyfill
📋 Verify: Over-broad MCP JSON-RPC detection drags non-MCP RPC traffic (viem/Ethereum RPC) into the auto-pay flow via the global fetch polyfill → ✅ Verified
pr-567-w1 failed — one or more audit iterations failed
pr-567-w2 iter 3 — timeout
pr-567-w2 failed — one or more audit iterations failed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants