mppx 0.15.0 → 0.17.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 (101) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/Challenge.d.ts +6 -0
  3. package/dist/Challenge.js +12 -0
  4. package/dist/Method.d.ts +2 -0
  5. package/dist/cli/cli.js +143 -131
  6. package/dist/cli/plugins/index.js +11 -11
  7. package/dist/cli/validate/payment.d.ts +12 -0
  8. package/dist/cli/validate/payment.js +137 -14
  9. package/dist/client/Mppx.d.ts +13 -3
  10. package/dist/client/Mppx.js +47 -22
  11. package/dist/client/internal/Fetch.d.ts +11 -0
  12. package/dist/client/internal/Fetch.js +46 -8
  13. package/dist/client/internal/ProofRealm.d.ts +21 -0
  14. package/dist/client/internal/ProofRealm.js +55 -0
  15. package/dist/client/internal/protocols/Mpp.js +4 -1
  16. package/dist/{attestation → extensions/identity}/Constants.d.ts +2 -2
  17. package/dist/{attestation → extensions/identity}/Constants.js +2 -2
  18. package/dist/extensions/identity/Types.d.ts +21 -0
  19. package/dist/extensions/identity/index.d.ts +7 -0
  20. package/dist/extensions/identity/index.js +6 -0
  21. package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.d.ts +3 -3
  22. package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.js +10 -10
  23. package/dist/extensions/identity/internal/SigningContext.d.ts +6 -0
  24. package/dist/{attestation → extensions/identity}/internal/SigningContext.js +3 -3
  25. package/dist/extensions/identity/internal/Verification.d.ts +9 -0
  26. package/dist/extensions/identity/internal/Verification.js +10 -0
  27. package/dist/{tap → extensions/identity/tap}/Constants.js +1 -1
  28. package/dist/extensions/identity/tap/client/index.d.ts +32 -0
  29. package/dist/{tap → extensions/identity/tap}/client/index.js +13 -9
  30. package/dist/extensions/identity/tap/server/index.d.ts +27 -0
  31. package/dist/{tap → extensions/identity/tap}/server/index.js +12 -4
  32. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.js +1 -1
  33. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.d.ts +6 -5
  34. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.js +5 -5
  35. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.d.ts +6 -5
  36. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.js +6 -4
  37. package/dist/identity/Client.d.ts +34 -0
  38. package/dist/identity/Client.js +133 -0
  39. package/dist/identity/Server.d.ts +59 -0
  40. package/dist/identity/Server.js +194 -0
  41. package/dist/identity/Types.d.ts +112 -0
  42. package/dist/identity/Types.js +2 -0
  43. package/dist/{attestation → identity}/index.d.ts +2 -2
  44. package/dist/{attestation → identity}/index.js +1 -2
  45. package/dist/identity/internal/SigningContext.d.ts +6 -0
  46. package/dist/identity/internal/SigningContext.js +11 -0
  47. package/dist/integrations/x402/Evm.d.ts +1 -0
  48. package/dist/internal/version.d.ts +2 -2
  49. package/dist/internal/version.js +1 -1
  50. package/dist/mcp/client/McpClient.d.ts +5 -0
  51. package/dist/mcp/client/McpClient.js +2 -0
  52. package/dist/proxy/Proxy.js +10 -5
  53. package/dist/proxy/Service.d.ts +42 -4
  54. package/dist/proxy/Service.js +21 -5
  55. package/dist/server/Mppx.d.ts +27 -8
  56. package/dist/server/Mppx.js +164 -54
  57. package/dist/server/internal/challengeMeta.d.ts +6 -0
  58. package/dist/server/internal/challengeMeta.js +17 -1
  59. package/dist/stripe/server/internal/html.gen.js +1 -1
  60. package/dist/tempo/client/Charge.d.ts +11 -0
  61. package/dist/tempo/client/Charge.js +30 -1
  62. package/dist/tempo/internal/charge.js +2 -1
  63. package/dist/tempo/internal/defaults.d.ts +9 -0
  64. package/dist/tempo/internal/defaults.js +9 -0
  65. package/dist/tempo/internal/fee-payer.d.ts +0 -2
  66. package/dist/tempo/internal/fee-payer.js +2 -6
  67. package/dist/tempo/server/Charge.js +12 -1
  68. package/dist/tempo/server/Subscription.js +0 -1
  69. package/dist/tempo/server/internal/html.gen.js +1 -1
  70. package/dist/tempo/session/client/Runtime.d.ts +2 -0
  71. package/dist/tempo/session/client/Session.js +1 -1
  72. package/dist/tempo/session/client/SessionManager.d.ts +6 -0
  73. package/dist/tempo/session/client/SessionManager.js +14 -7
  74. package/dist/tempo/session/client/Transports.d.ts +12 -3
  75. package/dist/tempo/session/client/Transports.js +37 -11
  76. package/dist/tempo/session/precompile/Chain.js +0 -3
  77. package/dist/tempo/subscription/Store.js +10 -9
  78. package/package.json +15 -11
  79. package/dist/attestation/Client.d.ts +0 -17
  80. package/dist/attestation/Client.js +0 -28
  81. package/dist/attestation/Server.d.ts +0 -4
  82. package/dist/attestation/Server.js +0 -8
  83. package/dist/attestation/Types.d.ts +0 -76
  84. package/dist/attestation/internal/SigningContext.d.ts +0 -4
  85. package/dist/tap/client/index.d.ts +0 -26
  86. package/dist/tap/server/index.d.ts +0 -20
  87. /package/dist/{attestation → extensions/identity}/Types.js +0 -0
  88. /package/dist/{tap → extensions/identity/tap}/Constants.d.ts +0 -0
  89. /package/dist/{tap → extensions/identity/tap}/Types.d.ts +0 -0
  90. /package/dist/{tap → extensions/identity/tap}/Types.js +0 -0
  91. /package/dist/{tap → extensions/identity/tap}/index.d.ts +0 -0
  92. /package/dist/{tap → extensions/identity/tap}/index.js +0 -0
  93. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.d.ts +0 -0
  94. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.d.ts +0 -0
  95. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.js +0 -0
  96. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.d.ts +0 -0
  97. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.js +0 -0
  98. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.d.ts +0 -0
  99. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.js +0 -0
  100. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.d.ts +0 -0
  101. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.js +0 -0
@@ -69,6 +69,11 @@ export declare namespace wrap {
69
69
  paymentPreferences?: AcceptPayment.Config<FlattenMethods<methods>> | undefined;
70
70
  /** Array of methods to use. Accepts individual clients or tuples (e.g. from `tempo()`). */
71
71
  methods: methods;
72
+ /**
73
+ * Realms allowed to request zero-amount Tempo proofs. An MCP server has no origin that could
74
+ * own a realm, so these proofs are refused unless their realm is listed here.
75
+ */
76
+ trustedTempoProofRealms?: readonly string[] | undefined;
72
77
  };
73
78
  type McpClient<client extends Pick<Client, 'callTool'> = Pick<Client, 'callTool'>, methods extends Methods = DefaultMethods> = Omit<client, 'callTool'> & {
74
79
  /** Call a tool with automatic payment handling. Preserves the MCP SDK signature. */
@@ -1,4 +1,5 @@
1
1
  import * as Challenge from '../../Challenge.js';
2
+ import * as ProofRealm from '../../client/internal/ProofRealm.js';
2
3
  import * as Credential from '../../Credential.js';
3
4
  import * as Expires from '../../Expires.js';
4
5
  import * as AcceptPayment from '../../internal/AcceptPayment.js';
@@ -111,6 +112,7 @@ function createPaymentAwareCallTool(callTool, config) {
111
112
  }
112
113
  if (selected.challenge.expires)
113
114
  Expires.assert(selected.challenge.expires, selected.challenge.id);
115
+ ProofRealm.assertWithoutOrigin(selected.challenge, config.trustedTempoProofRealms);
114
116
  if (call.onPaymentRequired) {
115
117
  const approved = await call.onPaymentRequired(selected.challenge);
116
118
  if (!approved)
@@ -91,22 +91,23 @@ export function create(config) {
91
91
  // so same-path paid routes can coexist without sharing credentials.
92
92
  Route.matchPath(service.routes, upstreamPath,
93
93
  // skip free routes (e.g. `'GET /foo/bar': true`)
94
- (endpoint) => endpoint !== true && matchesPaymentBinding(endpoint, fallbackBinding))
94
+ (endpoint) => Service.getHandler(endpoint) !== undefined &&
95
+ matchesPaymentBinding(endpoint, fallbackBinding))
95
96
  : null;
96
97
  const matched = exactMatch ?? fallbackMatch;
97
98
  if (!matched)
98
99
  return new Response('Not Found', { status: 404 });
99
100
  const endpoint = matched.value;
100
101
  const ctx = { request, service, upstreamPath };
101
- if (endpoint === true)
102
+ const handler = Service.getHandler(endpoint);
103
+ if (!handler)
102
104
  return proxyUpstream({
103
105
  request,
104
106
  service,
105
- ctx,
107
+ ctx: { ...ctx, ...Service.getOptions(endpoint) },
106
108
  proxy,
107
109
  onUpstreamError: service.onUpstreamError ?? config.onUpstreamError,
108
110
  });
109
- const handler = typeof endpoint === 'function' ? endpoint : endpoint.pay;
110
111
  const scope = getConfiguredScope(handler) ??
111
112
  deriveRouteScope({
112
113
  basePath: config.basePath,
@@ -276,6 +277,10 @@ function buildDiscoveryRoutes(services) {
276
277
  method: hasMethod ? tokens[0] : 'GET',
277
278
  path: `/${service.id}${path}`,
278
279
  payment: endpoint ? Service.paymentOf(endpoint) : null,
280
+ ...(typeof endpoint === 'object' && endpoint?.requestBody
281
+ ? { requestBody: endpoint.requestBody }
282
+ : {}),
283
+ ...(typeof endpoint === 'object' && endpoint?.summary ? { summary: endpoint.summary } : {}),
279
284
  };
280
285
  }));
281
286
  }
@@ -328,7 +333,7 @@ function getPaymentBinding(request) {
328
333
  return null;
329
334
  }
330
335
  function matchesPaymentBinding(endpoint, binding) {
331
- if (endpoint === true)
336
+ if (!Service.getHandler(endpoint))
332
337
  return false;
333
338
  if (!binding)
334
339
  return true;
@@ -30,13 +30,45 @@ export type Docs = {
30
30
  * An endpoint definition.
31
31
  *
32
32
  * - `IntentHandler` — payment required, calls the handler to issue a 402 challenge or verify payment.
33
- * - `{ pay, options }` — payment required with per-endpoint config overrides.
33
+ * - `EndpointConfig` — `{ pay, options, summary, requestBody }` (paid) or `{ free: true, options, summary, requestBody }` (free).
34
34
  * - `true` — free passthrough, no payment required, rewriteRequest is applied.
35
35
  */
36
- export type Endpoint = IntentHandler | {
36
+ export type Endpoint = IntentHandler | EndpointConfig | true;
37
+ /**
38
+ * An endpoint definition with per-endpoint config overrides and discovery metadata.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * routes: {
43
+ * 'POST /v1/search': {
44
+ * pay: mppx.charge({ amount: '0.01' }),
45
+ * summary: 'Search the web',
46
+ * requestBody: {
47
+ * content: { 'application/json': { schema: { type: 'object' } } },
48
+ * },
49
+ * },
50
+ * 'GET /v1/status': { free: true, summary: 'Service status' },
51
+ * }
52
+ * ```
53
+ */
54
+ export type EndpointConfig = EndpointMetadata & ({
55
+ /** Payment handler. */
37
56
  pay: IntentHandler;
38
- options: EndpointOptions;
39
- } | true;
57
+ free?: undefined;
58
+ } | {
59
+ /** Free passthrough route, declared explicitly so a missing handler never means free. */
60
+ free: true;
61
+ pay?: undefined;
62
+ });
63
+ /** Per-endpoint config and discovery metadata shared by paid and free endpoint objects. */
64
+ export type EndpointMetadata = {
65
+ /** Per-endpoint config overrides passed to request hooks via `ctx`. */
66
+ options?: EndpointOptions | undefined;
67
+ /** OpenAPI request body object emitted in `/openapi.json`. */
68
+ requestBody?: Record<string, unknown> | undefined;
69
+ /** Short summary emitted in `/openapi.json`. */
70
+ summary?: string | undefined;
71
+ };
40
72
  /** Map of `"METHOD /pattern"` keys to endpoint definitions. */
41
73
  export type EndpointMap<routes extends string = string> = Partial<Record<routes, Endpoint>> & Record<string & {}, Endpoint>;
42
74
  /** Per-endpoint configuration overrides (e.g. `{ apiKey: 'sk-...' }`). */
@@ -149,6 +181,12 @@ export declare function toLlmsTxt(services: Service[], options?: {
149
181
  }): string;
150
182
  /** Extracts per-endpoint options from an endpoint definition. */
151
183
  export declare function getOptions(endpoint: Endpoint): EndpointOptions | undefined;
184
+ /**
185
+ * Returns the payment handler of an endpoint, or `undefined` for a route declared free (`true` or
186
+ * `{ free: true }`). Throws for anything else, such as `{ pay: undefined }`, so a missing handler
187
+ * fails closed instead of proxying for free with the service's credentials.
188
+ */
189
+ export declare function getHandler(endpoint: Endpoint): IntentHandler | undefined;
152
190
  /** Derives discovery payment metadata from an explicitly priced endpoint handler. */
153
191
  export declare function paymentOf(endpoint: Endpoint): Record<string, unknown> | null;
154
192
  //# sourceMappingURL=Service.d.ts.map
@@ -86,16 +86,32 @@ export function toLlmsTxt(services, options) {
86
86
  }
87
87
  /** Extracts per-endpoint options from an endpoint definition. */
88
88
  export function getOptions(endpoint) {
89
- if (typeof endpoint === 'object' && endpoint !== null && 'options' in endpoint)
89
+ if (typeof endpoint === 'object' && endpoint !== null)
90
90
  return endpoint.options;
91
91
  return undefined;
92
92
  }
93
+ /**
94
+ * Returns the payment handler of an endpoint, or `undefined` for a route declared free (`true` or
95
+ * `{ free: true }`). Throws for anything else, such as `{ pay: undefined }`, so a missing handler
96
+ * fails closed instead of proxying for free with the service's credentials.
97
+ */
98
+ export function getHandler(endpoint) {
99
+ if (typeof endpoint === 'function')
100
+ return endpoint;
101
+ if (endpoint === true)
102
+ return undefined;
103
+ if (typeof endpoint === 'object' && endpoint !== null) {
104
+ if (typeof endpoint.pay === 'function')
105
+ return endpoint.pay;
106
+ if (endpoint.free === true && endpoint.pay === undefined)
107
+ return undefined;
108
+ }
109
+ throw new Error('Invalid proxy endpoint: expected a payment handler, `true` or `{ free: true }`.');
110
+ }
93
111
  /** Derives discovery payment metadata from an explicitly priced endpoint handler. */
94
112
  export function paymentOf(endpoint) {
95
- if (endpoint === true)
96
- return null;
97
- const handler = typeof endpoint === 'function' ? endpoint : endpoint.pay;
98
- if (!('_internal' in handler))
113
+ const handler = getHandler(endpoint);
114
+ if (!handler || !('_internal' in handler))
99
115
  return null;
100
116
  const metadata = handler._internal;
101
117
  if (!metadata)
@@ -1,8 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import type * as Attestation from '../attestation/Types.js';
3
2
  import * as Challenge from '../Challenge.js';
4
3
  import * as Credential from '../Credential.js';
5
4
  import * as Errors from '../Errors.js';
5
+ import type * as Identity from '../identity/Types.js';
6
6
  import type { DeepReadonly, MaybePromise } from '../internal/types.js';
7
7
  import * as Method from '../Method.js';
8
8
  import type * as Receipt from '../Receipt.js';
@@ -277,8 +277,25 @@ type IntentHandlers<methods extends readonly Method.AnyServer[], transport exten
277
277
  intent: intent;
278
278
  }>, transport>;
279
279
  };
280
+ /**
281
+ * Route identity option. HTTP routes accept identity verifiers, all of which must be valid;
282
+ * use `Identity.anyOf` to accept one of several protocols. Other transports reject it.
283
+ */
284
+ type RouteIdentity<transport extends Transport.AnyTransport> = Transport.InputOf<transport> extends Request ? Transport.ChallengeOutputOf<transport> extends Response ? {
285
+ identity?: readonly Identity.Verifier[] | undefined;
286
+ } : {
287
+ identity?: never;
288
+ } : {
289
+ identity?: never;
290
+ };
280
291
  /** Handler for an intent with multiple methods - takes the intersection of all methods' options. */
281
- type MultiMethodFn<methods extends Method.AnyServer, transport extends Transport.AnyTransport> = (options: UnionToIntersection<MultiMethodFnDistribute<methods>>) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>;
292
+ type MultiMethodFn<methods extends Method.AnyServer, transport extends Transport.AnyTransport> = (options: MultiMethodOptions<methods> & RouteIdentity<transport>) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>;
293
+ /**
294
+ * Intersected options of every method for an intent. When a method's options can't be inferred,
295
+ * the intersection is `unknown`; fall back to any object (interfaces included) so `RouteIdentity`
296
+ * doesn't turn it into an object that rejects every option but `identity`.
297
+ */
298
+ type MultiMethodOptions<methods extends Method.AnyServer> = UnionToIntersection<MultiMethodFnDistribute<methods>> extends infer options ? unknown extends options ? Record<string, any> : options : never;
282
299
  type MultiMethodFnDistribute<M> = M extends Method.Server<infer method, infer defaults, any, any, any> ? MethodFn.Options<method, NonNullable<defaults>> : never;
283
300
  type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
284
301
  /** Nested handlers: `mppx.tempo.charge(...)`, grouped by method name then intent. */
@@ -334,8 +351,6 @@ type ChallengeFn<method extends Method.Method, defaults extends Record<string, u
334
351
  export declare function create<const methods extends Methods, const transport extends Transport.AnyTransport = Transport.Http>(config: create.Config<methods, transport>): Mppx<methods, transport>;
335
352
  export declare namespace create {
336
353
  type Config<methods extends Methods = Methods, transport extends Transport.AnyTransport = Transport.Http> = {
337
- /** Request-attestation verifiers required before handling an HTTP payment request. */
338
- attestation?: transport extends Transport.Http ? Attestation.VerifierMap | undefined : never;
339
354
  /** Array of configured methods. @example [tempo()] */
340
355
  methods: methods;
341
356
  /** Uses `Payment-Authorization` for Payment credentials so `Authorization` remains available for application authentication. */
@@ -357,7 +372,7 @@ export declare class MissingReceiptResponseError extends Error {
357
372
  }
358
373
  /** Returns true when an error is the typed `withReceipt()` no-response sentinel. */
359
374
  export declare function isMissingReceiptResponseError(error: unknown): error is MissingReceiptResponseError;
360
- export type MethodFn<method extends Method.Method, transport extends Transport.AnyTransport, defaults extends Record<string, unknown>> = (options: MethodFn.Options<method, defaults>) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>;
375
+ export type MethodFn<method extends Method.Method, transport extends Transport.AnyTransport, defaults extends Record<string, unknown>> = (options: MethodFn.Options<method, defaults> & RouteIdentity<transport>) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>;
361
376
  /** @internal */
362
377
  export type AnyMethodFn = (options: any) => (input: any) => Promise<any>;
363
378
  /** @internal */
@@ -379,6 +394,8 @@ declare namespace MethodFn {
379
394
  status: 200;
380
395
  /** Canonical challenge resolved from the selected route's terms, when available. */
381
396
  challenge?: Challenge.Challenge | undefined;
397
+ /** Verified identity values for the route's verifiers. */
398
+ identity?: Identity.Results | undefined;
382
399
  withReceipt: Transport.WithReceipt<transport>;
383
400
  };
384
401
  }
@@ -395,6 +412,8 @@ type ConfiguredHandler<transport extends Transport.AnyTransport = Transport.Http
395
412
  scope?: string | undefined;
396
413
  _canonicalRequest: Record<string, unknown>;
397
414
  _stableBinding?: Method.StableBindingFn<Method.Method> | undefined;
415
+ /** The offer's payment-stage identity verifiers, if any. */
416
+ _paymentIdentity?: readonly Identity.Verifier[] | undefined;
398
417
  _transport: Transport.AnyTransport;
399
418
  };
400
419
  };
@@ -409,19 +428,19 @@ type ComposedHandler<transport extends Transport.AnyTransport = Transport.Http>
409
428
  type ComposeEntry<methods extends readonly Method.AnyServer[]> = {
410
429
  [i in keyof methods]: readonly [
411
430
  methods[i],
412
- MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>
431
+ MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>> & RouteIdentity<Transport.Http>
413
432
  ];
414
433
  }[number] | {
415
434
  [i in keyof methods]: readonly [
416
435
  `${methods[i]['name']}/${methods[i]['intent']}`,
417
- MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>
436
+ MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>> & RouteIdentity<Transport.Http>
418
437
  ];
419
438
  }[number] | {
420
439
  [i in keyof methods]: readonly [
421
440
  MethodFn<methods[i], any, any> & {
422
441
  _method: methods[i];
423
442
  },
424
- MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>
443
+ MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>> & RouteIdentity<Transport.Http>
425
444
  ];
426
445
  }[number];
427
446
  export declare function compose(...args: readonly unknown[]): ComposedHandler;