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
@@ -0,0 +1,220 @@
1
+ import { Challenge, Mcp } from 'mppx'
2
+ import { Methods } from 'mppx/tempo'
3
+ import { describe, expect, test } from 'vp/test'
4
+
5
+ import { mcp } from './Mcp.js'
6
+
7
+ const challenge = Challenge.fromMethod(Methods.charge, {
8
+ realm: 'api.example.com',
9
+ secretKey: 'test-secret-key',
10
+ expires: '2025-01-01T00:00:00.000Z',
11
+ request: {
12
+ amount: '0.001',
13
+ currency: '0x20c0000000000000000000000000000000000001',
14
+ decimals: 6,
15
+ recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f8fE00',
16
+ },
17
+ })
18
+
19
+ const paymentRequired = {
20
+ jsonrpc: '2.0',
21
+ id: 1,
22
+ error: {
23
+ code: Mcp.paymentRequiredCode,
24
+ message: 'Payment Required',
25
+ data: { challenges: [challenge] },
26
+ },
27
+ }
28
+
29
+ const request = (overrides: Partial<RequestInit> = {}) =>
30
+ ({
31
+ headers: { accept: 'application/json, text/event-stream' },
32
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} }),
33
+ ...overrides,
34
+ }) satisfies RequestInit
35
+
36
+ const jsonResponse = (body: unknown, contentType = 'application/json') =>
37
+ new Response(typeof body === 'string' ? body : JSON.stringify(body), {
38
+ headers: { 'content-type': contentType },
39
+ })
40
+
41
+ const sseResponse = (body: string) =>
42
+ new Response(body, { headers: { 'content-type': 'text/event-stream' } })
43
+
44
+ const sseEvent = (data: unknown, event = 'message') =>
45
+ `${event ? `event: ${event}\n` : ''}data: ${
46
+ typeof data === 'string' ? data : JSON.stringify(data)
47
+ }\n\n`
48
+
49
+ async function getChallengeIds(response: Response, requestInit = request()) {
50
+ return (await mcp().getChallenges(response, requestInit)).map((entry) => entry.id)
51
+ }
52
+
53
+ describe('mcp HTTP protocol', () => {
54
+ test('extracts payment challenges from JSON-RPC error JSON', async () => {
55
+ expect(await getChallengeIds(jsonResponse(paymentRequired))).toEqual([challenge.id])
56
+ })
57
+
58
+ test('extracts all valid payment challenges', async () => {
59
+ const alternate = { ...challenge, id: 'alternate' }
60
+ const response = jsonResponse({
61
+ ...paymentRequired,
62
+ error: {
63
+ ...paymentRequired.error,
64
+ data: { challenges: [challenge, alternate] },
65
+ },
66
+ })
67
+
68
+ expect(await getChallengeIds(response)).toEqual([challenge.id, alternate.id])
69
+ })
70
+
71
+ test('parses JSON content types case-insensitively', async () => {
72
+ const response = jsonResponse(paymentRequired, 'Application/JSON; charset=utf-8')
73
+
74
+ expect(await getChallengeIds(response)).toEqual([challenge.id])
75
+ })
76
+
77
+ test.each([
78
+ { body: '[', name: 'invalid JSON' },
79
+ { body: [paymentRequired], name: 'JSON-RPC batch' },
80
+ {
81
+ body: { jsonrpc: '2.0', method: 'notifications/progress', params: {} },
82
+ name: 'notification',
83
+ },
84
+ { body: { jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} }, name: 'request' },
85
+ { body: { ...paymentRequired, jsonrpc: '2.1' }, name: 'wrong JSON-RPC version' },
86
+ { body: { ...paymentRequired, id: null }, name: 'null id' },
87
+ { body: { ...paymentRequired, id: 2 }, name: 'different id' },
88
+ { body: { ...paymentRequired, result: {} }, name: 'result and error together' },
89
+ { body: { jsonrpc: '2.0', id: 1, result: {} }, name: 'successful result' },
90
+ {
91
+ body: {
92
+ jsonrpc: '2.0',
93
+ id: 1,
94
+ error: { code: -32600, message: 'Invalid Request' },
95
+ },
96
+ name: 'non-payment error',
97
+ },
98
+ {
99
+ body: {
100
+ jsonrpc: '2.0',
101
+ id: 1,
102
+ error: { code: `${Mcp.paymentRequiredCode}`, message: 'Payment Required' },
103
+ },
104
+ name: 'string error code',
105
+ },
106
+ {
107
+ body: {
108
+ jsonrpc: '2.0',
109
+ id: 1,
110
+ error: { code: Mcp.paymentRequiredCode, message: 402 },
111
+ },
112
+ name: 'non-string error message',
113
+ },
114
+ ])('ignores $name message shapes', async ({ body }) => {
115
+ expect(await getChallengeIds(jsonResponse(body))).toEqual([])
116
+ })
117
+
118
+ test.each([
119
+ { challenges: undefined, name: 'missing challenges' },
120
+ { challenges: [], name: 'empty challenges' },
121
+ { challenges: challenge, name: 'non-array challenges' },
122
+ { challenges: [{ ...challenge, realm: undefined }], name: 'invalid challenge' },
123
+ {
124
+ challenges: [challenge, { ...challenge, realm: undefined }],
125
+ name: 'partially invalid challenges',
126
+ },
127
+ ])('ignores $name', async ({ challenges }) => {
128
+ const response = jsonResponse({
129
+ ...paymentRequired,
130
+ error: {
131
+ ...paymentRequired.error,
132
+ data: challenges === undefined ? undefined : { challenges },
133
+ },
134
+ })
135
+
136
+ expect(await getChallengeIds(response)).toEqual([])
137
+ })
138
+
139
+ test('matches string request and response ids without coercion', async () => {
140
+ const stringRequest = request({
141
+ body: JSON.stringify({ jsonrpc: '2.0', id: '1', method: 'tools/call', params: {} }),
142
+ })
143
+ const response = jsonResponse({ ...paymentRequired, id: '1' })
144
+
145
+ expect(await getChallengeIds(response, stringRequest)).toEqual([challenge.id])
146
+ expect(await getChallengeIds(jsonResponse(paymentRequired), stringRequest)).toEqual([])
147
+ })
148
+
149
+ test.each([
150
+ {
151
+ body: '{',
152
+ headers: { accept: 'application/json, text/event-stream' },
153
+ name: 'malformed request JSON',
154
+ },
155
+ {
156
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),
157
+ headers: { accept: 'application/json, text/event-stream' },
158
+ name: 'request without id',
159
+ },
160
+ {
161
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, params: {} }),
162
+ headers: { accept: 'application/json, text/event-stream' },
163
+ name: 'request without method',
164
+ },
165
+ {
166
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} }),
167
+ headers: { accept: 'application/json' },
168
+ name: 'request without Streamable HTTP accept',
169
+ },
170
+ ])('does not inspect responses for $name', async ({ body, headers }) => {
171
+ expect(
172
+ await getChallengeIds(jsonResponse(paymentRequired), request({ body, headers })),
173
+ ).toEqual([])
174
+ })
175
+
176
+ test('accepts the mcp-method header as MCP request provenance', async () => {
177
+ const requestInit = request({
178
+ headers: { 'mcp-method': 'tools/call' },
179
+ })
180
+
181
+ expect(await getChallengeIds(jsonResponse(paymentRequired), requestInit)).toEqual([
182
+ challenge.id,
183
+ ])
184
+ })
185
+
186
+ test('does not inspect native HTTP 402 responses', async () => {
187
+ expect(await getChallengeIds(jsonResponse(paymentRequired), request({}))).toEqual([
188
+ challenge.id,
189
+ ])
190
+ expect(
191
+ await getChallengeIds(
192
+ new Response(JSON.stringify(paymentRequired), {
193
+ status: 402,
194
+ headers: { 'content-type': 'application/json' },
195
+ }),
196
+ ),
197
+ ).toEqual([])
198
+ })
199
+
200
+ test.each([
201
+ { body: sseEvent(paymentRequired), name: 'message event' },
202
+ { body: sseEvent(paymentRequired, ''), name: 'event without explicit type' },
203
+ { body: `: keepalive\n\n${sseEvent(paymentRequired)}`, name: 'message after SSE comment' },
204
+ ])('extracts payment challenges from SSE $name', async ({ body }) => {
205
+ expect(await getChallengeIds(sseResponse(body))).toEqual([challenge.id])
206
+ })
207
+
208
+ test.each([
209
+ { body: sseEvent('{'), name: 'invalid JSON' },
210
+ { body: sseEvent({ jsonrpc: '2.0', method: 'notifications/progress' }), name: 'notification' },
211
+ { body: sseEvent(paymentRequired, 'progress'), name: 'non-message event' },
212
+ {
213
+ body:
214
+ sseEvent({ jsonrpc: '2.0', method: 'notifications/progress' }) + sseEvent(paymentRequired),
215
+ name: 'payment challenge after first data event',
216
+ },
217
+ ])('ignores SSE $name', async ({ body }) => {
218
+ expect(await getChallengeIds(sseResponse(body))).toEqual([])
219
+ })
220
+ })
@@ -0,0 +1,162 @@
1
+ import { createParser } from 'eventsource-parser'
2
+
3
+ import * as Challenge from '../../../Challenge.js'
4
+ import * as Credential from '../../../Credential.js'
5
+ import * as Mcp from '../../../Mcp.js'
6
+ import type { Protocol } from './Protocol.js'
7
+ import { paymentRequiredStatus } from './Shared.js'
8
+
9
+ /** Returns the JSON-RPC request id — only requests can receive a `-32042` response. */
10
+ function jsonRpcRequestId(body: unknown): number | string | undefined {
11
+ if (typeof body !== 'string') return undefined
12
+ try {
13
+ const message = JSON.parse(body)
14
+ const id = message?.id
15
+ if (message?.jsonrpc !== '2.0' || typeof message?.method !== 'string') return undefined
16
+ return typeof id === 'string' || typeof id === 'number' ? id : undefined
17
+ } catch {
18
+ return undefined
19
+ }
20
+ }
21
+
22
+ const responseCache = new WeakMap<Response, Promise<Mcp.Response | undefined>>()
23
+
24
+ function mcpHttpRequestId(request?: RequestInit): number | string | undefined {
25
+ const id = jsonRpcRequestId(request?.body)
26
+ if (id === undefined) return undefined
27
+ const headers = new Headers(request?.headers)
28
+ if (headers.has('mcp-method')) return id
29
+ const accept = headers.get('accept')?.toLowerCase() ?? ''
30
+ return accept.includes('application/json') && accept.includes('text/event-stream')
31
+ ? id
32
+ : undefined
33
+ }
34
+
35
+ function parseMessage(value: unknown): Mcp.Response | undefined {
36
+ if (!value || typeof value !== 'object') return undefined
37
+ const message = value as {
38
+ error?: { code?: unknown; message?: unknown }
39
+ id?: unknown
40
+ jsonrpc?: unknown
41
+ result?: unknown
42
+ }
43
+ const id = message.id
44
+ if (message.jsonrpc !== '2.0') return undefined
45
+ if (id !== undefined && typeof id !== 'string' && typeof id !== 'number') return undefined
46
+ const hasError = 'error' in message
47
+ const hasResult = 'result' in message
48
+ if (hasError === hasResult) return undefined
49
+ if (hasError)
50
+ return Number.isInteger(message.error?.code) && typeof message.error?.message === 'string'
51
+ ? (value as Mcp.Response)
52
+ : undefined
53
+ return id !== undefined && typeof message.result === 'object'
54
+ ? (value as Mcp.Response)
55
+ : undefined
56
+ }
57
+
58
+ function paymentRequiredChallenges(
59
+ message: Mcp.Response | undefined,
60
+ id: number | string,
61
+ ): Challenge.Challenge[] {
62
+ if (
63
+ !message ||
64
+ message.id !== id ||
65
+ !('error' in message) ||
66
+ message.error?.code !== Mcp.paymentRequiredCode
67
+ )
68
+ return []
69
+ const challenges = message.error?.data?.challenges
70
+ if (!Array.isArray(challenges) || challenges.length === 0) return []
71
+ const parsed: Challenge.Challenge[] = []
72
+ for (const challenge of challenges) {
73
+ const result = Challenge.Schema.safeParse(challenge)
74
+ if (!result.success) return []
75
+ parsed.push(result.data as Challenge.Challenge)
76
+ }
77
+ return parsed
78
+ }
79
+
80
+ async function parseSseJsonRpcResponse(response: Response): Promise<Mcp.Response | undefined> {
81
+ const reader = response.clone().body?.getReader()
82
+ if (!reader) return undefined
83
+
84
+ const decoder = new TextDecoder()
85
+ let dataEventSeen = false
86
+ let message: Mcp.Response | undefined
87
+ const parser = createParser({
88
+ onEvent(event) {
89
+ if (dataEventSeen || !event.data) return
90
+ dataEventSeen = true
91
+ if (event.event && event.event !== 'message') return
92
+ try {
93
+ message = parseMessage(JSON.parse(event.data))
94
+ } catch {}
95
+ },
96
+ })
97
+
98
+ try {
99
+ for (;;) {
100
+ if (dataEventSeen) return message
101
+ const { done, value } = await reader.read()
102
+ if (done) {
103
+ parser.feed(decoder.decode())
104
+ parser.reset({ consume: true })
105
+ return message
106
+ }
107
+ parser.feed(decoder.decode(value, { stream: true }))
108
+ }
109
+ } finally {
110
+ void reader.cancel().catch(() => {})
111
+ }
112
+ }
113
+
114
+ /** Reads a cloned HTTP body into the first JSON-RPC response message, if any. */
115
+ function parseJsonRpcResponse(response: Response): Promise<Mcp.Response | undefined> {
116
+ const cached = responseCache.get(response)
117
+ if (cached) return cached
118
+ const promise = (async (): Promise<Mcp.Response | undefined> => {
119
+ const contentType = response.headers.get('content-type')?.toLowerCase() ?? ''
120
+ if (contentType.includes('application/json'))
121
+ return response
122
+ .clone()
123
+ .json()
124
+ .then(parseMessage)
125
+ .catch(() => undefined)
126
+ if (contentType.includes('text/event-stream')) return parseSseJsonRpcResponse(response)
127
+ return undefined
128
+ })()
129
+ responseCache.set(response, promise)
130
+ return promise
131
+ }
132
+
133
+ /**
134
+ * MCP-over-HTTP — remote MCP rides Streamable HTTP, so its challenge is a JSON-RPC `-32042` error
135
+ * in a normal 200 body (often `text/event-stream`), and the credential rides back in `_meta`.
136
+ */
137
+ export function mcp(): Protocol {
138
+ return {
139
+ getChallenges(response, request) {
140
+ // The 402 schemes own status 402; MCP challenges arrive in a normal 200 body.
141
+ const id = mcpHttpRequestId(request)
142
+ if (response.status === paymentRequiredStatus || id === undefined) return []
143
+ return parseJsonRpcResponse(response).then((message) =>
144
+ paymentRequiredChallenges(message, id),
145
+ )
146
+ },
147
+ setCredential(request, credential) {
148
+ const message = JSON.parse(request.body as string) as Mcp.Request
149
+ const parsed = Credential.deserialize(credential)
150
+ return {
151
+ ...request,
152
+ body: JSON.stringify({
153
+ ...message,
154
+ params: {
155
+ ...message.params,
156
+ ['_meta']: { ...message.params?.['_meta'], [Mcp.credentialMetaKey]: parsed },
157
+ },
158
+ }),
159
+ }
160
+ },
161
+ }
162
+ }
@@ -0,0 +1,21 @@
1
+ import * as Challenge from '../../../Challenge.js'
2
+ import * as Constants from '../../../Constants.js'
3
+ import type { Protocol } from './Protocol.js'
4
+ import { paymentRequiredStatus, setCredentialHeader } from './Shared.js'
5
+
6
+ /** MPP — the native HTTP scheme: a 402 carrying a `WWW-Authenticate` challenge, paid back in `Authorization`. */
7
+ export function mpp(): Protocol {
8
+ return {
9
+ getChallenges(response) {
10
+ if (
11
+ response.status !== paymentRequiredStatus ||
12
+ !response.headers.has(Constants.Headers.wwwAuthenticate)
13
+ )
14
+ return []
15
+ return Challenge.fromResponseList(response)
16
+ },
17
+ setCredential(request, credential) {
18
+ return setCredentialHeader(request, Constants.Headers.authorization, credential)
19
+ },
20
+ }
21
+ }
@@ -0,0 +1,10 @@
1
+ import type * as Challenge from '../../../Challenge.js'
2
+ import type { MaybePromise } from '../../../internal/types.js'
3
+
4
+ /** One payment protocol over the `http` transport. */
5
+ export type Protocol = {
6
+ /** This protocol's challenges from a response; `[]` when the response isn't its concern. */
7
+ getChallenges: (response: Response, request?: RequestInit) => MaybePromise<Challenge.Challenge[]>
8
+ /** Attaches this protocol's credential to a retry request. */
9
+ setCredential: (request: RequestInit, credential: string) => RequestInit
10
+ }
@@ -0,0 +1,25 @@
1
+ import * as Constants from '../../../Constants.js'
2
+ import * as x402_Types from '../../../x402/Types.js'
3
+
4
+ /** HTTP status that signals a native payment challenge. */
5
+ export const paymentRequiredStatus = 402
6
+
7
+ /** Credential headers purged before a fresh credential is attached. */
8
+ const credentialHeaders = [
9
+ Constants.Headers.authorization,
10
+ x402_Types.paymentRequiredHeader,
11
+ x402_Types.paymentResponseHeader,
12
+ x402_Types.paymentSignatureHeader,
13
+ ]
14
+
15
+ /** Attaches `credential` under `header`, clearing any stale credential headers first. */
16
+ export function setCredentialHeader(
17
+ request: RequestInit,
18
+ header: string,
19
+ credential: string,
20
+ ): RequestInit {
21
+ const headers = new Headers(request.headers)
22
+ for (const stale of credentialHeaders) headers.delete(stale)
23
+ headers.set(header, credential)
24
+ return { ...request, headers }
25
+ }
@@ -0,0 +1,42 @@
1
+ import * as Challenge from '../../../Challenge.js'
2
+ import * as x402_Header from '../../../x402/Header.js'
3
+ import * as x402_ChallengeBrand from '../../../x402/internal/ChallengeBrand.js'
4
+ import * as x402_Types from '../../../x402/Types.js'
5
+ import type { Protocol } from './Protocol.js'
6
+ import { paymentRequiredStatus, setCredentialHeader } from './Shared.js'
7
+
8
+ /**
9
+ * x402 — a 402 carrying a `PAYMENT-REQUIRED` header, paid back in `PAYMENT-SIGNATURE`. Synthesized
10
+ * challenges are branded for `evm/client/Charge.ts`, keeping them distinct from native `evm`
11
+ * charges with the same method/intent.
12
+ */
13
+ export function x402(): Protocol {
14
+ return {
15
+ getChallenges(response) {
16
+ if (response.status !== paymentRequiredStatus) return []
17
+ const header = response.headers.get(x402_Types.paymentRequiredHeader)
18
+ if (!header) return []
19
+ const paymentRequired = x402_Header.decodePaymentRequired(header)
20
+ if (response.url && paymentRequired.resource.url !== response.url)
21
+ throw new Error('x402 payment-required resource does not match response URL.')
22
+ return paymentRequired.accepts.map((accepted, index) =>
23
+ x402_ChallengeBrand.mark(
24
+ Challenge.from({
25
+ id: `${x402_Types.syntheticChallengeIdPrefix}${index}`,
26
+ intent: x402_Types.exactIntent,
27
+ method: x402_Types.paymentMethod,
28
+ realm: new URL(paymentRequired.resource.url).host,
29
+ request: {
30
+ ...accepted,
31
+ ...(paymentRequired.extensions ? { extensions: paymentRequired.extensions } : {}),
32
+ resource: paymentRequired.resource,
33
+ },
34
+ }),
35
+ ),
36
+ )
37
+ },
38
+ setCredential(request, credential) {
39
+ return setCredentialHeader(request, x402_Types.paymentSignatureHeader, credential)
40
+ },
41
+ }
42
+ }
@@ -73,7 +73,7 @@ function createMppx<const methods extends Mppx.Methods>(methods: methods) {
73
73
  return Mppx.create({
74
74
  methods,
75
75
  realm: 'test-realm',
76
- secretKey: 'test-secret',
76
+ secretKey: 'test-secret-key-test-secret-key-32',
77
77
  })
78
78
  }
79
79
 
@@ -14,7 +14,7 @@ import { Mppx as ServerMppx } from '../server/index.js'
14
14
 
15
15
  const account = {} as Account
16
16
  const recipient = '0x209693Bc6afc0C5328bA36FaF03C514EF312287C'
17
- const secretKey = 'test-secret'
17
+ const secretKey = 'test-secret-key-test-secret-key-32'
18
18
  const settle = async () => ({
19
19
  reference: `0x${'1'.repeat(64)}` as `0x${string}`,
20
20
  })
@@ -1,4 +1,5 @@
1
1
  export * as assets from '../Assets.js'
2
+ /** @deprecated Use `chains` instead. */
2
3
  export * as Chains from '../Chains.js'
3
4
  export * as chains from '../Chains.js'
4
5
  export { charge } from './Charge.js'
package/src/evm/index.ts CHANGED
@@ -1,4 +1,6 @@
1
+ /** @deprecated Use `assets` instead. */
1
2
  export * as Assets from './Assets.js'
3
+ /** @deprecated Use `chains` instead. */
2
4
  export * as Chains from './Chains.js'
3
5
  export * as Methods from './Methods.js'
4
6
  export * as Types from './Types.js'
@@ -45,7 +45,7 @@ describe('evm charge server', () => {
45
45
  },
46
46
  }),
47
47
  ],
48
- secretKey: 'test-secret',
48
+ secretKey: 'test-secret-key-test-secret-key-32',
49
49
  })
50
50
  const client = ClientMppx.create({
51
51
  methods: [
@@ -1,4 +1,5 @@
1
1
  export * as assets from '../Assets.js'
2
+ /** @deprecated Use `chains` instead. */
2
3
  export * as Chains from '../Chains.js'
3
4
  export * as chains from '../Chains.js'
4
5
  export { charge } from './Charge.js'
@@ -25,7 +25,7 @@ import * as McpServer_transport from '../server/Transport.js'
25
25
  import * as McpClient from './McpClient.js'
26
26
 
27
27
  const realm = 'api.example.com'
28
- const secretKey = 'test-secret-key'
28
+ const secretKey = 'test-secret-key-test-secret-key-32'
29
29
  const chargeAmountRaw = 1_000_000n
30
30
  const doubleSessionAmountRaw = chargeAmountRaw * 2n
31
31
  const topUpAmountRaw = chargeAmountRaw * 3n
@@ -410,6 +410,7 @@ describe.runIf(isLocalnet)('McpClient.wrap integration', () => {
410
410
  type WrappedClient = {
411
411
  callTool: (
412
412
  params: { name: string; arguments?: Record<string, unknown>; _meta?: Record<string, unknown> },
413
+ resultSchema?: undefined,
413
414
  options?: { context?: unknown; timeout?: number },
414
415
  ) => Promise<McpClient.CallToolResult>
415
416
  }
@@ -545,9 +546,14 @@ async function createHarness(options?: {
545
546
  const clientTransport = new StreamableHTTPClientTransport(new URL(`${httpServer.url}/mcp`))
546
547
  await sdkClient.connect(clientTransport as never)
547
548
 
548
- const mcp = McpClient.wrap(sdkClient, {
549
- methods: [chargeMethod, sessionMethod],
550
- })
549
+ // Wrap a facade over the same session so `sdkClient` keeps raw (challenge-
550
+ // throwing) behavior for the credential-level scenarios below.
551
+ const mcp = McpClient.wrap(
552
+ { callTool: sdkClient.callTool.bind(sdkClient) as Client['callTool'] },
553
+ {
554
+ methods: [chargeMethod, sessionMethod],
555
+ },
556
+ )
551
557
 
552
558
  return {
553
559
  async close() {