mppx 0.5.14 → 0.5.17

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 (72) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/Method.d.ts +5 -2
  3. package/dist/Method.d.ts.map +1 -1
  4. package/dist/Method.js.map +1 -1
  5. package/dist/cli/cli.d.ts.map +1 -1
  6. package/dist/cli/cli.js +30 -1
  7. package/dist/cli/cli.js.map +1 -1
  8. package/dist/mcp-sdk/server/Transport.d.ts.map +1 -1
  9. package/dist/mcp-sdk/server/Transport.js +8 -2
  10. package/dist/mcp-sdk/server/Transport.js.map +1 -1
  11. package/dist/server/Mppx.d.ts.map +1 -1
  12. package/dist/server/Mppx.js +17 -10
  13. package/dist/server/Mppx.js.map +1 -1
  14. package/dist/server/Request.js +5 -1
  15. package/dist/server/Request.js.map +1 -1
  16. package/dist/server/Transport.d.ts.map +1 -1
  17. package/dist/server/Transport.js +4 -0
  18. package/dist/server/Transport.js.map +1 -1
  19. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  20. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  21. package/dist/stripe/server/internal/html.gen.js +1 -1
  22. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  23. package/dist/tempo/Methods.d.ts.map +1 -1
  24. package/dist/tempo/Methods.js +4 -2
  25. package/dist/tempo/Methods.js.map +1 -1
  26. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  27. package/dist/tempo/client/SessionManager.js +20 -10
  28. package/dist/tempo/client/SessionManager.js.map +1 -1
  29. package/dist/tempo/internal/fee-payer.d.ts +4 -1
  30. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  31. package/dist/tempo/internal/fee-payer.js +92 -21
  32. package/dist/tempo/internal/fee-payer.js.map +1 -1
  33. package/dist/tempo/server/Session.d.ts.map +1 -1
  34. package/dist/tempo/server/Session.js +43 -20
  35. package/dist/tempo/server/Session.js.map +1 -1
  36. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  37. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  38. package/dist/tempo/server/internal/html.gen.js +1 -1
  39. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  40. package/dist/tempo/server/internal/transport.d.ts +0 -7
  41. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  42. package/dist/tempo/server/internal/transport.js +84 -13
  43. package/dist/tempo/server/internal/transport.js.map +1 -1
  44. package/package.json +3 -3
  45. package/src/Method.ts +5 -2
  46. package/src/cli/cli.test.ts +21 -0
  47. package/src/cli/cli.ts +33 -1
  48. package/src/internal/changeset.test.ts +106 -0
  49. package/src/mcp-sdk/client/McpClient.integration.test.ts +634 -0
  50. package/src/mcp-sdk/server/Transport.test.ts +1 -0
  51. package/src/mcp-sdk/server/Transport.ts +10 -2
  52. package/src/proxy/Proxy.test.ts +149 -1
  53. package/src/server/Mppx.test.ts +120 -0
  54. package/src/server/Mppx.ts +27 -11
  55. package/src/server/Request.test.ts +46 -1
  56. package/src/server/Request.ts +6 -1
  57. package/src/server/Transport.test.ts +2 -0
  58. package/src/server/Transport.ts +4 -0
  59. package/src/stripe/server/internal/html/package.json +1 -1
  60. package/src/stripe/server/internal/html.gen.ts +1 -1
  61. package/src/tempo/Methods.test.ts +13 -0
  62. package/src/tempo/Methods.ts +23 -16
  63. package/src/tempo/client/SessionManager.ts +32 -9
  64. package/src/tempo/internal/fee-payer.test.ts +40 -4
  65. package/src/tempo/internal/fee-payer.ts +105 -21
  66. package/src/tempo/server/Session.test.ts +760 -2
  67. package/src/tempo/server/Session.ts +59 -17
  68. package/src/tempo/server/internal/html/package.json +1 -1
  69. package/src/tempo/server/internal/html.gen.ts +1 -1
  70. package/src/tempo/server/internal/transport.test.ts +321 -10
  71. package/src/tempo/server/internal/transport.ts +101 -14
  72. package/src/viem/Client.test.ts +52 -1
@@ -1 +1 @@
1
- {"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,w91bAAw91b,CAAA"}
1
+ {"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,27icAA27ic,CAAA"}
@@ -1,10 +1,3 @@
1
- /**
2
- * Tempo-specific SSE transport that wraps the base HTTP transport
3
- * with metering logic (context capture from verified credentials, per-token
4
- * charging via Sse.serve).
5
- *
6
- * @internal
7
- */
8
1
  import * as Transport from '../../../server/Transport.js';
9
2
  import * as ChannelStore from '../../session/ChannelStore.js';
10
3
  import * as Sse_core from '../../session/Sse.js';
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/tempo/server/internal/transport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,SAAS,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAA;AAC7D,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAA;AAGhD,mDAAmD;AACnD,MAAM,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG;IAAE,KAAK,EAAE,YAAY,CAAC,YAAY,CAAA;CAAE,GAAG,GAAG,CAmHpF;AAED,MAAM,CAAC,OAAO,WAAW,GAAG,CAAC;IAC3B,KAAK,OAAO,GAAG;QACb;;;;;;;;;WASG;QACH,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;QAC1B,sDAAsD;QACtD,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KACrC,CAAA;CACF;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7E,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,QAAQ,CAwBX"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/tempo/server/internal/transport.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,SAAS,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAA;AAC7D,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAA;AAGhD,mDAAmD;AACnD,MAAM,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG;IAAE,KAAK,EAAE,YAAY,CAAC,YAAY,CAAA;CAAE,GAAG,GAAG,CAsKpF;AAED,MAAM,CAAC,OAAO,WAAW,GAAG,CAAC;IAC3B,KAAK,OAAO,GAAG;QACb;;;;;;;;;WASG;QACH,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;QAC1B,sDAAsD;QACtD,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KACrC,CAAA;CACF;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7E,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,QAAQ,CAwBX"}
@@ -5,6 +5,8 @@
5
5
  *
6
6
  * @internal
7
7
  */
8
+ import * as Challenge from '../../../Challenge.js';
9
+ import * as Errors from '../../../Errors.js';
8
10
  import * as Transport from '../../../server/Transport.js';
9
11
  import * as ChannelStore from '../../session/ChannelStore.js';
10
12
  import * as Sse_core from '../../session/Sse.js';
@@ -33,6 +35,7 @@ export function sse(options) {
33
35
  name: 'sse',
34
36
  captureRequest(request) {
35
37
  return (base.captureRequest?.(request) ?? {
38
+ hasBody: request.body !== null,
36
39
  headers: new Headers(request.headers),
37
40
  method: request.method,
38
41
  url: new URL(request.url),
@@ -52,6 +55,9 @@ export function sse(options) {
52
55
  throw new Error('No SSE context available');
53
56
  const channelId = payload.channelId;
54
57
  const tickCost = BigInt(verifiedCredential.challenge.request.amount);
58
+ const unitType = typeof verifiedCredential.challenge.request.unitType === 'string'
59
+ ? verifiedCredential.challenge.request.unitType
60
+ : undefined;
55
61
  // Auto-detect upstream SSE responses and parse them into an
56
62
  // AsyncIterable so they flow through the metered pipeline.
57
63
  // This lets proxy consumers simply pass `result.withReceipt(upstreamRes)`
@@ -63,9 +69,7 @@ export function sse(options) {
63
69
  // Pass async generator functions directly so Sse.serve gives them
64
70
  // a SessionController for manual charge(). Pass raw AsyncIterables
65
71
  // as-is so Sse.serve auto-charges per yielded value.
66
- const generate = isAsyncGeneratorFunction(resolved)
67
- ? resolved
68
- : resolved;
72
+ const generate = resolveMeteredGenerate(resolved, unitType);
69
73
  const stream = Sse_core.serve({
70
74
  store,
71
75
  channelId,
@@ -85,22 +89,60 @@ export function sse(options) {
85
89
  response: response,
86
90
  challengeId: verifiedChallengeId,
87
91
  });
92
+ if (!shouldChargePlainResponse(input, payload)) {
93
+ return baseResponse;
94
+ }
95
+ const currentReceipt = receipt;
96
+ const available = BigInt(currentReceipt.acceptedCumulative) - BigInt(currentReceipt.spent);
97
+ if (available < tickCost) {
98
+ const error = new Errors.InsufficientBalanceError({
99
+ reason: `requested ${tickCost}, available ${available}`,
100
+ });
101
+ return new Response(JSON.stringify(error.toProblemDetails(verifiedCredential.challenge.id)), {
102
+ status: error.status,
103
+ headers: {
104
+ 'WWW-Authenticate': Challenge.serialize(verifiedCredential.challenge),
105
+ 'Cache-Control': 'no-store',
106
+ 'Content-Type': 'application/problem+json',
107
+ },
108
+ });
109
+ }
110
+ const chargedReceipt = {
111
+ ...currentReceipt,
112
+ spent: (BigInt(currentReceipt.spent) + tickCost).toString(),
113
+ units: (currentReceipt.units ?? 0) + 1,
114
+ };
115
+ const chargedResponse = base.respondReceipt({
116
+ credential: verifiedCredential,
117
+ envelope,
118
+ input,
119
+ receipt: chargedReceipt,
120
+ response: response,
121
+ challengeId: verifiedChallengeId,
122
+ });
88
123
  // Non-SSE response (e.g. upstream returned JSON instead of event-stream).
89
124
  // Need to deduct tickCost so request isn't free.
90
- // Null-body statuses (e.g. 204 from management actions) cannot carry a
91
- // response body per Fetch/HTTP semantics.
92
- if (isNullBodyStatus(baseResponse.status)) {
93
- return baseResponse;
125
+ // For null-body statuses, the request shape determines whether the
126
+ // response is management (no charge) or plain content (charge one tick).
127
+ if (isNullBodyStatus(chargedResponse.status)) {
128
+ void ChannelStore.deductFromChannel(store, channelId, tickCost);
129
+ return chargedResponse;
94
130
  }
95
131
  const stream = new ReadableStream({
96
132
  async start(controller) {
97
133
  // deduction completes before consumer reads
98
- await ChannelStore.deductFromChannel(store, channelId, tickCost);
99
- if (!baseResponse.body) {
134
+ const result = await ChannelStore.deductFromChannel(store, channelId, tickCost);
135
+ if (!result.ok) {
136
+ controller.error(new Errors.InsufficientBalanceError({
137
+ reason: `requested ${tickCost}, available ${result.channel.highestVoucherAmount - result.channel.spent}`,
138
+ }));
139
+ return;
140
+ }
141
+ if (!chargedResponse.body) {
100
142
  controller.close();
101
143
  return;
102
144
  }
103
- const reader = baseResponse.body.getReader();
145
+ const reader = chargedResponse.body.getReader();
104
146
  try {
105
147
  while (true) {
106
148
  const { done, value } = await reader.read();
@@ -116,9 +158,9 @@ export function sse(options) {
116
158
  },
117
159
  });
118
160
  return new Response(stream, {
119
- status: baseResponse.status,
120
- statusText: baseResponse.statusText,
121
- headers: baseResponse.headers,
161
+ status: chargedResponse.status,
162
+ statusText: chargedResponse.statusText,
163
+ headers: chargedResponse.headers,
122
164
  });
123
165
  },
124
166
  });
@@ -158,7 +200,36 @@ function isAsyncGeneratorFunction(value) {
158
200
  function isAsyncIterable(value) {
159
201
  return value !== null && typeof value === 'object' && Symbol.asyncIterator in value;
160
202
  }
203
+ function resolveMeteredGenerate(value, unitType) {
204
+ if (isAsyncGeneratorFunction(value))
205
+ return value;
206
+ if (unitType !== 'request')
207
+ return value;
208
+ const iterable = value;
209
+ return async function* chargeOnce(stream) {
210
+ let charged = false;
211
+ for await (const chunk of iterable) {
212
+ if (!charged) {
213
+ await stream.charge();
214
+ charged = true;
215
+ }
216
+ yield chunk;
217
+ }
218
+ };
219
+ }
161
220
  function isNullBodyStatus(status) {
162
221
  return [101, 204, 205, 304].includes(status);
163
222
  }
223
+ function shouldChargePlainResponse(input, payload) {
224
+ if (payload.action === 'close' || payload.action === 'topUp')
225
+ return false;
226
+ if (input.method !== 'POST')
227
+ return true;
228
+ const contentLength = input.headers.get('content-length');
229
+ if (contentLength !== null && contentLength !== '0')
230
+ return true;
231
+ if (input.headers.has('transfer-encoding'))
232
+ return true;
233
+ return false;
234
+ }
164
235
  //# sourceMappingURL=transport.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/transport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAClD,OAAO,KAAK,MAAM,MAAM,oBAAoB,CAAA;AAC5C,OAAO,KAAK,SAAS,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAA;AAC7D,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAA;AAMhD;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG,CAAC,OAA2D;IAC7E,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IAEzC,oEAAoE;IACpE,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE;QAClB,IAAI,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,KAAK,CAAA;QAC/B,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QACpD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,EAAE,CAAA;IAEJ,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,CAAA;IAC7B,OAAO,SAAS,CAAC,IAAI,CAAgE;QACnF,IAAI,EAAE,KAAK;QAEX,cAAc,CAAC,OAAO;YACpB,OAAO,CACL,IAAI,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,IAAI;gBAChC,OAAO,EAAE,OAAO,CAAC,IAAI,KAAK,IAAI;gBAC9B,OAAO,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;gBACrC,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,GAAG,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC;aAC1B,CACF,CAAA;QACH,CAAC;QAED,aAAa,CAAC,OAAO;YACnB,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QACpC,CAAC;QAED,gBAAgB,CAAC,OAAO;YACtB,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAa,CAAA;QACnD,CAAC;QAED,cAAc,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE;YAC5E,MAAM,kBAAkB,GAAG,QAAQ,EAAE,UAAU,IAAI,UAAU,CAAA;YAC7D,MAAM,mBAAmB,GAAG,QAAQ,EAAE,SAAS,CAAC,EAAE,IAAI,WAAW,CAAA;YACjE,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAA4C,CAAA;YAC/E,IAAI,CAAC,OAAO,CAAC,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;YACnE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;YACnC,MAAM,QAAQ,GAAG,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,MAAgB,CAAC,CAAA;YAC9E,MAAM,QAAQ,GACZ,OAAO,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBAC/D,CAAC,CAAC,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ;gBAC/C,CAAC,CAAC,SAAS,CAAA;YAEf,4DAA4D;YAC5D,2DAA2D;YAC3D,0EAA0E;YAC1E,4CAA4C;YAC5C,MAAM,QAAQ,GACZ,QAAQ,YAAY,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,IAAI;gBAC/E,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACjE,CAAC,CAAC,QAAQ,CAAA;YAEd,IAAI,wBAAwB,CAAC,QAAQ,CAAC,IAAI,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpE,kEAAkE;gBAClE,mEAAmE;gBACnE,qDAAqD;gBACrD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;gBAC3D,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC;oBAC5B,KAAK;oBACL,SAAS;oBACT,WAAW,EAAE,mBAAmB;oBAChC,QAAQ;oBACR,cAAc,EAAE,eAAe;oBAC/B,QAAQ;oBACR,MAAM,EAAE,KAAK,CAAC,MAAM;iBACrB,CAAC,CAAA;gBACF,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC;YAED,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC;gBACvC,UAAU,EAAE,kBAAkB;gBAC9B,QAAQ;gBACR,KAAK;gBACL,OAAO;gBACP,QAAQ,EAAE,QAAoB;gBAC9B,WAAW,EAAE,mBAAmB;aACjC,CAAC,CAAA;YAEF,IAAI,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC/C,OAAO,YAAY,CAAA;YACrB,CAAC;YAED,MAAM,cAAc,GAAG,OAAyB,CAAA;YAChD,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;YAC1F,IAAI,SAAS,GAAG,QAAQ,EAAE,CAAC;gBACzB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,wBAAwB,CAAC;oBAChD,MAAM,EAAE,aAAa,QAAQ,eAAe,SAAS,EAAE;iBACxD,CAAC,CAAA;gBACF,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EACvE;oBACE,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,OAAO,EAAE;wBACP,kBAAkB,EAAE,SAAS,CAAC,SAAS,CAAC,kBAAkB,CAAC,SAAS,CAAC;wBACrE,eAAe,EAAE,UAAU;wBAC3B,cAAc,EAAE,0BAA0B;qBAC3C;iBACF,CACF,CAAA;YACH,CAAC;YAED,MAAM,cAAc,GAAmB;gBACrC,GAAG,cAAc;gBACjB,KAAK,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,QAAQ,EAAE;gBAC3D,KAAK,EAAE,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC;aACvC,CAAA;YACD,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC;gBAC1C,UAAU,EAAE,kBAAkB;gBAC9B,QAAQ;gBACR,KAAK;gBACL,OAAO,EAAE,cAAc;gBACvB,QAAQ,EAAE,QAAoB;gBAC9B,WAAW,EAAE,mBAAmB;aACjC,CAAC,CAAA;YAEF,0EAA0E;YAC1E,iDAAiD;YACjD,mEAAmE;YACnE,yEAAyE;YACzE,IAAI,gBAAgB,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC7C,KAAK,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;gBAC/D,OAAO,eAAe,CAAA;YACxB,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,cAAc,CAAa;gBAC5C,KAAK,CAAC,KAAK,CAAC,UAAU;oBACpB,4CAA4C;oBAC5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;oBAC/E,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;wBACf,UAAU,CAAC,KAAK,CACd,IAAI,MAAM,CAAC,wBAAwB,CAAC;4BAClC,MAAM,EAAE,aAAa,QAAQ,eAC3B,MAAM,CAAC,OAAO,CAAC,oBAAoB,GAAG,MAAM,CAAC,OAAO,CAAC,KACvD,EAAE;yBACH,CAAC,CACH,CAAA;wBACD,OAAM;oBACR,CAAC;oBACD,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;wBAC1B,UAAU,CAAC,KAAK,EAAE,CAAA;wBAClB,OAAM;oBACR,CAAC;oBACD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,CAAA;oBAC/C,IAAI,CAAC;wBACH,OAAO,IAAI,EAAE,CAAC;4BACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;4BAC3C,IAAI,IAAI;gCAAE,MAAK;4BACf,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;wBAC3B,CAAC;oBACH,CAAC;4BAAS,CAAC;wBACT,MAAM,CAAC,WAAW,EAAE,CAAA;wBACpB,UAAU,CAAC,KAAK,EAAE,CAAA;oBACpB,CAAC;gBACH,CAAC;aACF,CAAC,CAAA;YACF,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE;gBAC1B,MAAM,EAAE,eAAe,CAAC,MAAM;gBAC9B,UAAU,EAAE,eAAe,CAAC,UAAU;gBACtC,OAAO,EAAE,eAAe,CAAC,OAAO;aACjC,CAAC,CAAA;QACJ,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAoBD,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,OAG5B;IACC,MAAM,QAAQ,GACZ,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAgB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAA;IAChG,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,MAAM,GAAG,IAAI,cAAc,CAAa;QAC5C,KAAK,CAAC,KAAK,CAAC,UAAU;YACpB,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;oBACnC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,yBAAyB,KAAK,MAAM,CAAC,CAAC,CAAA;gBAC1E,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACrB,CAAC;oBAAS,CAAC;gBACT,UAAU,CAAC,KAAK,EAAE,CAAA;YACpB,CAAC;QACH,CAAC;KACF,CAAC,CAAA;IACF,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE;QAC1B,OAAO,EAAE;YACP,cAAc,EAAE,kCAAkC;YAClD,eAAe,EAAE,wBAAwB;YACzC,UAAU,EAAE,YAAY;SACzB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,wBAAwB,CAC/B,KAAc;IAEd,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,KAAK,CAAA;IAC7C,OAAO,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,wBAAwB,CAAA;AAC7D,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,IAAK,KAAgB,CAAA;AACjG,CAAC;AAED,SAAS,sBAAsB,CAC7B,KAA8E,EAC9E,QAA4B;IAE5B,IAAI,wBAAwB,CAAC,KAAK,CAAC;QAAE,OAAO,KAA2C,CAAA;IACvF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,KAA8B,CAAA;IAEjE,MAAM,QAAQ,GAAG,KAA8B,CAAA;IAC/C,OAAO,KAAK,SAAS,CAAC,CAAC,UAAU,CAAC,MAAM;QACtC,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,MAAM,CAAC,MAAM,EAAE,CAAA;gBACrB,OAAO,GAAG,IAAI,CAAA;YAChB,CAAC;YACD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc;IACtC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAC9C,CAAC;AAED,SAAS,yBAAyB,CAChC,KAAc,EACd,OAA0C;IAE1C,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,KAAK,CAAA;IAC1E,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAExC,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;IACzD,IAAI,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAAE,OAAO,IAAI,CAAA;IAEvD,OAAO,KAAK,CAAA;AACd,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.5.14",
4
+ "version": "0.5.17",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "files": [
@@ -123,8 +123,8 @@
123
123
  }
124
124
  },
125
125
  "dependencies": {
126
- "incur": "^0.3.23",
127
- "ox": "0.14.10",
126
+ "incur": "^0.3.25",
127
+ "ox": "0.14.15",
128
128
  "zod": "^4.3.6"
129
129
  },
130
130
  "repository": {
package/src/Method.ts CHANGED
@@ -69,6 +69,7 @@ export type AnyClient = Client<any, any>
69
69
 
70
70
  /** Transport-captured request metadata used as the authoritative request snapshot. */
71
71
  export type CapturedRequest = {
72
+ readonly hasBody?: boolean | undefined
72
73
  readonly headers: Headers
73
74
  readonly method: string
74
75
  readonly url: URL
@@ -167,8 +168,10 @@ export type VerifyFn<method extends Method> = (
167
168
  * response or generator. If it returns `undefined`, the server handler
168
169
  * is expected to serve content via `withReceipt(response)`.
169
170
  *
170
- * **HTTP-only.** The `input` parameter is a `Request` object; MCP transports
171
- * do not invoke this hook.
171
+ * Use `parameters.envelope?.capturedRequest` for any transport-agnostic
172
+ * authorization, billing, or routing decisions. The raw `input` should only
173
+ * be used for transport-specific response shaping (for example, HTTP content
174
+ * negotiation).
172
175
  */
173
176
  export type RespondFn<method extends Method> = (
174
177
  parameters: RespondContext<method>,
@@ -1118,6 +1118,27 @@ describe.skipIf(!!process.env.CI)('account', () => {
1118
1118
  expect(result.stdout).toContain('not found')
1119
1119
  })
1120
1120
 
1121
+ // --- account export ---
1122
+
1123
+ test('export: prints private key for existing account', () => {
1124
+ const name = `${prefix}_export`
1125
+ createAccount(name)
1126
+ const result = accountRun(['account', 'export', '--account', name])
1127
+ expect(result.status).toBe(0)
1128
+
1129
+ const privateKey = result.stdout.match(/0x[0-9a-fA-F]{64}/)?.[0]
1130
+ expect(privateKey).toBeDefined()
1131
+
1132
+ const view = accountRun(['account', 'view', '--account', name])
1133
+ expect(view.stdout).toContain(privateKeyToAccount(privateKey as `0x${string}`).address)
1134
+ })
1135
+
1136
+ test('export: missing account exits non-zero', () => {
1137
+ const result = accountRun(['account', 'export', '--account', `${prefix}_missing_export`])
1138
+ expect(result.status).not.toBe(0)
1139
+ expect(result.stdout).toContain('not found')
1140
+ })
1141
+
1121
1142
  // --- account list ---
1122
1143
 
1123
1144
  test('list: includes created accounts', () => {
package/src/cli/cli.ts CHANGED
@@ -497,7 +497,7 @@ const cli = Cli.create('mppx', {
497
497
  })
498
498
 
499
499
  const account = Cli.create('account', {
500
- description: 'Manage accounts (create, default, delete, fund, list, view)',
500
+ description: 'Manage accounts (create, default, delete, export, fund, list, view)',
501
501
  })
502
502
  .command('create', {
503
503
  description: 'Create new account',
@@ -713,6 +713,38 @@ const account = Cli.create('account', {
713
713
  }
714
714
  },
715
715
  })
716
+ .command('export', {
717
+ description: 'Export the private key for a local account',
718
+ options: z.object({
719
+ account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
720
+ }),
721
+ alias: { account: 'a' },
722
+ async run(c) {
723
+ const accountName = resolveAccountName(c.options.account)
724
+
725
+ if (isTempoAccount(accountName)) {
726
+ return c.error({
727
+ code: 'UNSUPPORTED_ACCOUNT',
728
+ message: `Account "${accountName}" is managed by Tempo wallet and does not expose a private key via mppx.`,
729
+ exitCode: 2,
730
+ })
731
+ }
732
+
733
+ const key = await createKeychain(accountName).get()
734
+ if (!key) {
735
+ if (c.options.account)
736
+ return c.error({
737
+ code: 'ACCOUNT_NOT_FOUND',
738
+ message: `Account "${accountName}" not found.`,
739
+ exitCode: 69,
740
+ })
741
+ else
742
+ return c.error({ code: 'ACCOUNT_NOT_FOUND', message: 'No account found.', exitCode: 69 })
743
+ }
744
+
745
+ console.log(key)
746
+ },
747
+ })
716
748
  .command('view', {
717
749
  description: 'View account address',
718
750
  options: z.object({
@@ -0,0 +1,106 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+
4
+ import { describe, expect, test } from 'vp/test'
5
+
6
+ const changesetDir = path.resolve(import.meta.dirname, '../../.changeset')
7
+ const validPackages = new Set(['mppx'])
8
+ const validBumpTypes = new Set(['major', 'minor', 'patch', 'none'])
9
+
10
+ function getChangesetFiles() {
11
+ return fs
12
+ .readdirSync(changesetDir)
13
+ .filter((f) => f.endsWith('.md') && f !== 'README.md' && !f.startsWith('.'))
14
+ }
15
+
16
+ function parseFrontmatter(content: string) {
17
+ const match = content.match(/^\s*---\n([^]*?)\n\s*---/)
18
+ if (!match) return undefined
19
+ return match[1]
20
+ }
21
+
22
+ function parseReleases(frontmatter: string) {
23
+ return [...frontmatter.matchAll(/^['"]?([^'":\n]+?)['"]?\s*:\s*(.+)$/gm)].map(
24
+ ([, name, type]) => ({ name: name!.trim(), type: type!.trim().replace(/['"]/g, '') }),
25
+ )
26
+ }
27
+
28
+ describe('changesets', () => {
29
+ const files = getChangesetFiles()
30
+
31
+ test('all changeset files have valid frontmatter fences', () => {
32
+ for (const file of files) {
33
+ const content = fs.readFileSync(path.join(changesetDir, file), 'utf-8')
34
+ expect(content, `${file}: must start with "---"`).toMatch(/^---\n/)
35
+ const closingIdx = content.indexOf('---', 3)
36
+ expect(closingIdx, `${file}: missing closing "---" fence`).toBeGreaterThan(3)
37
+
38
+ // closing fence must not have trailing non-whitespace (parser chokes)
39
+ const afterClosing = content.slice(closingIdx + 3).split('\n')[0]!
40
+ expect(afterClosing.trim(), `${file}: trailing characters after closing "---"`).toBe('')
41
+ }
42
+ })
43
+
44
+ test('all changesets reference valid packages', () => {
45
+ for (const file of files) {
46
+ const content = fs.readFileSync(path.join(changesetDir, file), 'utf-8')
47
+ const frontmatter = parseFrontmatter(content)
48
+ if (!frontmatter) continue
49
+ for (const { name } of parseReleases(frontmatter)) {
50
+ expect(
51
+ validPackages.has(name),
52
+ `${file}: unknown package "${name}" (valid: ${[...validPackages].join(', ')})`,
53
+ ).toBe(true)
54
+ }
55
+ }
56
+ })
57
+
58
+ test('all changesets use valid bump types', () => {
59
+ for (const file of files) {
60
+ const content = fs.readFileSync(path.join(changesetDir, file), 'utf-8')
61
+ const frontmatter = parseFrontmatter(content)
62
+ if (!frontmatter) continue
63
+ for (const { name, type } of parseReleases(frontmatter)) {
64
+ expect(
65
+ validBumpTypes.has(type),
66
+ `${file}: invalid bump type "${type}" for "${name}" (valid: major, minor, patch, none)`,
67
+ ).toBe(true)
68
+ }
69
+ }
70
+ })
71
+
72
+ test('no changeset uses major bump (pre-v1 policy)', () => {
73
+ for (const file of files) {
74
+ const content = fs.readFileSync(path.join(changesetDir, file), 'utf-8')
75
+ const frontmatter = parseFrontmatter(content)
76
+ if (!frontmatter) continue
77
+ for (const { name, type } of parseReleases(frontmatter)) {
78
+ expect(type, `${file}: "${name}" uses major bump — pre-v1 policy forbids this`).not.toBe(
79
+ 'major',
80
+ )
81
+ }
82
+ }
83
+ })
84
+
85
+ test('all changesets have a non-empty description', () => {
86
+ for (const file of files) {
87
+ const content = fs.readFileSync(path.join(changesetDir, file), 'utf-8')
88
+ const match = content.match(/^\s*---[^]*?---\s*(.*)$/s)
89
+ expect(match?.[1]?.trim(), `${file}: changeset has an empty description`).toBeTruthy()
90
+ }
91
+ })
92
+
93
+ test('no duplicate package entries in frontmatter', () => {
94
+ for (const file of files) {
95
+ const content = fs.readFileSync(path.join(changesetDir, file), 'utf-8')
96
+ const frontmatter = parseFrontmatter(content)
97
+ if (!frontmatter) continue
98
+ const releases = parseReleases(frontmatter)
99
+ const seen = new Set<string>()
100
+ for (const { name } of releases) {
101
+ expect(seen.has(name), `${file}: duplicate entry for "${name}"`).toBe(false)
102
+ seen.add(name)
103
+ }
104
+ }
105
+ })
106
+ })