mppx 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +20 -11
  3. package/dist/Challenge.d.ts.map +1 -1
  4. package/dist/Challenge.js +18 -6
  5. package/dist/Challenge.js.map +1 -1
  6. package/dist/Mcp.d.ts +3 -0
  7. package/dist/Mcp.d.ts.map +1 -1
  8. package/dist/Mcp.js +2 -0
  9. package/dist/Mcp.js.map +1 -1
  10. package/dist/PaymentRequest.d.ts +10 -10
  11. package/dist/PaymentRequest.js +8 -8
  12. package/dist/cli/internal.d.ts +1 -0
  13. package/dist/cli/internal.d.ts.map +1 -1
  14. package/dist/cli/internal.js +1 -15
  15. package/dist/cli/internal.js.map +1 -1
  16. package/dist/client/Mppx.js +2 -2
  17. package/dist/client/Mppx.js.map +1 -1
  18. package/dist/client/Transport.d.ts +11 -16
  19. package/dist/client/Transport.d.ts.map +1 -1
  20. package/dist/client/Transport.js +55 -75
  21. package/dist/client/Transport.js.map +1 -1
  22. package/dist/client/index.d.ts +3 -0
  23. package/dist/client/index.d.ts.map +1 -1
  24. package/dist/client/index.js +1 -0
  25. package/dist/client/index.js.map +1 -1
  26. package/dist/client/internal/Fetch.d.ts.map +1 -1
  27. package/dist/client/internal/Fetch.js +46 -7
  28. package/dist/client/internal/Fetch.js.map +1 -1
  29. package/dist/client/internal/protocols/Mcp.d.ts +7 -0
  30. package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
  31. package/dist/client/internal/protocols/Mcp.js +159 -0
  32. package/dist/client/internal/protocols/Mcp.js.map +1 -0
  33. package/dist/client/internal/protocols/Mpp.d.ts +4 -0
  34. package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
  35. package/dist/client/internal/protocols/Mpp.js +18 -0
  36. package/dist/client/internal/protocols/Mpp.js.map +1 -0
  37. package/dist/client/internal/protocols/Protocol.d.ts +10 -0
  38. package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
  39. package/dist/client/internal/protocols/Protocol.js +2 -0
  40. package/dist/client/internal/protocols/Protocol.js.map +1 -0
  41. package/dist/client/internal/protocols/Shared.d.ts +5 -0
  42. package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
  43. package/dist/client/internal/protocols/Shared.js +20 -0
  44. package/dist/client/internal/protocols/Shared.js.map +1 -0
  45. package/dist/client/internal/protocols/X402.d.ts +8 -0
  46. package/dist/client/internal/protocols/X402.d.ts.map +1 -0
  47. package/dist/client/internal/protocols/X402.js +39 -0
  48. package/dist/client/internal/protocols/X402.js.map +1 -0
  49. package/dist/evm/client/index.d.ts +1 -0
  50. package/dist/evm/client/index.d.ts.map +1 -1
  51. package/dist/evm/client/index.js +1 -0
  52. package/dist/evm/client/index.js.map +1 -1
  53. package/dist/evm/index.d.ts +2 -0
  54. package/dist/evm/index.d.ts.map +1 -1
  55. package/dist/evm/index.js +2 -0
  56. package/dist/evm/index.js.map +1 -1
  57. package/dist/evm/server/index.d.ts +1 -0
  58. package/dist/evm/server/index.d.ts.map +1 -1
  59. package/dist/evm/server/index.js +1 -0
  60. package/dist/evm/server/index.js.map +1 -1
  61. package/dist/mcp/client/McpClient.d.ts +101 -0
  62. package/dist/mcp/client/McpClient.d.ts.map +1 -0
  63. package/dist/mcp/client/McpClient.js +162 -0
  64. package/dist/mcp/client/McpClient.js.map +1 -0
  65. package/dist/mcp/client/index.d.ts.map +1 -0
  66. package/dist/mcp/client/index.js.map +1 -0
  67. package/dist/mcp/server/Transport.d.ts.map +1 -0
  68. package/dist/mcp/server/Transport.js.map +1 -0
  69. package/dist/mcp/server/index.d.ts.map +1 -0
  70. package/dist/mcp/server/index.js.map +1 -0
  71. package/dist/server/Mppx.d.ts +1 -1
  72. package/dist/server/Mppx.d.ts.map +1 -1
  73. package/dist/server/Mppx.js +9 -0
  74. package/dist/server/Mppx.js.map +1 -1
  75. package/dist/server/Transport.d.ts +1 -1
  76. package/dist/server/Transport.d.ts.map +1 -1
  77. package/dist/server/Transport.js +1 -1
  78. package/dist/server/Transport.js.map +1 -1
  79. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  80. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  81. package/dist/stripe/server/internal/html.gen.js +1 -1
  82. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  83. package/dist/tempo/Proof.d.ts +85 -1
  84. package/dist/tempo/Proof.d.ts.map +1 -1
  85. package/dist/tempo/Proof.js +35 -0
  86. package/dist/tempo/Proof.js.map +1 -1
  87. package/dist/tempo/client/Charge.d.ts +13 -1
  88. package/dist/tempo/client/Charge.d.ts.map +1 -1
  89. package/dist/tempo/client/Charge.js +38 -25
  90. package/dist/tempo/client/Charge.js.map +1 -1
  91. package/dist/tempo/client/Methods.d.ts +5 -3
  92. package/dist/tempo/client/Methods.d.ts.map +1 -1
  93. package/dist/tempo/client/Methods.js +4 -2
  94. package/dist/tempo/client/Methods.js.map +1 -1
  95. package/dist/tempo/client/ResolveAccount.d.ts +40 -0
  96. package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
  97. package/dist/tempo/client/ResolveAccount.js +2 -0
  98. package/dist/tempo/client/ResolveAccount.js.map +1 -0
  99. package/dist/tempo/internal/fee-payer.d.ts +26 -1
  100. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  101. package/dist/tempo/internal/fee-payer.js +83 -30
  102. package/dist/tempo/internal/fee-payer.js.map +1 -1
  103. package/dist/tempo/internal/proof.d.ts +71 -5
  104. package/dist/tempo/internal/proof.d.ts.map +1 -1
  105. package/dist/tempo/internal/proof.js +42 -6
  106. package/dist/tempo/internal/proof.js.map +1 -1
  107. package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
  108. package/dist/tempo/legacy/client/SessionManager.js +10 -3
  109. package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
  110. package/dist/tempo/server/Charge.d.ts.map +1 -1
  111. package/dist/tempo/server/Charge.js +46 -18
  112. package/dist/tempo/server/Charge.js.map +1 -1
  113. package/dist/tempo/server/Methods.d.ts +4 -2
  114. package/dist/tempo/server/Methods.d.ts.map +1 -1
  115. package/dist/tempo/server/Methods.js +4 -2
  116. package/dist/tempo/server/Methods.js.map +1 -1
  117. package/dist/tempo/server/Subscription.d.ts +10 -0
  118. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  119. package/dist/tempo/server/Subscription.js +135 -23
  120. package/dist/tempo/server/Subscription.js.map +1 -1
  121. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  122. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  123. package/dist/tempo/server/internal/html.gen.js +1 -1
  124. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  125. package/dist/tempo/session/client/ChannelOps.d.ts +2 -3
  126. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  127. package/dist/tempo/session/client/ChannelOps.js +7 -10
  128. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  129. package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
  130. package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
  131. package/dist/tempo/session/client/ChannelStore.js +63 -0
  132. package/dist/tempo/session/client/ChannelStore.js.map +1 -0
  133. package/dist/tempo/session/client/CredentialState.d.ts +7 -24
  134. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  135. package/dist/tempo/session/client/CredentialState.js +51 -49
  136. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  137. package/dist/tempo/session/client/Session.d.ts +8 -2
  138. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  139. package/dist/tempo/session/client/Session.js +22 -8
  140. package/dist/tempo/session/client/Session.js.map +1 -1
  141. package/dist/tempo/session/client/SessionManager.d.ts +4 -40
  142. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  143. package/dist/tempo/session/client/SessionManager.js +124 -174
  144. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  145. package/dist/tempo/session/client/index.d.ts +3 -4
  146. package/dist/tempo/session/client/index.d.ts.map +1 -1
  147. package/dist/tempo/session/client/index.js +1 -0
  148. package/dist/tempo/session/client/index.js.map +1 -1
  149. package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
  150. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
  151. package/dist/tempo/session/precompile/Voucher.js +24 -25
  152. package/dist/tempo/session/precompile/Voucher.js.map +1 -1
  153. package/dist/tempo/session/server/CredentialVerification.d.ts +6 -0
  154. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  155. package/dist/tempo/session/server/CredentialVerification.js +13 -0
  156. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  157. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  158. package/dist/tempo/session/server/Settlement.js +4 -2
  159. package/dist/tempo/session/server/Settlement.js.map +1 -1
  160. package/dist/tempo/session/server/Sse.d.ts.map +1 -1
  161. package/dist/tempo/session/server/Sse.js.map +1 -1
  162. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  163. package/dist/tempo/session/server/Ws.js.map +1 -1
  164. package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
  165. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  166. package/dist/tempo/subscription/Store.d.ts +2 -0
  167. package/dist/tempo/subscription/Store.d.ts.map +1 -1
  168. package/dist/tempo/subscription/Store.js +16 -1
  169. package/dist/tempo/subscription/Store.js.map +1 -1
  170. package/dist/x402/index.d.ts +1 -0
  171. package/dist/x402/index.d.ts.map +1 -1
  172. package/dist/x402/index.js +1 -0
  173. package/dist/x402/index.js.map +1 -1
  174. package/package.json +21 -10
  175. package/src/Challenge.test.ts +40 -0
  176. package/src/Challenge.ts +19 -6
  177. package/src/Mcp.ts +4 -0
  178. package/src/PaymentRequest.ts +10 -10
  179. package/src/cli/cli.test.ts +15 -15
  180. package/src/cli/config.test.ts +13 -7
  181. package/src/cli/internal.ts +1 -16
  182. package/src/client/Mppx.test-d.ts +21 -1
  183. package/src/client/Mppx.test.ts +1 -1
  184. package/src/client/Mppx.ts +2 -2
  185. package/src/client/Transport.test.ts +225 -178
  186. package/src/client/Transport.ts +77 -83
  187. package/src/client/index.ts +14 -0
  188. package/src/client/internal/Fetch.test.ts +207 -2
  189. package/src/client/internal/Fetch.ts +52 -6
  190. package/src/client/internal/protocols/Mcp.test.ts +220 -0
  191. package/src/client/internal/protocols/Mcp.ts +162 -0
  192. package/src/client/internal/protocols/Mpp.ts +21 -0
  193. package/src/client/internal/protocols/Protocol.ts +10 -0
  194. package/src/client/internal/protocols/Shared.ts +25 -0
  195. package/src/client/internal/protocols/X402.ts +42 -0
  196. package/src/discovery/OpenApi.test.ts +1 -1
  197. package/src/evm/PublicInterface.test-d.ts +1 -1
  198. package/src/evm/client/index.ts +1 -0
  199. package/src/evm/index.ts +2 -0
  200. package/src/evm/server/Charge.test.ts +1 -1
  201. package/src/evm/server/index.ts +1 -0
  202. package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
  203. package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
  204. package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
  205. package/src/mcp/client/McpClient.ts +307 -0
  206. package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
  207. package/src/middlewares/elysia.test.ts +1 -1
  208. package/src/middlewares/express.test.ts +1 -1
  209. package/src/middlewares/hono.test.ts +1 -1
  210. package/src/middlewares/internal/mppx.test.ts +1 -1
  211. package/src/middlewares/nextjs.test.ts +1 -1
  212. package/src/proxy/Proxy.test.ts +1 -1
  213. package/src/proxy/services/anthropic.test.ts +1 -1
  214. package/src/proxy/services/openai.test.ts +1 -1
  215. package/src/proxy/services/stripe.test.ts +1 -1
  216. package/src/server/Mppx.authorize.test.ts +1 -1
  217. package/src/server/Mppx.test-d.ts +1 -1
  218. package/src/server/Mppx.test.ts +20 -2
  219. package/src/server/Mppx.ts +14 -1
  220. package/src/server/Transport.test.ts +6 -6
  221. package/src/server/Transport.ts +1 -1
  222. package/src/stripe/Charge.integration.test.ts +1 -1
  223. package/src/stripe/client/Charge.test.ts +1 -1
  224. package/src/stripe/server/Charge.test.ts +1 -1
  225. package/src/stripe/server/internal/html/package.json +1 -1
  226. package/src/stripe/server/internal/html.gen.ts +1 -1
  227. package/src/tempo/Proof.conformance.test.ts +146 -0
  228. package/src/tempo/Proof.test-d.ts +15 -0
  229. package/src/tempo/Proof.ts +52 -1
  230. package/src/tempo/Subscription.integration.test.ts +1 -1
  231. package/src/tempo/client/Charge.test.ts +173 -0
  232. package/src/tempo/client/Charge.ts +65 -36
  233. package/src/tempo/client/Methods.ts +4 -2
  234. package/src/tempo/client/ResolveAccount.ts +46 -0
  235. package/src/tempo/internal/fee-payer.test.ts +89 -10
  236. package/src/tempo/internal/fee-payer.ts +128 -41
  237. package/src/tempo/internal/proof.test.ts +12 -4
  238. package/src/tempo/internal/proof.ts +55 -6
  239. package/src/tempo/legacy/client/SessionManager.ts +11 -3
  240. package/src/tempo/legacy/server/Session.test.ts +91 -26
  241. package/src/tempo/server/Charge.test.ts +388 -17
  242. package/src/tempo/server/Charge.ts +50 -24
  243. package/src/tempo/server/Methods.ts +4 -2
  244. package/src/tempo/server/Subscription.test.ts +465 -3
  245. package/src/tempo/server/Subscription.ts +174 -19
  246. package/src/tempo/server/internal/html/package.json +2 -2
  247. package/src/tempo/server/internal/html.gen.ts +1 -1
  248. package/src/tempo/session/client/ChannelOps.ts +5 -19
  249. package/src/tempo/session/client/ChannelStore.ts +111 -0
  250. package/src/tempo/session/client/CredentialState.test.ts +206 -62
  251. package/src/tempo/session/client/CredentialState.ts +58 -73
  252. package/src/tempo/session/client/Session.test.ts +41 -1
  253. package/src/tempo/session/client/Session.ts +36 -10
  254. package/src/tempo/session/client/SessionManager.test.ts +154 -65
  255. package/src/tempo/session/client/SessionManager.ts +141 -235
  256. package/src/tempo/session/client/index.ts +8 -5
  257. package/src/tempo/session/precompile/Voucher.test.ts +45 -7
  258. package/src/tempo/session/precompile/Voucher.ts +27 -25
  259. package/src/tempo/session/server/CredentialVerification.test.ts +36 -0
  260. package/src/tempo/session/server/CredentialVerification.ts +18 -0
  261. package/src/tempo/session/server/Session.test.ts +4 -4
  262. package/src/tempo/session/server/Settlement.test.ts +88 -1
  263. package/src/tempo/session/server/Settlement.ts +2 -1
  264. package/src/tempo/session/server/Sse.ts +0 -2
  265. package/src/tempo/session/server/Ws.ts +0 -4
  266. package/src/tempo/subscription/Store.ts +27 -9
  267. package/src/x402/Exact.e2e.test.ts +1 -1
  268. package/src/x402/PublicInterface.test-d.ts +1 -1
  269. package/src/x402/index.ts +1 -0
  270. package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
  271. package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
  272. package/dist/mcp-sdk/client/McpClient.js +0 -118
  273. package/dist/mcp-sdk/client/McpClient.js.map +0 -1
  274. package/dist/mcp-sdk/client/index.d.ts.map +0 -1
  275. package/dist/mcp-sdk/client/index.js.map +0 -1
  276. package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
  277. package/dist/mcp-sdk/server/Transport.js.map +0 -1
  278. package/dist/mcp-sdk/server/index.d.ts.map +0 -1
  279. package/dist/mcp-sdk/server/index.js.map +0 -1
  280. package/src/mcp-sdk/client/McpClient.ts +0 -228
  281. /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
  282. /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
  283. /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
  284. /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
  285. /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
  286. /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
  287. /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
  288. /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
  289. /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
  290. /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
@@ -1,18 +1,11 @@
1
1
  import * as Challenge from '../Challenge.js'
2
- import * as Constants from '../Constants.js'
3
2
  import * as Credential from '../Credential.js'
4
3
  import * as Mcp from '../Mcp.js'
5
- import * as x402_Header from '../x402/Header.js'
6
- import * as x402_ChallengeBrand from '../x402/internal/ChallengeBrand.js'
7
- import * as x402_Types from '../x402/Types.js'
8
-
9
- const paymentRequiredStatus = 402
10
- const credentialHeaders = [
11
- Constants.Headers.authorization,
12
- x402_Types.paymentRequiredHeader,
13
- x402_Types.paymentResponseHeader,
14
- x402_Types.paymentSignatureHeader,
15
- ]
4
+ import { mcp as mcpProtocol } from './internal/protocols/Mcp.js'
5
+ import { mpp as mppProtocol } from './internal/protocols/Mpp.js'
6
+ import type { Protocol } from './internal/protocols/Protocol.js'
7
+ import { paymentRequiredStatus } from './internal/protocols/Shared.js'
8
+ import { x402 as x402Protocol } from './internal/protocols/X402.js'
16
9
 
17
10
  /**
18
11
  * Client-side transport adapter.
@@ -23,12 +16,21 @@ const credentialHeaders = [
23
16
  export type Transport<in out request = unknown, in out response = unknown> = {
24
17
  /** Transport name for identification. */
25
18
  name: string
26
- /** Checks if a response indicates payment is required. */
27
- isPaymentRequired: (response: response) => boolean
19
+ /**
20
+ * Checks if a response indicates payment is required. May inspect the request (to gate
21
+ * body reads) and be async (to read a response body).
22
+ */
23
+ isPaymentRequired: (response: response, request?: request) => boolean | Promise<boolean>
28
24
  /** Extracts all challenges from a payment-required response, when the transport supports multiple offers. */
29
- getChallenges?: (response: response) => Challenge.Challenge[]
25
+ getChallenges?: (
26
+ response: response,
27
+ request?: request,
28
+ ) => Challenge.Challenge[] | Promise<Challenge.Challenge[]>
30
29
  /** Extracts the challenge from a payment-required response. */
31
- getChallenge: (response: response) => Challenge.Challenge
30
+ getChallenge: (
31
+ response: response,
32
+ request?: request,
33
+ ) => Challenge.Challenge | Promise<Challenge.Challenge>
32
34
  /** Attaches a credential to a request. */
33
35
  setCredential: (
34
36
  request: request,
@@ -74,87 +76,79 @@ export function from<request, response>(
74
76
  return transport
75
77
  }
76
78
 
77
- /**
78
- * HTTP transport for client-side payment handling.
79
- *
80
- * - Detects payment required via 402 status
81
- * - Extracts Payment auth challenges from `WWW-Authenticate`
82
- * - Falls back to x402 exact challenges from `PAYMENT-REQUIRED`
83
- * - Sends credentials via `Authorization` or `PAYMENT-SIGNATURE`
84
- */
85
- export function http() {
79
+ /** HTTP transport that composes payment protocols while keeping `fetch` as the single boundary. */
80
+ export function http(): Transport<RequestInit, Response> {
81
+ const protocols: readonly Protocol[] = [mppProtocol(), x402Protocol(), mcpProtocol()]
82
+ const protocolForChallenge = new WeakMap<Challenge.Challenge, Protocol>()
83
+
84
+ const remember = (protocol: Protocol, challenges: Challenge.Challenge[]) => {
85
+ for (const challenge of challenges) protocolForChallenge.set(challenge, protocol)
86
+ return challenges
87
+ }
88
+
89
+ // Collect every protocol offer. Header-only 402 paths stay synchronous; MCP returns a promise
90
+ // only when it has to inspect a JSON-RPC/SSE body.
91
+ const collect = (
92
+ response: Response,
93
+ request?: RequestInit,
94
+ ): Challenge.Challenge[] | Promise<Challenge.Challenge[]> => {
95
+ const collectFrom = (
96
+ index: number,
97
+ collected: Challenge.Challenge[],
98
+ ): Challenge.Challenge[] | Promise<Challenge.Challenge[]> => {
99
+ for (let i = index; i < protocols.length; i++) {
100
+ const protocol = protocols[i]!
101
+ const challenges = protocol.getChallenges(response, request)
102
+ if (challenges instanceof Promise)
103
+ return challenges.then((list) =>
104
+ collectFrom(i + 1, [...collected, ...remember(protocol, list)]),
105
+ )
106
+ collected.push(...remember(protocol, challenges))
107
+ }
108
+ return collected
109
+ }
110
+ return collectFrom(0, [])
111
+ }
112
+
86
113
  return from<RequestInit, Response>({
87
114
  name: 'http',
88
115
 
89
- isPaymentRequired(response) {
90
- return response.status === paymentRequiredStatus
116
+ isPaymentRequired(response, request) {
117
+ if (response.status === paymentRequiredStatus) return true // HTTP 402 — sync fast path
118
+ const challenges = collect(response, request)
119
+ return challenges instanceof Promise
120
+ ? challenges.then((list) => list.length > 0)
121
+ : challenges.length > 0
91
122
  },
92
123
 
93
- getChallenges(response) {
94
- return paymentRequiredChallenges(response)
124
+ getChallenges(response, request) {
125
+ return collect(response, request)
95
126
  },
96
127
 
97
- getChallenge(response) {
98
- const challenge = paymentRequiredChallenges(response)[0]
99
- if (!challenge) throw new Error('No challenge in response.')
100
- return challenge
128
+ getChallenge(response, request) {
129
+ const pick = (challenges: Challenge.Challenge[]): Challenge.Challenge => {
130
+ const challenge = challenges[0]
131
+ if (!challenge) throw new Error('No challenge in response.')
132
+ return challenge
133
+ }
134
+ const challenges = collect(response, request)
135
+ return challenges instanceof Promise ? challenges.then(pick) : pick(challenges)
101
136
  },
102
137
 
103
138
  setCredential(request, credential, options) {
104
- const headers = new Headers(request.headers)
105
- for (const header of credentialHeaders) headers.delete(header)
106
- if (isX402Challenge(options?.challenge)) {
107
- headers.set(x402_Types.paymentSignatureHeader, credential)
108
- } else {
109
- headers.set(Constants.Headers.authorization, credential)
110
- }
111
- return { ...request, headers }
139
+ const protocol = options?.challenge ? protocolForChallenge.get(options.challenge) : undefined
140
+ const fallback = protocols[0]
141
+ if (!protocol && !fallback) throw new Error('No protocol to attach the credential.')
142
+ return (protocol ?? fallback)!.setCredential(request, credential)
112
143
  },
113
144
  })
114
145
  }
115
146
 
116
- function paymentRequiredChallenges(response: Response): Challenge.Challenge[] {
117
- return [
118
- ...(response.headers.has(Constants.Headers.wwwAuthenticate)
119
- ? Challenge.fromResponseList(response)
120
- : []),
121
- ...x402Challenges(response),
122
- ]
123
- }
124
-
125
- function x402Challenges(response: Response): Challenge.Challenge[] {
126
- const header = response.headers.get(x402_Types.paymentRequiredHeader)
127
- if (!header) return []
128
- const paymentRequired = x402_Header.decodePaymentRequired(header)
129
- if (response.url && paymentRequired.resource.url !== response.url)
130
- throw new Error('x402 payment-required resource does not match response URL.')
131
- return paymentRequired.accepts.map((accepted, index) =>
132
- x402_ChallengeBrand.mark(
133
- Challenge.from({
134
- id: `${x402_Types.syntheticChallengeIdPrefix}${index}`,
135
- intent: x402_Types.exactIntent,
136
- method: x402_Types.paymentMethod,
137
- realm: new URL(paymentRequired.resource.url).host,
138
- request: {
139
- ...accepted,
140
- ...(paymentRequired.extensions ? { extensions: paymentRequired.extensions } : {}),
141
- resource: paymentRequired.resource,
142
- },
143
- }),
144
- ),
145
- )
146
- }
147
-
148
- function isX402Challenge(challenge: Challenge.Challenge | undefined): boolean {
149
- return x402_ChallengeBrand.is(challenge)
150
- }
151
-
152
147
  /**
153
- * MCP transport for client-side payment handling.
148
+ * MCP protocol transport for direct JSON-RPC objects.
154
149
  *
155
- * - Detects payment required via error code -32042
156
- * - Extracts challenges from `error.data.challenges[0]`
157
- * - Sends credentials via `_meta["org.paymentauth/credential"]`
150
+ * Prefer {@link http} for MCP-over-HTTP fetches; this remains for callers that already operate on
151
+ * parsed MCP request/response objects.
158
152
  */
159
153
  export function mcp() {
160
154
  return from<Mcp.Request, Mcp.Response>({
@@ -184,8 +178,8 @@ export function mcp() {
184
178
  ...request,
185
179
  params: {
186
180
  ...request.params,
187
- _meta: {
188
- ...request.params?._meta,
181
+ ['_meta']: {
182
+ ...request.params?.['_meta'],
189
183
  [Mcp.credentialMetaKey]: parsed,
190
184
  },
191
185
  },
@@ -11,5 +11,19 @@ export {
11
11
  stripe,
12
12
  tempo,
13
13
  } from './Methods.js'
14
+ export {
15
+ createChannelStore,
16
+ createJsonChannelStore,
17
+ entryKey,
18
+ type ChannelStore,
19
+ type JsonChannelKv,
20
+ } from '../tempo/session/client/ChannelStore.js'
21
+ export type { ChargeContext } from '../tempo/client/Charge.js'
22
+ export type {
23
+ ResolveAccount,
24
+ ResolveAccountCall,
25
+ ResolveAccountInfo,
26
+ ResolveAccountOperation,
27
+ } from '../tempo/client/ResolveAccount.js'
14
28
  export * as Mppx from './Mppx.js'
15
29
  export * as Transport from './Transport.js'
@@ -1,6 +1,7 @@
1
- import { Challenge, Errors, Receipt } from 'mppx'
1
+ import { Challenge, Credential, Errors, Mcp, Receipt } from 'mppx'
2
2
  import { tempo } from 'mppx/client'
3
3
  import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
4
+ import { Header as x402_Header, Types as x402_Types, type PaymentRequired } from 'mppx/x402'
4
5
  import { createClient, defineChain } from 'viem'
5
6
  import { describe, expect, test, vi } from 'vp/test'
6
7
  import * as Http from '~test/Http.js'
@@ -10,7 +11,7 @@ import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
10
11
  import * as Fetch from './Fetch.js'
11
12
 
12
13
  const realm = 'api.example.com'
13
- const secretKey = 'test-secret-key'
14
+ const secretKey = 'test-secret-key-test-secret-key-32'
14
15
 
15
16
  const server = Mppx_server.create({
16
17
  methods: [
@@ -334,6 +335,21 @@ const noopMethod = {
334
335
  createCredential: async () => 'credential',
335
336
  } as any
336
337
 
338
+ const x402PaymentRequired = {
339
+ accepts: [
340
+ {
341
+ amount: '10000',
342
+ asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
343
+ maxTimeoutSeconds: 60,
344
+ network: 'eip155:84532',
345
+ payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
346
+ scheme: x402_Types.schemes[0],
347
+ },
348
+ ],
349
+ resource: { url: 'https://example.com/api' },
350
+ x402Version: 2,
351
+ } satisfies PaymentRequired
352
+
337
353
  /** Builds a valid 402 response with a WWW-Authenticate header. */
338
354
  function make402(overrides?: { expires?: string; intent?: string; method?: string }) {
339
355
  const method = overrides?.method ?? 'test'
@@ -350,6 +366,19 @@ function make402(overrides?: { expires?: string; intent?: string; method?: strin
350
366
  })
351
367
  }
352
368
 
369
+ function makeCombined402() {
370
+ const response = make402()
371
+ const headers = new Headers(response.headers)
372
+ headers.set(
373
+ x402_Types.paymentRequiredHeader,
374
+ x402_Header.encodePaymentRequired(x402PaymentRequired),
375
+ )
376
+ return new Response(null, {
377
+ status: 402,
378
+ headers,
379
+ })
380
+ }
381
+
353
382
  describe('Fetch.from: init passthrough (non-402)', () => {
354
383
  test('preserves init object identity while adding Accept-Payment', async () => {
355
384
  const receivedInits: (RequestInit | undefined)[] = []
@@ -669,6 +698,182 @@ describe('Fetch.from: 402 retry path', () => {
669
698
  expect(headers.get('Authorization')).toBe('credential')
670
699
  })
671
700
 
701
+ test('chooses native Payment-auth from combined MPP and x402 HTTP 402 offers', async () => {
702
+ let callCount = 0
703
+ const calls: { init: RequestInit | undefined }[] = []
704
+ const mockFetch: typeof globalThis.fetch = async (_input, init) => {
705
+ calls.push({ init })
706
+ callCount++
707
+ if (callCount === 1) return makeCombined402()
708
+ return new Response('OK', { status: 200 })
709
+ }
710
+
711
+ const fetch = Fetch.from({
712
+ fetch: mockFetch,
713
+ methods: [noopMethod],
714
+ })
715
+
716
+ const response = await fetch('https://example.com/api')
717
+
718
+ expect(response.status).toBe(200)
719
+ expect(calls).toHaveLength(2)
720
+ const retryHeaders = new Headers(calls[1]!.init?.headers)
721
+ expect(retryHeaders.get('Authorization')).toBe('credential')
722
+ expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBeNull()
723
+ })
724
+
725
+ test('settles MCP-over-HTTP JSON-RPC payment challenges at the fetch boundary', async () => {
726
+ const mcpChallenge = Challenge.from({
727
+ id: 'mcp-challenge',
728
+ intent: 'test',
729
+ method: 'test',
730
+ realm: 'test',
731
+ request: { amount: '1' },
732
+ })
733
+ const method = {
734
+ ...noopMethod,
735
+ createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
736
+ Credential.serialize({ challenge, payload: { source: 'mcp' } }),
737
+ }
738
+ const initialBody = JSON.stringify({
739
+ jsonrpc: '2.0',
740
+ id: 1,
741
+ method: 'tools/call',
742
+ params: { name: 'paid-tool' },
743
+ })
744
+ let callCount = 0
745
+ const calls: { init: RequestInit | undefined }[] = []
746
+ const mockFetch: typeof globalThis.fetch = async (_input, init) => {
747
+ calls.push({ init })
748
+ callCount++
749
+ if (callCount === 1)
750
+ return Response.json({
751
+ jsonrpc: '2.0',
752
+ id: 1,
753
+ error: {
754
+ code: Mcp.paymentRequiredCode,
755
+ message: 'Payment Required',
756
+ data: { challenges: [mcpChallenge] },
757
+ },
758
+ })
759
+
760
+ const body = JSON.parse(init?.body as string)
761
+ expect(new Headers(init?.headers).get('Authorization')).toBeNull()
762
+ expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
763
+ payload: { source: 'mcp' },
764
+ })
765
+ return Response.json({ ok: true })
766
+ }
767
+
768
+ const fetch = Fetch.from({
769
+ fetch: mockFetch,
770
+ methods: [method],
771
+ })
772
+
773
+ const response = await fetch('https://example.com/mcp', {
774
+ method: 'POST',
775
+ headers: { accept: 'application/json, text/event-stream' },
776
+ body: initialBody,
777
+ })
778
+
779
+ expect(response.status).toBe(200)
780
+ expect(calls).toHaveLength(2)
781
+ })
782
+
783
+ test('settles MCP-over-HTTP when the JSON-RPC request body is carried by Request input', async () => {
784
+ const mcpChallenge = Challenge.from({
785
+ id: 'mcp-request-input-challenge',
786
+ intent: 'test',
787
+ method: 'test',
788
+ realm: 'test',
789
+ request: { amount: '1' },
790
+ })
791
+ const method = {
792
+ ...noopMethod,
793
+ createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
794
+ Credential.serialize({ challenge, payload: { source: 'request-input' } }),
795
+ }
796
+ const initialBody = JSON.stringify({
797
+ jsonrpc: '2.0',
798
+ id: 1,
799
+ method: 'tools/call',
800
+ params: { name: 'paid-tool' },
801
+ })
802
+ let callCount = 0
803
+ const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
804
+ const mockFetch: typeof globalThis.fetch = async (input, init) => {
805
+ calls.push({ input, init })
806
+ callCount++
807
+ if (callCount === 1) {
808
+ expect(input).toBeInstanceOf(Request)
809
+ expect(await (input as Request).text()).toBe(initialBody)
810
+ return Response.json({
811
+ jsonrpc: '2.0',
812
+ id: 1,
813
+ error: {
814
+ code: Mcp.paymentRequiredCode,
815
+ message: 'Payment Required',
816
+ data: { challenges: [mcpChallenge] },
817
+ },
818
+ })
819
+ }
820
+
821
+ const body = JSON.parse(init?.body as string)
822
+ expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
823
+ payload: { source: 'request-input' },
824
+ })
825
+ return Response.json({ ok: true })
826
+ }
827
+
828
+ const fetch = Fetch.from({
829
+ fetch: mockFetch,
830
+ methods: [method],
831
+ })
832
+ const request = new Request('https://example.com/mcp', {
833
+ method: 'POST',
834
+ headers: { accept: 'application/json, text/event-stream' },
835
+ body: initialBody,
836
+ })
837
+
838
+ const response = await fetch(request)
839
+
840
+ expect(response.status).toBe(200)
841
+ expect(calls).toHaveLength(2)
842
+ expect(calls[1]?.input).toBe(request)
843
+ })
844
+
845
+ test('settles native HTTP 402 when a POST body is carried by Request input', async () => {
846
+ const initialBody = JSON.stringify({ ok: true })
847
+ let callCount = 0
848
+ const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
849
+ const mockFetch: typeof globalThis.fetch = async (input, init) => {
850
+ calls.push({ input, init })
851
+ callCount++
852
+ const request = input instanceof Request && !init ? input : new Request(input, init)
853
+ expect(await request.text()).toBe(initialBody)
854
+ if (callCount === 1) return make402()
855
+
856
+ expect(request.headers.get('Authorization')).toBe('credential')
857
+ return new Response('OK', { status: 200 })
858
+ }
859
+
860
+ const fetch = Fetch.from({
861
+ fetch: mockFetch,
862
+ methods: [noopMethod],
863
+ })
864
+ const request = new Request('https://example.com/api', {
865
+ method: 'POST',
866
+ headers: { 'Content-Type': 'application/json' },
867
+ body: initialBody,
868
+ })
869
+
870
+ const response = await fetch(request)
871
+
872
+ expect(response.status).toBe(200)
873
+ expect(calls).toHaveLength(2)
874
+ expect(calls[1]?.input).toBe(request)
875
+ })
876
+
672
877
  test('sends credential retry to the final same-origin 402 response URL', async () => {
673
878
  let callCount = 0
674
879
  const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
@@ -176,6 +176,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
176
176
  const callerHeaders = getCallerHeaders(input, init?.headers)
177
177
  const hasExplicitAcceptPayment = callerHeaders.has(Constants.Headers.acceptPayment)
178
178
  const paymentPreferences = resolvePaymentPreferences(callerHeaders, resolvedAcceptPayment)
179
+ const capturedBody = captureRequestBody(input, init, callerHeaders)
179
180
  const initialRequest = prepareInitialRequest(
180
181
  input,
181
182
  init,
@@ -184,9 +185,10 @@ export function from<const methods extends readonly Method.AnyClient[]>(
184
185
  hasExplicitAcceptPayment,
185
186
  acceptPaymentPolicy,
186
187
  )
187
- const response = await baseFetch(initialRequest.input, initialRequest.init)
188
+ const response = await baseFetch(cloneRequestInput(initialRequest.input), initialRequest.init)
189
+ const transportRequest = withCapturedBody(initialRequest.init, await capturedBody)
188
190
 
189
- if (!transport.isPaymentRequired(response)) return response
191
+ if (!(await transport.isPaymentRequired(response, transportRequest as never))) return response
190
192
 
191
193
  // Only extract context for payment handling after confirming 402.
192
194
  const context = (init as Record<string, unknown> | undefined)?.context
@@ -194,7 +196,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
194
196
  context: _,
195
197
  orderChallenges: requestOrderChallenges,
196
198
  ...fetchInit
197
- } = (initialRequest.init ?? {}) as Record<string, unknown>
199
+ } = (transportRequest ?? {}) as Record<string, unknown>
198
200
 
199
201
  let challenge: Challenge.Challenge | undefined
200
202
  let challenges: readonly Challenge.Challenge[] | undefined
@@ -202,8 +204,8 @@ export function from<const methods extends readonly Method.AnyClient[]>(
202
204
 
203
205
  try {
204
206
  challenges = transport.getChallenges
205
- ? transport.getChallenges(response)
206
- : [transport.getChallenge(response)]
207
+ ? await transport.getChallenges(response, transportRequest as never)
208
+ : [await transport.getChallenge(response, transportRequest as never)]
207
209
 
208
210
  const candidates = AcceptPayment.selectChallengeCandidates(
209
211
  challenges,
@@ -666,7 +668,13 @@ function snapshotInit<methods extends readonly Method.AnyClient[]>(
666
668
  }
667
669
 
668
670
  function snapshotInput(input: RequestInfo | URL | undefined): RequestInfo | URL | undefined {
669
- if (input instanceof Request) return input.clone()
671
+ if (input instanceof Request) {
672
+ try {
673
+ return input.clone()
674
+ } catch {
675
+ return input
676
+ }
677
+ }
670
678
  if (input instanceof URL) return new URL(input)
671
679
  return input
672
680
  }
@@ -738,6 +746,35 @@ function prepareInitialRequest<methods extends readonly Method.AnyClient[]>(
738
746
  }
739
747
  }
740
748
 
749
+ function captureRequestBody<methods extends readonly Method.AnyClient[]>(
750
+ input: RequestInfo | URL,
751
+ init: from.RequestInit<methods> | undefined,
752
+ headers: Headers,
753
+ ): Promise<string | undefined> | undefined {
754
+ if (!(input instanceof Request) || init?.body !== undefined || !input.body || input.bodyUsed)
755
+ return undefined
756
+ if (!headers.has('mcp-method')) {
757
+ const accept = headers.get('accept')?.toLowerCase() ?? ''
758
+ if (!accept.includes('application/json') || !accept.includes('text/event-stream'))
759
+ return undefined
760
+ }
761
+ try {
762
+ return input
763
+ .clone()
764
+ .text()
765
+ .catch(() => undefined)
766
+ } catch {
767
+ return undefined
768
+ }
769
+ }
770
+
771
+ function withCapturedBody<methods extends readonly Method.AnyClient[]>(
772
+ init: from.RequestInit<methods> | undefined,
773
+ body: string | undefined,
774
+ ): from.RequestInit<methods> | undefined {
775
+ return body === undefined ? init : ({ ...init, body } as from.RequestInit<methods>)
776
+ }
777
+
741
778
  /** @internal */
742
779
  function getCallerHeaders(input: RequestInfo | URL, headers: HeadersInit | undefined): Headers {
743
780
  if (headers) return new Headers(headers)
@@ -753,6 +790,15 @@ function unwrapFetch(fetch: typeof globalThis.fetch): typeof globalThis.fetch {
753
790
  return current as typeof globalThis.fetch
754
791
  }
755
792
 
793
+ function cloneRequestInput(input: RequestInfo | URL): RequestInfo | URL {
794
+ if (!(input instanceof Request) || !input.body || input.bodyUsed) return input
795
+ try {
796
+ return input.clone()
797
+ } catch {
798
+ return input
799
+ }
800
+ }
801
+
756
802
  /** @internal */
757
803
  function isWrappedFetch(fetch: typeof globalThis.fetch): fetch is WrappedFetch {
758
804
  return Boolean((fetch as WrappedFetch)[MPPX_FETCH_WRAPPER])