mppx 0.6.31 → 0.7.0

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 (477) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/Challenge.d.ts.map +1 -1
  3. package/dist/Challenge.js +9 -7
  4. package/dist/Challenge.js.map +1 -1
  5. package/dist/Constants.d.ts +46 -0
  6. package/dist/Constants.d.ts.map +1 -0
  7. package/dist/Constants.js +46 -0
  8. package/dist/Constants.js.map +1 -0
  9. package/dist/Credential.d.ts.map +1 -1
  10. package/dist/Credential.js +5 -4
  11. package/dist/Credential.js.map +1 -1
  12. package/dist/Method.d.ts +32 -4
  13. package/dist/Method.d.ts.map +1 -1
  14. package/dist/Method.js +5 -2
  15. package/dist/Method.js.map +1 -1
  16. package/dist/Receipt.d.ts.map +1 -1
  17. package/dist/Receipt.js +3 -2
  18. package/dist/Receipt.js.map +1 -1
  19. package/dist/cli/cli.d.ts.map +1 -1
  20. package/dist/cli/cli.js +19 -11
  21. package/dist/cli/cli.js.map +1 -1
  22. package/dist/cli/plugins/tempo.d.ts.map +1 -1
  23. package/dist/cli/plugins/tempo.js +17 -6
  24. package/dist/cli/plugins/tempo.js.map +1 -1
  25. package/dist/cli/utils.d.ts +5 -0
  26. package/dist/cli/utils.d.ts.map +1 -1
  27. package/dist/cli/utils.js +10 -0
  28. package/dist/cli/utils.js.map +1 -1
  29. package/dist/client/Methods.d.ts +5 -2
  30. package/dist/client/Methods.d.ts.map +1 -1
  31. package/dist/client/Methods.js +5 -2
  32. package/dist/client/Methods.js.map +1 -1
  33. package/dist/client/Transport.d.ts.map +1 -1
  34. package/dist/client/Transport.js +4 -5
  35. package/dist/client/Transport.js.map +1 -1
  36. package/dist/client/index.d.ts +2 -1
  37. package/dist/client/index.d.ts.map +1 -1
  38. package/dist/client/index.js +2 -1
  39. package/dist/client/index.js.map +1 -1
  40. package/dist/client/internal/Fetch.d.ts.map +1 -1
  41. package/dist/client/internal/Fetch.js +14 -6
  42. package/dist/client/internal/Fetch.js.map +1 -1
  43. package/dist/evm/server/Methods.d.ts +1 -1
  44. package/dist/evm/server/Methods.d.ts.map +1 -1
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/internal/AcceptPayment.d.ts +3 -0
  50. package/dist/internal/AcceptPayment.d.ts.map +1 -1
  51. package/dist/internal/AcceptPayment.js +15 -11
  52. package/dist/internal/AcceptPayment.js.map +1 -1
  53. package/dist/mcp-sdk/client/McpClient.d.ts +12 -5
  54. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
  55. package/dist/mcp-sdk/client/McpClient.js +55 -42
  56. package/dist/mcp-sdk/client/McpClient.js.map +1 -1
  57. package/dist/server/Mppx.d.ts +11 -3
  58. package/dist/server/Mppx.d.ts.map +1 -1
  59. package/dist/server/Mppx.js +76 -27
  60. package/dist/server/Mppx.js.map +1 -1
  61. package/dist/server/Response.d.ts.map +1 -1
  62. package/dist/server/Response.js +2 -1
  63. package/dist/server/Response.js.map +1 -1
  64. package/dist/server/Transport.d.ts.map +1 -1
  65. package/dist/server/Transport.js +4 -3
  66. package/dist/server/Transport.js.map +1 -1
  67. package/dist/server/index.d.ts +1 -0
  68. package/dist/server/index.d.ts.map +1 -1
  69. package/dist/server/index.js +1 -0
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/stripe/client/Charge.d.ts +1 -1
  72. package/dist/stripe/client/Charge.d.ts.map +1 -1
  73. package/dist/stripe/client/Charge.js +3 -1
  74. package/dist/stripe/client/Charge.js.map +1 -1
  75. package/dist/stripe/server/Charge.d.ts +1 -1
  76. package/dist/stripe/server/Charge.d.ts.map +1 -1
  77. package/dist/stripe/server/Charge.js +9 -2
  78. package/dist/stripe/server/Charge.js.map +1 -1
  79. package/dist/stripe/server/Methods.d.ts +1 -1
  80. package/dist/stripe/server/Methods.d.ts.map +1 -1
  81. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  82. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  83. package/dist/stripe/server/internal/html.gen.js +1 -1
  84. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  85. package/dist/tempo/Methods.d.ts +18 -0
  86. package/dist/tempo/Methods.d.ts.map +1 -1
  87. package/dist/tempo/Methods.js +16 -1
  88. package/dist/tempo/Methods.js.map +1 -1
  89. package/dist/tempo/client/Charge.d.ts +6 -0
  90. package/dist/tempo/client/Charge.d.ts.map +1 -1
  91. package/dist/tempo/client/Charge.js +9 -2
  92. package/dist/tempo/client/Charge.js.map +1 -1
  93. package/dist/tempo/client/Methods.d.ts +36 -7
  94. package/dist/tempo/client/Methods.d.ts.map +1 -1
  95. package/dist/tempo/client/Methods.js +12 -5
  96. package/dist/tempo/client/Methods.js.map +1 -1
  97. package/dist/tempo/client/index.d.ts +7 -4
  98. package/dist/tempo/client/index.d.ts.map +1 -1
  99. package/dist/tempo/client/index.js +5 -3
  100. package/dist/tempo/client/index.js.map +1 -1
  101. package/dist/tempo/index.d.ts +1 -0
  102. package/dist/tempo/index.d.ts.map +1 -1
  103. package/dist/tempo/index.js +1 -0
  104. package/dist/tempo/index.js.map +1 -1
  105. package/dist/tempo/internal/fee-payer.d.ts +21 -1
  106. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  107. package/dist/tempo/internal/fee-payer.js +109 -4
  108. package/dist/tempo/internal/fee-payer.js.map +1 -1
  109. package/dist/tempo/{client → legacy/client}/ChannelOps.d.ts +19 -6
  110. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -0
  111. package/dist/tempo/{client → legacy/client}/ChannelOps.js +9 -3
  112. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -0
  113. package/dist/tempo/{client → legacy/client}/Session.d.ts +23 -4
  114. package/dist/tempo/legacy/client/Session.d.ts.map +1 -0
  115. package/dist/tempo/{client → legacy/client}/Session.js +14 -7
  116. package/dist/tempo/legacy/client/Session.js.map +1 -0
  117. package/dist/tempo/{client → legacy/client}/SessionManager.d.ts +20 -5
  118. package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -0
  119. package/dist/tempo/{client → legacy/client}/SessionManager.js +20 -16
  120. package/dist/tempo/legacy/client/SessionManager.js.map +1 -0
  121. package/dist/tempo/legacy/client/index.d.ts +7 -0
  122. package/dist/tempo/legacy/client/index.d.ts.map +1 -0
  123. package/dist/tempo/legacy/client/index.js +5 -0
  124. package/dist/tempo/legacy/client/index.js.map +1 -0
  125. package/dist/tempo/legacy/index.d.ts +7 -0
  126. package/dist/tempo/legacy/index.d.ts.map +1 -0
  127. package/dist/tempo/legacy/index.js +7 -0
  128. package/dist/tempo/legacy/index.js.map +1 -0
  129. package/dist/tempo/{server → legacy/server}/Session.d.ts +28 -11
  130. package/dist/tempo/legacy/server/Session.d.ts.map +1 -0
  131. package/dist/tempo/{server → legacy/server}/Session.js +12 -10
  132. package/dist/tempo/legacy/server/Session.js.map +1 -0
  133. package/dist/tempo/legacy/server/index.d.ts +5 -0
  134. package/dist/tempo/legacy/server/index.d.ts.map +1 -0
  135. package/dist/tempo/legacy/server/index.js +5 -0
  136. package/dist/tempo/legacy/server/index.js.map +1 -0
  137. package/dist/tempo/{session → legacy/session}/Chain.d.ts +30 -23
  138. package/dist/tempo/legacy/session/Chain.d.ts.map +1 -0
  139. package/dist/tempo/{session → legacy/session}/Chain.js +12 -11
  140. package/dist/tempo/legacy/session/Chain.js.map +1 -0
  141. package/dist/tempo/{session → legacy/session}/Channel.d.ts +1 -0
  142. package/dist/tempo/legacy/session/Channel.d.ts.map +1 -0
  143. package/dist/tempo/legacy/session/Channel.js.map +1 -0
  144. package/dist/tempo/legacy/session/ChannelStore.d.ts +22 -0
  145. package/dist/tempo/legacy/session/ChannelStore.d.ts.map +1 -0
  146. package/dist/tempo/legacy/session/ChannelStore.js +6 -0
  147. package/dist/tempo/legacy/session/ChannelStore.js.map +1 -0
  148. package/dist/tempo/legacy/session/Types.d.ts +73 -0
  149. package/dist/tempo/legacy/session/Types.d.ts.map +1 -0
  150. package/dist/tempo/legacy/session/Types.js.map +1 -0
  151. package/dist/tempo/{session → legacy/session}/Voucher.d.ts +4 -4
  152. package/dist/tempo/legacy/session/Voucher.d.ts.map +1 -0
  153. package/dist/tempo/{session → legacy/session}/Voucher.js +1 -1
  154. package/dist/tempo/legacy/session/Voucher.js.map +1 -0
  155. package/dist/tempo/{session → legacy/session}/escrow.abi.d.ts +1 -0
  156. package/dist/tempo/{session → legacy/session}/escrow.abi.d.ts.map +1 -1
  157. package/dist/tempo/{session → legacy/session}/escrow.abi.js +1 -0
  158. package/dist/tempo/legacy/session/escrow.abi.js.map +1 -0
  159. package/dist/tempo/legacy/session/index.d.ts +9 -0
  160. package/dist/tempo/legacy/session/index.d.ts.map +1 -0
  161. package/dist/tempo/legacy/session/index.js +9 -0
  162. package/dist/tempo/legacy/session/index.js.map +1 -0
  163. package/dist/tempo/server/Charge.d.ts +1 -1
  164. package/dist/tempo/server/Charge.d.ts.map +1 -1
  165. package/dist/tempo/server/Charge.js +13 -16
  166. package/dist/tempo/server/Charge.js.map +1 -1
  167. package/dist/tempo/server/Methods.d.ts +63 -6
  168. package/dist/tempo/server/Methods.d.ts.map +1 -1
  169. package/dist/tempo/server/Methods.js +36 -8
  170. package/dist/tempo/server/Methods.js.map +1 -1
  171. package/dist/tempo/server/Subscription.d.ts +1 -1
  172. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  173. package/dist/tempo/server/index.d.ts +6 -5
  174. package/dist/tempo/server/index.d.ts.map +1 -1
  175. package/dist/tempo/server/index.js +5 -5
  176. package/dist/tempo/server/index.js.map +1 -1
  177. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  178. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  179. package/dist/tempo/server/internal/html.gen.js +1 -1
  180. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  181. package/dist/tempo/server/internal/request-body.d.ts +7 -2
  182. package/dist/tempo/server/internal/request-body.d.ts.map +1 -1
  183. package/dist/tempo/server/internal/request-body.js +20 -3
  184. package/dist/tempo/server/internal/request-body.js.map +1 -1
  185. package/dist/tempo/server/internal/transport.d.ts +8 -4
  186. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  187. package/dist/tempo/server/internal/transport.js +8 -7
  188. package/dist/tempo/server/internal/transport.js.map +1 -1
  189. package/dist/tempo/session/Snapshot.d.ts +32 -0
  190. package/dist/tempo/session/Snapshot.d.ts.map +1 -0
  191. package/dist/tempo/session/Snapshot.js +37 -0
  192. package/dist/tempo/session/Snapshot.js.map +1 -0
  193. package/dist/tempo/session/client/ChannelOps.d.ts +82 -0
  194. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -0
  195. package/dist/tempo/session/client/ChannelOps.js +204 -0
  196. package/dist/tempo/session/client/ChannelOps.js.map +1 -0
  197. package/dist/tempo/session/client/CredentialState.d.ts +262 -0
  198. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -0
  199. package/dist/tempo/session/client/CredentialState.js +417 -0
  200. package/dist/tempo/session/client/CredentialState.js.map +1 -0
  201. package/dist/tempo/session/client/ReceiptCoordinator.d.ts +26 -0
  202. package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +1 -0
  203. package/dist/tempo/session/client/ReceiptCoordinator.js +61 -0
  204. package/dist/tempo/session/client/ReceiptCoordinator.js.map +1 -0
  205. package/dist/tempo/session/client/Runtime.d.ts +464 -0
  206. package/dist/tempo/session/client/Runtime.d.ts.map +1 -0
  207. package/dist/tempo/session/client/Runtime.js +499 -0
  208. package/dist/tempo/session/client/Runtime.js.map +1 -0
  209. package/dist/tempo/session/client/Session.d.ts +132 -0
  210. package/dist/tempo/session/client/Session.d.ts.map +1 -0
  211. package/dist/tempo/session/client/Session.js +55 -0
  212. package/dist/tempo/session/client/Session.js.map +1 -0
  213. package/dist/tempo/session/client/SessionManager.d.ts +120 -0
  214. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -0
  215. package/dist/tempo/session/client/SessionManager.js +627 -0
  216. package/dist/tempo/session/client/SessionManager.js.map +1 -0
  217. package/dist/tempo/session/client/Transports.d.ts +449 -0
  218. package/dist/tempo/session/client/Transports.d.ts.map +1 -0
  219. package/dist/tempo/session/client/Transports.js +721 -0
  220. package/dist/tempo/session/client/Transports.js.map +1 -0
  221. package/dist/tempo/session/client/index.d.ts +12 -0
  222. package/dist/tempo/session/client/index.d.ts.map +1 -0
  223. package/dist/tempo/session/client/index.js +5 -0
  224. package/dist/tempo/session/client/index.js.map +1 -0
  225. package/dist/tempo/session/index.d.ts +7 -8
  226. package/dist/tempo/session/index.d.ts.map +1 -1
  227. package/dist/tempo/session/index.js +7 -8
  228. package/dist/tempo/session/index.js.map +1 -1
  229. package/dist/tempo/session/precompile/Chain.d.ts +319 -0
  230. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -0
  231. package/dist/tempo/session/precompile/Chain.js +492 -0
  232. package/dist/tempo/session/precompile/Chain.js.map +1 -0
  233. package/dist/tempo/session/precompile/Channel.d.ts +46 -0
  234. package/dist/tempo/session/precompile/Channel.d.ts.map +1 -0
  235. package/dist/tempo/session/precompile/Channel.js +56 -0
  236. package/dist/tempo/session/precompile/Channel.js.map +1 -0
  237. package/dist/tempo/session/precompile/Protocol.d.ts +308 -0
  238. package/dist/tempo/session/precompile/Protocol.d.ts.map +1 -0
  239. package/dist/tempo/session/precompile/Protocol.js +264 -0
  240. package/dist/tempo/session/precompile/Protocol.js.map +1 -0
  241. package/dist/tempo/session/precompile/Voucher.d.ts +40 -0
  242. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -0
  243. package/dist/tempo/session/precompile/Voucher.js +126 -0
  244. package/dist/tempo/session/precompile/Voucher.js.map +1 -0
  245. package/dist/tempo/session/precompile/escrow.abi.d.ts +522 -0
  246. package/dist/tempo/session/precompile/escrow.abi.d.ts.map +1 -0
  247. package/dist/tempo/session/precompile/escrow.abi.js +224 -0
  248. package/dist/tempo/session/precompile/escrow.abi.js.map +1 -0
  249. package/dist/tempo/session/precompile/index.d.ts +24 -0
  250. package/dist/tempo/session/precompile/index.d.ts.map +1 -0
  251. package/dist/tempo/session/precompile/index.js +22 -0
  252. package/dist/tempo/session/precompile/index.js.map +1 -0
  253. package/dist/tempo/session/server/ChannelOps.d.ts +56 -0
  254. package/dist/tempo/session/server/ChannelOps.d.ts.map +1 -0
  255. package/dist/tempo/session/server/ChannelOps.js +91 -0
  256. package/dist/tempo/session/server/ChannelOps.js.map +1 -0
  257. package/dist/tempo/session/server/ChannelStore.d.ts +347 -0
  258. package/dist/tempo/session/server/ChannelStore.d.ts.map +1 -0
  259. package/dist/tempo/session/server/ChannelStore.js +404 -0
  260. package/dist/tempo/session/server/ChannelStore.js.map +1 -0
  261. package/dist/tempo/session/server/CredentialVerification.d.ts +85 -0
  262. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -0
  263. package/dist/tempo/session/server/CredentialVerification.js +494 -0
  264. package/dist/tempo/session/server/CredentialVerification.js.map +1 -0
  265. package/dist/tempo/session/server/MeteredStream.d.ts +40 -0
  266. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -0
  267. package/dist/tempo/session/server/MeteredStream.js +42 -0
  268. package/dist/tempo/session/server/MeteredStream.js.map +1 -0
  269. package/dist/tempo/session/server/RequestState.d.ts +208 -0
  270. package/dist/tempo/session/server/RequestState.d.ts.map +1 -0
  271. package/dist/tempo/session/server/RequestState.js +252 -0
  272. package/dist/tempo/session/server/RequestState.js.map +1 -0
  273. package/dist/tempo/session/server/Session.d.ts +169 -0
  274. package/dist/tempo/session/server/Session.d.ts.map +1 -0
  275. package/dist/tempo/session/server/Session.js +351 -0
  276. package/dist/tempo/session/server/Session.js.map +1 -0
  277. package/dist/tempo/session/server/Settlement.d.ts +185 -0
  278. package/dist/tempo/session/server/Settlement.d.ts.map +1 -0
  279. package/dist/tempo/session/server/Settlement.js +250 -0
  280. package/dist/tempo/session/server/Settlement.js.map +1 -0
  281. package/dist/tempo/session/{Sse.d.ts → server/Sse.d.ts} +9 -56
  282. package/dist/tempo/session/server/Sse.d.ts.map +1 -0
  283. package/dist/tempo/session/server/Sse.js +184 -0
  284. package/dist/tempo/session/server/Sse.js.map +1 -0
  285. package/dist/tempo/session/server/Transports.d.ts +89 -0
  286. package/dist/tempo/session/server/Transports.d.ts.map +1 -0
  287. package/dist/tempo/session/server/Transports.js +149 -0
  288. package/dist/tempo/session/server/Transports.js.map +1 -0
  289. package/dist/tempo/session/server/Ws.d.ts +48 -0
  290. package/dist/tempo/session/server/Ws.d.ts.map +1 -0
  291. package/dist/tempo/session/server/Ws.js +244 -0
  292. package/dist/tempo/session/server/Ws.js.map +1 -0
  293. package/dist/tempo/session/server/index.d.ts +4 -0
  294. package/dist/tempo/session/server/index.d.ts.map +1 -0
  295. package/dist/tempo/session/server/index.js +2 -0
  296. package/dist/tempo/session/server/index.js.map +1 -0
  297. package/package.json +6 -1
  298. package/src/Challenge.ts +9 -7
  299. package/src/Constants.ts +58 -0
  300. package/src/Credential.ts +5 -4
  301. package/src/Method.ts +46 -5
  302. package/src/Receipt.ts +3 -2
  303. package/src/cli/cli.test.ts +23 -28
  304. package/src/cli/cli.ts +23 -10
  305. package/src/cli/mcp.test.ts +21 -7
  306. package/src/cli/plugins/tempo.ts +21 -8
  307. package/src/cli/utils.test.ts +25 -1
  308. package/src/cli/utils.ts +10 -0
  309. package/src/client/Methods.ts +5 -2
  310. package/src/client/Mppx.test-d.ts +10 -0
  311. package/src/client/Mppx.test.ts +75 -0
  312. package/src/client/Transport.ts +4 -5
  313. package/src/client/index.ts +11 -1
  314. package/src/client/internal/Fetch.test.ts +29 -4
  315. package/src/client/internal/Fetch.ts +17 -5
  316. package/src/env.d.ts +1 -1
  317. package/src/index.ts +1 -0
  318. package/src/internal/AcceptPayment.test.ts +61 -0
  319. package/src/internal/AcceptPayment.ts +21 -14
  320. package/src/mcp-sdk/client/McpClient.integration.test.ts +8 -7
  321. package/src/mcp-sdk/client/McpClient.test-d.ts +7 -0
  322. package/src/mcp-sdk/client/McpClient.ts +99 -67
  323. package/src/mcp-sdk/client/McpClient.unit.test.ts +131 -0
  324. package/src/middlewares/elysia.test.ts +8 -4
  325. package/src/middlewares/express.test.ts +8 -4
  326. package/src/middlewares/hono.test.ts +4 -4
  327. package/src/middlewares/nextjs.test.ts +8 -4
  328. package/src/proxy/Proxy.test.ts +8 -8
  329. package/src/server/Mppx.test-d.ts +54 -0
  330. package/src/server/Mppx.test.ts +200 -7
  331. package/src/server/Mppx.ts +487 -406
  332. package/src/server/Response.ts +2 -1
  333. package/src/server/Transport.ts +4 -3
  334. package/src/server/index.ts +1 -0
  335. package/src/stripe/client/Charge.test.ts +20 -5
  336. package/src/stripe/client/Charge.ts +6 -2
  337. package/src/stripe/server/Charge.test.ts +114 -1
  338. package/src/stripe/server/Charge.ts +13 -2
  339. package/src/stripe/server/internal/html.gen.ts +1 -1
  340. package/src/tempo/AccessKeyAuthorization.test.ts +4 -94
  341. package/src/tempo/Methods.test.ts +45 -17
  342. package/src/tempo/Methods.ts +22 -0
  343. package/src/tempo/PublicExports.test-d.ts +105 -0
  344. package/src/tempo/client/Charge.test.ts +85 -0
  345. package/src/tempo/client/Charge.ts +19 -2
  346. package/src/tempo/client/Methods.ts +18 -6
  347. package/src/tempo/client/index.ts +15 -4
  348. package/src/tempo/index.ts +1 -0
  349. package/src/tempo/internal/fee-payer.test.ts +241 -17
  350. package/src/tempo/internal/fee-payer.ts +150 -4
  351. package/src/tempo/internal/fee-token.test.ts +14 -9
  352. package/src/tempo/legacy/AccessKeyAuthorization.test.ts +162 -0
  353. package/src/tempo/legacy/README.md +9 -0
  354. package/src/tempo/{client → legacy/client}/ChannelOps.test.ts +6 -7
  355. package/src/tempo/{client → legacy/client}/ChannelOps.ts +22 -9
  356. package/src/tempo/{client → legacy/client}/Session.test.ts +51 -9
  357. package/src/tempo/{client → legacy/client}/Session.ts +25 -11
  358. package/src/tempo/{client → legacy/client}/SessionManager.test.ts +81 -9
  359. package/src/tempo/{client → legacy/client}/SessionManager.ts +41 -20
  360. package/src/tempo/legacy/client/index.ts +6 -0
  361. package/src/tempo/legacy/index.ts +6 -0
  362. package/src/tempo/{server → legacy/server}/Session.test.ts +45 -45
  363. package/src/tempo/{server → legacy/server}/Session.ts +32 -23
  364. package/src/tempo/legacy/server/index.ts +4 -0
  365. package/src/tempo/{session → legacy/session}/Chain.test.ts +3 -4
  366. package/src/tempo/{session → legacy/session}/Chain.ts +94 -63
  367. package/src/tempo/{session → legacy/session}/Channel.ts +1 -0
  368. package/src/tempo/legacy/session/ChannelStore.test.ts +58 -0
  369. package/src/tempo/legacy/session/ChannelStore.ts +39 -0
  370. package/src/tempo/legacy/session/Types.ts +91 -0
  371. package/src/tempo/{session → legacy/session}/Voucher.ts +12 -8
  372. package/src/tempo/{session → legacy/session}/escrow.abi.ts +1 -0
  373. package/src/tempo/legacy/session/index.ts +8 -0
  374. package/src/tempo/server/AtomicStore.test-d.ts +16 -11
  375. package/src/tempo/server/Charge.test.ts +92 -14
  376. package/src/tempo/server/Charge.ts +18 -16
  377. package/src/tempo/server/Methods.ts +54 -8
  378. package/src/tempo/server/Sse.test.ts +2 -2
  379. package/src/tempo/server/index.ts +6 -5
  380. package/src/tempo/server/internal/html.gen.ts +1 -1
  381. package/src/tempo/server/internal/request-body.test.ts +37 -4
  382. package/src/tempo/server/internal/request-body.ts +25 -6
  383. package/src/tempo/server/internal/transport.test.ts +4 -4
  384. package/src/tempo/server/internal/transport.ts +19 -10
  385. package/src/tempo/session/Snapshot.test.ts +41 -0
  386. package/src/tempo/session/Snapshot.ts +74 -0
  387. package/src/tempo/session/client/ChannelOps.test.ts +163 -0
  388. package/src/tempo/session/client/ChannelOps.ts +344 -0
  389. package/src/tempo/session/client/CredentialState.test.ts +645 -0
  390. package/src/tempo/session/client/CredentialState.ts +814 -0
  391. package/src/tempo/session/client/ReceiptCoordinator.ts +95 -0
  392. package/src/tempo/session/client/Runtime.test.ts +1092 -0
  393. package/src/tempo/session/client/Runtime.ts +986 -0
  394. package/src/tempo/session/client/Session.test.ts +734 -0
  395. package/src/tempo/session/client/Session.ts +97 -0
  396. package/src/tempo/session/client/SessionManager.test.ts +1308 -0
  397. package/src/tempo/session/client/SessionManager.ts +845 -0
  398. package/src/tempo/session/client/Transports.test.ts +837 -0
  399. package/src/tempo/session/client/Transports.ts +1292 -0
  400. package/src/tempo/session/client/index.ts +37 -0
  401. package/src/tempo/session/index.ts +7 -8
  402. package/src/tempo/session/precompile/Chain.integration.test.ts +321 -0
  403. package/src/tempo/session/precompile/Chain.test.ts +1258 -0
  404. package/src/tempo/session/precompile/Chain.ts +979 -0
  405. package/src/tempo/session/precompile/Channel.test.ts +138 -0
  406. package/src/tempo/session/precompile/Channel.ts +103 -0
  407. package/src/tempo/session/precompile/Protocol.test.ts +358 -0
  408. package/src/tempo/session/precompile/Protocol.ts +520 -0
  409. package/src/tempo/session/precompile/Voucher.test.ts +316 -0
  410. package/src/tempo/session/precompile/Voucher.ts +160 -0
  411. package/src/tempo/session/precompile/escrow.abi.ts +226 -0
  412. package/src/tempo/session/precompile/index.ts +33 -0
  413. package/src/tempo/session/server/ChannelOps.test.ts +129 -0
  414. package/src/tempo/session/server/ChannelOps.ts +157 -0
  415. package/src/tempo/session/{ChannelStore.test.ts → server/ChannelStore.test.ts} +536 -29
  416. package/src/tempo/session/server/ChannelStore.ts +835 -0
  417. package/src/tempo/session/server/CredentialVerification.test.ts +146 -0
  418. package/src/tempo/session/server/CredentialVerification.ts +710 -0
  419. package/src/tempo/session/server/MeteredStream.ts +88 -0
  420. package/src/tempo/session/server/RequestState.test.ts +531 -0
  421. package/src/tempo/session/server/RequestState.ts +499 -0
  422. package/src/tempo/session/server/Session.integration.test.ts +444 -0
  423. package/src/tempo/session/server/Session.test.ts +3253 -0
  424. package/src/tempo/session/server/Session.ts +543 -0
  425. package/src/tempo/session/server/Settlement.test.ts +242 -0
  426. package/src/tempo/session/server/Settlement.ts +470 -0
  427. package/src/tempo/session/{Sse.test.ts → server/Sse.test.ts} +37 -3
  428. package/src/tempo/session/server/Sse.ts +256 -0
  429. package/src/tempo/session/server/Transports.test.ts +346 -0
  430. package/src/tempo/session/server/Transports.ts +255 -0
  431. package/src/tempo/session/{Ws.test.ts → server/Ws.test.ts} +4 -4
  432. package/src/tempo/session/server/Ws.ts +384 -0
  433. package/src/tempo/session/server/index.ts +8 -0
  434. package/dist/tempo/client/ChannelOps.d.ts.map +0 -1
  435. package/dist/tempo/client/ChannelOps.js.map +0 -1
  436. package/dist/tempo/client/Session.d.ts.map +0 -1
  437. package/dist/tempo/client/Session.js.map +0 -1
  438. package/dist/tempo/client/SessionManager.d.ts.map +0 -1
  439. package/dist/tempo/client/SessionManager.js.map +0 -1
  440. package/dist/tempo/server/Session.d.ts.map +0 -1
  441. package/dist/tempo/server/Session.js.map +0 -1
  442. package/dist/tempo/session/Chain.d.ts.map +0 -1
  443. package/dist/tempo/session/Chain.js.map +0 -1
  444. package/dist/tempo/session/Channel.d.ts.map +0 -1
  445. package/dist/tempo/session/Channel.js.map +0 -1
  446. package/dist/tempo/session/ChannelStore.d.ts +0 -117
  447. package/dist/tempo/session/ChannelStore.d.ts.map +0 -1
  448. package/dist/tempo/session/ChannelStore.js +0 -172
  449. package/dist/tempo/session/ChannelStore.js.map +0 -1
  450. package/dist/tempo/session/Receipt.d.ts +0 -22
  451. package/dist/tempo/session/Receipt.d.ts.map +0 -1
  452. package/dist/tempo/session/Receipt.js +0 -34
  453. package/dist/tempo/session/Receipt.js.map +0 -1
  454. package/dist/tempo/session/Sse.d.ts.map +0 -1
  455. package/dist/tempo/session/Sse.js +0 -363
  456. package/dist/tempo/session/Sse.js.map +0 -1
  457. package/dist/tempo/session/Types.d.ts +0 -78
  458. package/dist/tempo/session/Types.d.ts.map +0 -1
  459. package/dist/tempo/session/Types.js.map +0 -1
  460. package/dist/tempo/session/Voucher.d.ts.map +0 -1
  461. package/dist/tempo/session/Voucher.js.map +0 -1
  462. package/dist/tempo/session/Ws.d.ts +0 -87
  463. package/dist/tempo/session/Ws.d.ts.map +0 -1
  464. package/dist/tempo/session/Ws.js +0 -443
  465. package/dist/tempo/session/Ws.js.map +0 -1
  466. package/dist/tempo/session/escrow.abi.js.map +0 -1
  467. package/src/tempo/session/ChannelStore.ts +0 -308
  468. package/src/tempo/session/Receipt.test.ts +0 -89
  469. package/src/tempo/session/Receipt.ts +0 -46
  470. package/src/tempo/session/Sse.ts +0 -462
  471. package/src/tempo/session/Types.ts +0 -86
  472. package/src/tempo/session/Ws.ts +0 -576
  473. /package/dist/tempo/{session → legacy/session}/Channel.js +0 -0
  474. /package/dist/tempo/{session → legacy/session}/Types.js +0 -0
  475. /package/src/tempo/{session → legacy/session}/Channel.test.ts +0 -0
  476. /package/src/tempo/{session → legacy/session}/Voucher.test.ts +0 -0
  477. /package/src/tempo/session/{Sse.fuzz.test.ts → server/Sse.fuzz.test.ts} +0 -0
@@ -4,7 +4,7 @@ import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
4
4
  import { createClient, defineChain } from 'viem'
5
5
  import { describe, expect, test, vi } from 'vp/test'
6
6
  import * as Http from '~test/Http.js'
7
- import { rpcUrl } from '~test/tempo/prool.js'
7
+ import { rpcUrl } from '~test/tempo/rpc.js'
8
8
  import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
9
9
 
10
10
  import * as Fetch from './Fetch.js'
@@ -669,7 +669,7 @@ describe('Fetch.from: 402 retry path', () => {
669
669
  expect(headers.get('Authorization')).toBe('credential')
670
670
  })
671
671
 
672
- test('sends credential retry to the final 402 response URL', async () => {
672
+ test('sends credential retry to the final same-origin 402 response URL', async () => {
673
673
  let callCount = 0
674
674
  const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
675
675
  const mockFetch: typeof globalThis.fetch = async (input, init) => {
@@ -678,7 +678,7 @@ describe('Fetch.from: 402 retry path', () => {
678
678
  if (callCount === 1) {
679
679
  const response = make402()
680
680
  Object.defineProperty(response, 'url', {
681
- value: 'https://payments.example.com/protected',
681
+ value: 'https://api.example.com/redirected-protected',
682
682
  })
683
683
  return response
684
684
  }
@@ -694,10 +694,35 @@ describe('Fetch.from: 402 retry path', () => {
694
694
 
695
695
  expect(response.status).toBe(200)
696
696
  expect(calls[0]!.input).toBe('https://api.example.com/protected')
697
- expect(calls[1]!.input).toBe('https://payments.example.com/protected')
697
+ expect(calls[1]!.input).toBe('https://api.example.com/redirected-protected')
698
698
  expect(new Headers(calls[1]!.init?.headers).get('Authorization')).toBe('credential')
699
699
  })
700
700
 
701
+ test('rejects credential retry to a cross-origin 402 response URL', async () => {
702
+ const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
703
+ const mockFetch: typeof globalThis.fetch = async (input, init) => {
704
+ calls.push({ input, init })
705
+ const response = make402()
706
+ Object.defineProperty(response, 'url', {
707
+ value: 'https://payments.example.com/protected',
708
+ })
709
+ return response
710
+ }
711
+
712
+ const fetch = Fetch.from({
713
+ fetch: mockFetch,
714
+ methods: [noopMethod],
715
+ })
716
+
717
+ await expect(fetch('https://api.example.com/protected')).rejects.toThrow(
718
+ 'Refusing to send payment credential across redirect from https://api.example.com to https://payments.example.com',
719
+ )
720
+
721
+ expect(calls).toHaveLength(1)
722
+ expect(calls[0]!.input).toBe('https://api.example.com/protected')
723
+ expect(new Headers(calls[0]!.init?.headers).get('Authorization')).toBeNull()
724
+ })
725
+
701
726
  test('emits client events and allows challenge handler to provide credential', async () => {
702
727
  const events: string[] = []
703
728
  const createCredential = vi.fn(async () => 'method-credential')
@@ -1,4 +1,5 @@
1
1
  import * as Challenge from '../../Challenge.js'
2
+ import * as Constants from '../../Constants.js'
2
3
  import * as Expires from '../../Expires.js'
3
4
  import * as AcceptPayment from '../../internal/AcceptPayment.js'
4
5
  import type { MaybePromise } from '../../internal/types.js'
@@ -173,7 +174,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
173
174
 
174
175
  const wrappedFetch = async (input: RequestInfo | URL, init?: from.RequestInit<methods>) => {
175
176
  const callerHeaders = getCallerHeaders(input, init?.headers)
176
- const hasExplicitAcceptPayment = callerHeaders.has('Accept-Payment')
177
+ const hasExplicitAcceptPayment = callerHeaders.has(Constants.Headers.acceptPayment)
177
178
  const paymentPreferences = resolvePaymentPreferences(callerHeaders, resolvedAcceptPayment)
178
179
  const initialRequest = prepareInitialRequest(
179
180
  input,
@@ -262,7 +263,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
262
263
  )
263
264
 
264
265
  const paymentResponse = await baseFetch(
265
- resolvePaymentRetryInput(response, initialRequest.input),
266
+ resolvePaymentRetryInput(response, initialRequest.input, initialRequest.input),
266
267
  transport.setCredential(
267
268
  {
268
269
  ...fetchInit,
@@ -717,7 +718,7 @@ function prepareInitialRequest<methods extends readonly Method.AnyClient[]>(
717
718
  callerHeaders.forEach((value, key) => {
718
719
  headers.set(key, value)
719
720
  })
720
- headers.set('Accept-Payment', header)
721
+ headers.set(Constants.Headers.acceptPayment, header)
721
722
 
722
723
  if (init) {
723
724
  // Preserve init identity for callers like websocket upgrade helpers that
@@ -781,7 +782,7 @@ function resolvePaymentPreferences<methods extends readonly Method.AnyClient[]>(
781
782
  headers: Headers,
782
783
  acceptPayment: AcceptPayment.Resolved<methods>,
783
784
  ): AcceptPayment.Resolved<methods> {
784
- const header = headers.get('Accept-Payment')
785
+ const header = headers.get(Constants.Headers.acceptPayment)
785
786
  if (!header) return acceptPayment
786
787
 
787
788
  try {
@@ -847,6 +848,17 @@ function resolveRequestUrl(input: RequestInfo | URL): URL {
847
848
  function resolvePaymentRetryInput(
848
849
  response: Response,
849
850
  fallback: RequestInfo | URL,
851
+ initialInput: RequestInfo | URL,
850
852
  ): RequestInfo | URL {
851
- return response.url ? response.url : fallback
853
+ if (!response.url) return fallback
854
+
855
+ const responseUrl = new URL(response.url)
856
+ const initialUrl = resolveRequestUrl(initialInput)
857
+ if (responseUrl.origin !== initialUrl.origin) {
858
+ throw new Error(
859
+ `Refusing to send payment credential across redirect from ${initialUrl.origin} to ${responseUrl.origin}`,
860
+ )
861
+ }
862
+
863
+ return response.url
852
864
  }
package/src/env.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  interface ImportMetaEnv {
4
4
  readonly MODE: 'test' | 'production'
5
- readonly VITE_NODE_ENV: 'localnet' | 'testnet' | 'mainnet'
5
+ readonly VITE_TEMPO_NETWORK: 'localnet' | 'moderato' | 'devnet'
6
6
  readonly VITE_HTTP_LOG: 'true' | 'false'
7
7
  readonly VITE_RPC_CREDENTIALS: string
8
8
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * as BodyDigest from './BodyDigest.js'
2
2
  export * as Challenge from './Challenge.js'
3
+ export * as Constants from './Constants.js'
3
4
  export * as Credential from './Credential.js'
4
5
  export * as Errors from './Errors.js'
5
6
  export * as Expires from './Expires.js'
@@ -178,6 +178,67 @@ describe('AcceptPayment', () => {
178
178
  ])
179
179
  })
180
180
 
181
+ test('selectChallengeCandidates supports duplicate method keys with challenge predicates', () => {
182
+ const challenges = [
183
+ {
184
+ id: 'v2',
185
+ intent: 'session',
186
+ method: 'tempo',
187
+ realm: 'test',
188
+ request: { methodDetails: { sessionProtocol: 'v2' } },
189
+ },
190
+ {
191
+ id: 'v1',
192
+ intent: 'session',
193
+ method: 'tempo',
194
+ realm: 'test',
195
+ request: { methodDetails: { sessionProtocol: 'v1' } },
196
+ },
197
+ {
198
+ id: 'old-server',
199
+ intent: 'session',
200
+ method: 'tempo',
201
+ realm: 'test',
202
+ request: {},
203
+ },
204
+ ]
205
+ const methods = [
206
+ {
207
+ name: 'tempo',
208
+ intent: 'session',
209
+ canHandleChallenge: ({
210
+ challenge,
211
+ }: {
212
+ challenge: AcceptPayment.ChallengeCandidate['challenge']
213
+ }) =>
214
+ (challenge.request.methodDetails as { sessionProtocol?: string } | undefined)
215
+ ?.sessionProtocol === 'v2',
216
+ },
217
+ {
218
+ name: 'tempo',
219
+ intent: 'session',
220
+ canHandleChallenge: ({
221
+ challenge,
222
+ }: {
223
+ challenge: AcceptPayment.ChallengeCandidate['challenge']
224
+ }) => {
225
+ const sessionProtocol = (
226
+ challenge.request.methodDetails as { sessionProtocol?: string } | undefined
227
+ )?.sessionProtocol
228
+ return sessionProtocol === undefined || sessionProtocol === 'v1'
229
+ },
230
+ },
231
+ ] as const
232
+
233
+ const candidates = AcceptPayment.selectChallengeCandidates(
234
+ challenges,
235
+ methods,
236
+ AcceptPayment.parse('tempo/session'),
237
+ )
238
+
239
+ expect(candidates.map(({ challenge }) => challenge.id)).toEqual(['v2', 'v1', 'old-server'])
240
+ })
241
+
181
242
  test('selectChallenge honors a specific opt-out over a broader wildcard', () => {
182
243
  const selected = AcceptPayment.selectChallenge(
183
244
  [
@@ -2,6 +2,7 @@ import type * as Challenge from '../Challenge.js'
2
2
  import type { MaybePromise } from './types.js'
3
3
 
4
4
  type MethodLike = {
5
+ canHandleChallenge?: ((parameters: { challenge: Challenge.Challenge }) => boolean) | undefined
5
6
  intent: string
6
7
  name: string
7
8
  }
@@ -179,28 +180,34 @@ export function selectChallengeCandidates<const methods extends readonly MethodL
179
180
  methods: methods,
180
181
  preferences: readonly Entry[],
181
182
  ): ChallengeCandidate<methods[number]>[] {
182
- const methodByKey = new Map<string, methods[number]>()
183
+ const methodsByKey = new Map<string, methods[number][]>()
183
184
  for (const method of methods) {
184
185
  const key = keyOf(method)
185
- if (!methodByKey.has(key)) methodByKey.set(key, method)
186
+ const matchingMethods = methodsByKey.get(key)
187
+ if (matchingMethods) matchingMethods.push(method)
188
+ else methodsByKey.set(key, [method])
186
189
  }
187
190
 
188
191
  return challenges
189
- .map((challenge, index) => {
190
- const method = methodByKey.get(keyOf(challenge))
191
- if (!method) return undefined
192
+ .flatMap((challenge, index) => {
193
+ const matchingMethods = methodsByKey.get(keyOf(challenge))
194
+ if (!matchingMethods) return []
192
195
 
193
196
  const match = bestMatch(challenge, preferences)
194
- if (!match || match.q <= 0) return undefined
195
-
196
- return {
197
- challenge,
198
- index,
199
- match,
200
- method,
201
- } as ChallengeCandidate<methods[number]> & { match: Match }
197
+ if (!match || match.q <= 0) return []
198
+
199
+ return matchingMethods
200
+ .filter((method) => method.canHandleChallenge?.({ challenge }) ?? true)
201
+ .map(
202
+ (method) =>
203
+ ({
204
+ challenge,
205
+ index,
206
+ match,
207
+ method,
208
+ }) as ChallengeCandidate<methods[number]> & { match: Match },
209
+ )
202
210
  })
203
- .filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate))
204
211
  .sort((left, right) => right.match.q - left.match.q || left.index - right.index)
205
212
  .map((candidate) => {
206
213
  const { match: _match, ...rest } = candidate
@@ -6,21 +6,21 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
6
6
  import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js'
7
7
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
8
8
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
9
- import { session as tempo_session_client, tempo as tempo_client } from 'mppx/client'
9
+ import { sessionLegacy as tempo_session_client, tempo as tempo_client } from 'mppx/client'
10
10
  import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
11
11
  import type { Address } from 'viem'
12
12
  import { readContract } from 'viem/actions'
13
13
  import { Actions, Addresses } from 'viem/tempo'
14
14
  import { beforeAll, describe, expect, test } from 'vp/test'
15
- import { nodeEnv } from '~test/config.js'
16
- import { deployEscrow, signTopUpChannel } from '~test/tempo/session.js'
15
+ import { tempoNetwork } from '~test/config.js'
16
+ import { deployEscrow, signTopUpChannel } from '~test/tempo/legacy/session.js'
17
17
  import { accounts, asset, client as testClient, fundAccount } from '~test/tempo/viem.js'
18
18
 
19
19
  import * as Credential from '../../Credential.js'
20
20
  import * as core_Mcp from '../../Mcp.js'
21
21
  import * as Store from '../../Store.js'
22
- import * as ChannelStore from '../../tempo/session/ChannelStore.js'
23
- import type { SessionReceipt } from '../../tempo/session/Types.js'
22
+ import type { SessionReceipt } from '../../tempo/session/precompile/Protocol.js'
23
+ import * as ChannelStore from '../../tempo/session/server/ChannelStore.js'
24
24
  import * as McpServer_transport from '../server/Transport.js'
25
25
  import * as McpClient from './McpClient.js'
26
26
 
@@ -29,6 +29,7 @@ const secretKey = 'test-secret-key'
29
29
  const chargeAmountRaw = 1_000_000n
30
30
  const doubleSessionAmountRaw = chargeAmountRaw * 2n
31
31
  const topUpAmountRaw = chargeAmountRaw * 3n
32
+ const isLocalnet = tempoNetwork === 'localnet'
32
33
 
33
34
  let escrowContract: Address
34
35
 
@@ -40,7 +41,7 @@ beforeAll(async () => {
40
41
  await fundAccount({ address: accounts[2].address, token: asset })
41
42
  }, 60_000)
42
43
 
43
- describe.runIf(nodeEnv === 'localnet')('McpClient.wrap integration', () => {
44
+ describe.runIf(isLocalnet)('McpClient.wrap integration', () => {
44
45
  const scenarios: readonly Scenario[] = [
45
46
  {
46
47
  name: 'charge intent settles a paid MCP tool against the live chain',
@@ -464,7 +465,7 @@ async function createHarness(options?: {
464
465
  currency: asset,
465
466
  getClient: () => testClient,
466
467
  }),
467
- tempo_server.session({
468
+ tempo_server.sessionLegacy({
468
469
  account: accounts[0],
469
470
  currency: asset,
470
471
  escrowContract,
@@ -73,8 +73,15 @@ describe('McpClient.wrap', () => {
73
73
  })
74
74
 
75
75
  expectTypeOf(wrapped.callTool).toBeCallableWith({ name: 'tool' })
76
+ expectTypeOf(wrapped.callTool).toBeCallableWith(null, { name: 'tool' })
77
+ expectTypeOf(wrapped.callTool).toBeCallableWith(() => true, { name: 'tool' })
76
78
  expectTypeOf(wrapped.callTool).toBeCallableWith({ name: 'tool' }, {})
77
79
  expectTypeOf(wrapped.callTool).toBeCallableWith({ name: 'tool' }, { timeout: 5000 })
80
+ expectTypeOf(wrapped.callTool).toBeCallableWith(
81
+ async (challenge) => challenge.intent === 'charge',
82
+ { name: 'tool' },
83
+ { timeout: 5000 },
84
+ )
78
85
  })
79
86
 
80
87
  test('callTool result includes receipt', () => {
@@ -11,6 +11,14 @@ import type * as z from '../../zod.js'
11
11
 
12
12
  type AnyClient = Method.Client<any, any>
13
13
 
14
+ export type CallToolParameters = {
15
+ name: string
16
+ arguments?: Record<string, unknown>
17
+ _meta?: Record<string, unknown>
18
+ }
19
+
20
+ export type OnPaymentRequired = (challenge: Challenge.Challenge) => boolean | Promise<boolean>
21
+
14
22
  /**
15
23
  * Result of a tool call with payment handling.
16
24
  * Extends the SDK's callTool return type with an optional payment receipt.
@@ -55,69 +63,91 @@ export function wrap<
55
63
  const { methods } = config
56
64
  const paymentPreferences = AcceptPayment.resolve(methods)
57
65
 
58
- return {
59
- ...client,
60
- async callTool(params, options) {
61
- const context = options?.context
62
- const timeout = options?.timeout
63
-
64
- try {
65
- const result = await client.callTool(
66
- params,
67
- undefined,
68
- timeout !== undefined ? { timeout } : undefined,
66
+ const callTool = (async (
67
+ first: CallToolParameters | OnPaymentRequired | null | undefined,
68
+ second?: CallToolParameters | wrap.CallToolOptions<methods>,
69
+ third?: wrap.CallToolOptions<methods>,
70
+ ) => {
71
+ const hasApprovalArgument = typeof first === 'function' || first === null || first === undefined
72
+ const params = (hasApprovalArgument ? second : first) as CallToolParameters
73
+ const options = (hasApprovalArgument ? third : second) as
74
+ | wrap.CallToolOptions<methods>
75
+ | undefined
76
+ const onPaymentRequired =
77
+ first === null
78
+ ? undefined
79
+ : hasApprovalArgument
80
+ ? ((first as OnPaymentRequired | undefined) ?? config.onPaymentRequired)
81
+ : config.onPaymentRequired
82
+ const context = options?.context
83
+ const timeout = options?.timeout
84
+
85
+ try {
86
+ const result = await client.callTool(
87
+ params,
88
+ undefined,
89
+ timeout !== undefined ? { timeout } : undefined,
90
+ )
91
+
92
+ return {
93
+ ...result,
94
+ receipt: result._meta?.[core_Mcp.receiptMetaKey] as core_Mcp.Receipt | undefined,
95
+ }
96
+ } catch (error) {
97
+ // Check if this is a payment required error
98
+ if (!isPaymentRequiredError(error)) throw error
99
+
100
+ const challenges = (error.data as { challenges?: Challenge.Challenge[] })?.challenges
101
+ if (!challenges?.length) throw error
102
+
103
+ const selected = AcceptPayment.selectChallenge(
104
+ challenges,
105
+ methods,
106
+ paymentPreferences.entries,
107
+ )
108
+ if (!selected) {
109
+ const available = challenges.map((c) => `${c.method}.${c.intent}`).join(', ')
110
+ const installed = methods.map((m) => `${m.name}.${m.intent}`).join(', ')
111
+ throw new Error(
112
+ `No compatible payment method. Server offers: ${available}. Client has: ${installed}`,
113
+ { cause: error },
69
114
  )
115
+ }
70
116
 
71
- return {
72
- ...result,
73
- receipt: result._meta?.[core_Mcp.receiptMetaKey] as core_Mcp.Receipt | undefined,
74
- }
75
- } catch (error) {
76
- // Check if this is a payment required error
77
- if (!isPaymentRequiredError(error)) throw error
78
-
79
- const challenges = (error.data as { challenges?: Challenge.Challenge[] })?.challenges
80
- if (!challenges?.length) throw error
81
-
82
- const selected = AcceptPayment.selectChallenge(
83
- challenges,
84
- methods,
85
- paymentPreferences.entries,
86
- )
87
- if (!selected) {
88
- const available = challenges.map((c) => `${c.method}.${c.intent}`).join(', ')
89
- const installed = methods.map((m) => `${m.name}.${m.intent}`).join(', ')
90
- throw new Error(
91
- `No compatible payment method. Server offers: ${available}. Client has: ${installed}`,
92
- { cause: error },
93
- )
94
- }
95
-
96
- const credential = await createCredential(selected.challenge, {
97
- context,
98
- methods,
99
- })
100
- const parsed = Credential.deserialize(credential)
101
-
102
- const retryResult = await client.callTool(
103
- {
104
- ...params,
105
- _meta: {
106
- ...params._meta,
107
- [core_Mcp.credentialMetaKey]: parsed,
108
- },
109
- },
110
- undefined,
111
- timeout !== undefined ? { timeout } : undefined,
112
- )
117
+ if (selected.challenge.expires)
118
+ Expires.assert(selected.challenge.expires, selected.challenge.id)
113
119
 
114
- return {
115
- ...retryResult,
116
- receipt: retryResult._meta?.[core_Mcp.receiptMetaKey] as core_Mcp.Receipt | undefined,
117
- }
120
+ if (onPaymentRequired) {
121
+ const approved = await onPaymentRequired(selected.challenge)
122
+ if (!approved) throw new Error('Payment declined.', { cause: error })
118
123
  }
119
- },
120
- }
124
+
125
+ const credential = await createCredential(selected.challenge, {
126
+ context,
127
+ methods,
128
+ })
129
+ const parsed = Credential.deserialize(credential)
130
+
131
+ const retryResult = await client.callTool(
132
+ {
133
+ ...params,
134
+ _meta: {
135
+ ...params._meta,
136
+ [core_Mcp.credentialMetaKey]: parsed,
137
+ },
138
+ },
139
+ undefined,
140
+ timeout !== undefined ? { timeout } : undefined,
141
+ )
142
+
143
+ return {
144
+ ...retryResult,
145
+ receipt: retryResult._meta?.[core_Mcp.receiptMetaKey] as core_Mcp.Receipt | undefined,
146
+ }
147
+ }
148
+ }) as wrap.McpClient<client, methods>['callTool']
149
+
150
+ return { ...client, callTool } as wrap.McpClient<client, methods>
121
151
  }
122
152
 
123
153
  /** Union of all context types from all methods that have context schemas. */
@@ -133,6 +163,8 @@ export declare namespace wrap {
133
163
  type Config<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = {
134
164
  /** Array of methods to use. */
135
165
  methods: methods
166
+ /** Optional approval hook called before creating a payment credential. */
167
+ onPaymentRequired?: OnPaymentRequired
136
168
  }
137
169
 
138
170
  type McpClient<
@@ -140,14 +172,14 @@ export declare namespace wrap {
140
172
  methods extends readonly AnyClient[] = readonly AnyClient[],
141
173
  > = Omit<client, 'callTool'> & {
142
174
  /** Call a tool with automatic payment handling. */
143
- callTool: (
144
- params: {
145
- name: string
146
- arguments?: Record<string, unknown>
147
- _meta?: Record<string, unknown>
148
- },
149
- options?: CallToolOptions<methods>,
150
- ) => Promise<CallToolResult>
175
+ callTool: {
176
+ (params: CallToolParameters, options?: CallToolOptions<methods>): Promise<CallToolResult>
177
+ (
178
+ onPaymentRequired: OnPaymentRequired | null | undefined,
179
+ params: CallToolParameters,
180
+ options?: CallToolOptions<methods>,
181
+ ): Promise<CallToolResult>
182
+ }
151
183
  }
152
184
 
153
185
  type CallToolOptions<methods extends readonly AnyClient[] = readonly AnyClient[]> = {
@@ -0,0 +1,131 @@
1
+ import type { Client } from '@modelcontextprotocol/sdk/client/index.js'
2
+ import { McpError } from '@modelcontextprotocol/sdk/types.js'
3
+ import { Challenge, Credential, Mcp as core_Mcp, Method } from 'mppx'
4
+ import { Methods } from 'mppx/tempo'
5
+ import { describe, expect, test, vi } from 'vp/test'
6
+
7
+ import * as McpClient from './McpClient.js'
8
+
9
+ describe('MCP client payment approval', () => {
10
+ test('calls an approval hook before creating a credential', async () => {
11
+ const challenge = Challenge.from({
12
+ id: 'approval-test',
13
+ intent: 'charge',
14
+ method: 'tempo',
15
+ realm: 'api.example.com',
16
+ request: {},
17
+ })
18
+ const calls: unknown[] = []
19
+ const client = {
20
+ async callTool(params: unknown) {
21
+ calls.push(params)
22
+ if (calls.length === 1)
23
+ throw new McpError(core_Mcp.paymentRequiredCode, 'Payment Required', {
24
+ challenges: [challenge],
25
+ httpStatus: 402,
26
+ })
27
+ return {
28
+ _meta: {
29
+ [core_Mcp.receiptMetaKey]: {
30
+ method: 'tempo',
31
+ reference: 'test',
32
+ status: 'success',
33
+ timestamp: new Date().toISOString(),
34
+ },
35
+ },
36
+ content: [{ type: 'text', text: 'ok' }],
37
+ }
38
+ },
39
+ }
40
+ const createCredential = vi.fn(async ({ challenge }: { challenge: Challenge.Challenge }) =>
41
+ Credential.serialize({
42
+ challenge,
43
+ payload: { signature: '0xsignature', type: 'transaction' },
44
+ }),
45
+ )
46
+ const onPaymentRequired = vi.fn(() => true)
47
+ const mcp = McpClient.wrap(client as unknown as Pick<Client, 'callTool'>, {
48
+ methods: [Method.toClient(Methods.charge, { createCredential })],
49
+ })
50
+
51
+ const result = await mcp.callTool(onPaymentRequired, { name: 'paid_tool', arguments: {} })
52
+
53
+ expect(result.content).toEqual([{ type: 'text', text: 'ok' }])
54
+ expect(onPaymentRequired).toHaveBeenCalledWith(challenge)
55
+ expect(createCredential).toHaveBeenCalledOnce()
56
+ expect(calls).toHaveLength(2)
57
+ })
58
+
59
+ test('does not create a credential when approval is denied', async () => {
60
+ const challenge = Challenge.from({
61
+ id: 'denied-test',
62
+ intent: 'charge',
63
+ method: 'tempo',
64
+ realm: 'api.example.com',
65
+ request: {},
66
+ })
67
+ const client = {
68
+ async callTool() {
69
+ throw new McpError(core_Mcp.paymentRequiredCode, 'Payment Required', {
70
+ challenges: [challenge],
71
+ httpStatus: 402,
72
+ })
73
+ },
74
+ }
75
+ const createCredential = vi.fn(async ({ challenge }: { challenge: Challenge.Challenge }) =>
76
+ Credential.serialize({
77
+ challenge,
78
+ payload: { signature: '0xsignature', type: 'transaction' },
79
+ }),
80
+ )
81
+ const mcp = McpClient.wrap(client as unknown as Pick<Client, 'callTool'>, {
82
+ methods: [Method.toClient(Methods.charge, { createCredential })],
83
+ })
84
+
85
+ await expect(mcp.callTool(() => false, { name: 'paid_tool' })).rejects.toThrow(
86
+ 'Payment declined.',
87
+ )
88
+ expect(createCredential).not.toHaveBeenCalled()
89
+ })
90
+
91
+ test('allows null to bypass a config approval hook', async () => {
92
+ const challenge = Challenge.from({
93
+ id: 'null-bypass-test',
94
+ intent: 'charge',
95
+ method: 'tempo',
96
+ realm: 'api.example.com',
97
+ request: {},
98
+ })
99
+ let calls = 0
100
+ const client = {
101
+ async callTool() {
102
+ calls += 1
103
+ if (calls === 1)
104
+ throw new McpError(core_Mcp.paymentRequiredCode, 'Payment Required', {
105
+ challenges: [challenge],
106
+ httpStatus: 402,
107
+ })
108
+ return {
109
+ content: [{ type: 'text', text: 'ok' }],
110
+ }
111
+ },
112
+ }
113
+ const createCredential = vi.fn(async ({ challenge }: { challenge: Challenge.Challenge }) =>
114
+ Credential.serialize({
115
+ challenge,
116
+ payload: { signature: '0xsignature', type: 'transaction' },
117
+ }),
118
+ )
119
+ const onPaymentRequired = vi.fn(() => false)
120
+ const mcp = McpClient.wrap(client as unknown as Pick<Client, 'callTool'>, {
121
+ methods: [Method.toClient(Methods.charge, { createCredential })],
122
+ onPaymentRequired,
123
+ })
124
+
125
+ await expect(mcp.callTool(null, { name: 'paid_tool' })).resolves.toMatchObject({
126
+ content: [{ type: 'text', text: 'ok' }],
127
+ })
128
+ expect(onPaymentRequired).not.toHaveBeenCalled()
129
+ expect(createCredential).toHaveBeenCalledOnce()
130
+ })
131
+ })