mppx 0.8.19 → 0.9.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.
- package/CHANGELOG.md +16 -0
- package/README.md +0 -6
- package/dist/Challenge.d.ts +7 -0
- package/dist/Challenge.js +32 -6
- package/dist/Constants.d.ts +1 -0
- package/dist/Constants.js +1 -0
- package/dist/Credential.d.ts +11 -5
- package/dist/Credential.js +8 -8
- package/dist/Html.js +1 -1
- package/dist/cli/THIRD-PARTY-LICENSES.txt +1 -1
- package/dist/cli/cli.d.ts +2 -2
- package/dist/cli/cli.js +134 -134
- package/dist/cli/plugins/index.js +13 -13
- package/dist/cli/sessions/Manager.js +4 -6
- package/dist/cli/sessions/commands.d.ts +1 -1
- package/dist/cli/sessions/store.js +13 -19
- package/dist/cli/validate/index.d.ts +2 -2
- package/dist/client/Mppx.d.ts +39 -2
- package/dist/client/Mppx.js +102 -46
- package/dist/client/Transport.js +1 -1
- package/dist/client/internal/protocols/Mpp.d.ts +1 -1
- package/dist/client/internal/protocols/Mpp.js +5 -3
- package/dist/client/internal/protocols/Protocol.d.ts +3 -1
- package/dist/client/internal/protocols/Shared.js +5 -1
- package/dist/integrations/x402/Evm.d.ts +29 -0
- package/dist/integrations/x402/Evm.js +150 -0
- package/dist/integrations/x402/Negotiator.d.ts +36 -0
- package/dist/integrations/x402/Negotiator.js +165 -0
- package/dist/mcp/internal/Credential.d.ts +9 -0
- package/dist/mcp/internal/Credential.js +16 -0
- package/dist/mcp/server/Transport.js +2 -4
- package/dist/middlewares/express.js +5 -12
- package/dist/middlewares/internal/express.d.ts +8 -0
- package/dist/middlewares/internal/express.js +24 -0
- package/dist/middlewares/internal/node.d.ts +10 -0
- package/dist/middlewares/internal/node.js +34 -0
- package/dist/proxy/Proxy.js +15 -9
- package/dist/server/Mppx.d.ts +2 -0
- package/dist/server/Mppx.js +17 -14
- package/dist/server/Transport.d.ts +8 -2
- package/dist/server/Transport.js +6 -3
- package/dist/server/internal/SecretKey.d.ts +3 -0
- package/dist/server/internal/SecretKey.js +9 -0
- package/dist/server/internal/html/serviceWorker.client.d.ts +1 -1
- package/dist/server/internal/html/serviceWorker.client.js +2 -2
- package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
- package/dist/server/internal/html/serviceWorker.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/Methods.d.ts +0 -10
- package/dist/tempo/Methods.js +3 -12
- package/dist/tempo/client/Charge.d.ts +0 -3
- package/dist/tempo/client/Charge.js +3 -13
- package/dist/tempo/client/Methods.d.ts +0 -10
- package/dist/tempo/internal/account.d.ts +15 -6
- package/dist/tempo/internal/account.js +17 -26
- package/dist/tempo/internal/defaults.d.ts +0 -11
- package/dist/tempo/internal/defaults.js +0 -11
- package/dist/tempo/internal/fee-payer.d.ts +21 -3
- package/dist/tempo/internal/fee-payer.js +10 -19
- package/dist/tempo/internal/remote-fee-payer.d.ts +12 -0
- package/dist/tempo/internal/remote-fee-payer.js +17 -0
- package/dist/tempo/legacy/client/Session.d.ts +0 -7
- package/dist/tempo/server/Charge.d.ts +0 -3
- package/dist/tempo/server/Charge.js +26 -68
- package/dist/tempo/server/Methods.d.ts +4 -18
- package/dist/tempo/server/Methods.js +4 -8
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +0 -14
- package/dist/tempo/session/client/ChannelOps.js +0 -22
- package/dist/tempo/session/client/ChannelStore.d.ts +5 -0
- package/dist/tempo/session/client/ChannelStore.js +2 -2
- package/dist/tempo/session/client/CredentialState.d.ts +2 -12
- package/dist/tempo/session/client/CredentialState.js +75 -73
- package/dist/tempo/session/client/Session.d.ts +2 -9
- package/dist/tempo/session/client/Session.js +71 -176
- package/dist/tempo/session/client/SessionManager.js +9 -28
- package/dist/tempo/session/precompile/Chain.d.ts +0 -4
- package/dist/tempo/session/precompile/Chain.js +1 -29
- package/dist/tempo/session/precompile/Protocol.d.ts +0 -13
- package/dist/tempo/session/precompile/Voucher.d.ts +1 -3
- package/dist/tempo/session/precompile/Voucher.js +12 -6
- package/dist/tempo/session/server/ChannelStore.d.ts +0 -6
- package/dist/tempo/session/server/ChannelStore.js +4 -11
- package/dist/tempo/session/server/CredentialVerification.d.ts +7 -9
- package/dist/tempo/session/server/CredentialVerification.js +118 -184
- package/dist/tempo/session/server/RequestState.d.ts +0 -17
- package/dist/tempo/session/server/RequestState.js +15 -59
- package/dist/tempo/session/server/Session.d.ts +0 -9
- package/dist/tempo/session/server/Session.js +8 -37
- package/dist/tempo/session/server/Settlement.d.ts +0 -7
- package/dist/tempo/session/server/Settlement.js +16 -50
- package/dist/viem/Client.d.ts +7 -2
- package/dist/viem/Client.js +17 -5
- package/dist/x402/Types.d.ts +2 -0
- package/dist/x402/Types.js +2 -0
- package/dist/x402/mcp.d.ts +20 -0
- package/dist/x402/mcp.js +153 -0
- package/dist/x402/middlewares/express.d.ts +23 -0
- package/dist/x402/middlewares/express.js +49 -0
- package/dist/x402/middlewares/hono.d.ts +13 -0
- package/dist/x402/middlewares/hono.js +36 -0
- package/dist/x402/middlewares/next.d.ts +17 -0
- package/dist/x402/middlewares/next.js +58 -0
- package/package.json +41 -1
- package/dist/tempo/internal/machine-token-charge.d.ts +0 -51
- package/dist/tempo/internal/machine-token-charge.js +0 -113
- package/dist/tempo/internal/machine-token-session.d.ts +0 -88
- package/dist/tempo/internal/machine-token-session.js +0 -173
- package/dist/tempo/internal/signature-envelope.d.ts +0 -14
- package/dist/tempo/internal/signature-envelope.js +0 -28
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { decodePaymentRequiredHeader } from '@x402/core/http';
|
|
2
|
+
import { getFacilitatorResponseError, x402HTTPResourceServer } from '@x402/core/server';
|
|
3
|
+
import * as Constants from '../../Constants.js';
|
|
4
|
+
import * as Credential from '../../Credential.js';
|
|
5
|
+
import * as SecretKey from '../../server/internal/SecretKey.js';
|
|
6
|
+
import * as Types from '../../x402/Types.js';
|
|
7
|
+
import * as Evm from './Evm.js';
|
|
8
|
+
/** Creates the native MPP side of an x402 compatibility integration. */
|
|
9
|
+
export function create(config) {
|
|
10
|
+
SecretKey.assert(config.secretKey);
|
|
11
|
+
const httpServer = new x402HTTPResourceServer(config.server, config.routes);
|
|
12
|
+
let initialization;
|
|
13
|
+
async function initialize() {
|
|
14
|
+
initialization ??= httpServer.initialize().catch((error) => {
|
|
15
|
+
initialization = undefined;
|
|
16
|
+
throw error;
|
|
17
|
+
});
|
|
18
|
+
await initialization;
|
|
19
|
+
}
|
|
20
|
+
async function negotiate(request, context) {
|
|
21
|
+
if (!httpServer.requiresPayment(context))
|
|
22
|
+
return { status: 'unprotected' };
|
|
23
|
+
if (hasMppCredential(request) && getX402Credential(request))
|
|
24
|
+
return {
|
|
25
|
+
response: Response.json({ error: 'Send either an MPP credential or an x402 payment signature, not both.' }, { status: 400 }),
|
|
26
|
+
status: 'handled',
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
await initialize();
|
|
30
|
+
if (getX402Credential(request))
|
|
31
|
+
return { status: 'x402' };
|
|
32
|
+
const result = await httpServer.processHTTPRequest(withoutX402Credential(context), config.paywallConfig);
|
|
33
|
+
if (result.type === 'no-payment-required')
|
|
34
|
+
return { status: 'unprotected' };
|
|
35
|
+
if (result.type !== 'payment-error')
|
|
36
|
+
throw new Error(`Expected x402 payment response, received ${result.type}.`);
|
|
37
|
+
const x402Response = responseFromInstructions(result.response);
|
|
38
|
+
const paymentRequired = getPaymentRequired(result);
|
|
39
|
+
const handler = paymentRequired
|
|
40
|
+
? Evm.createHandler({ config, context, paymentRequired })
|
|
41
|
+
: undefined;
|
|
42
|
+
if (!handler)
|
|
43
|
+
return { response: x402Response, status: 'handled' };
|
|
44
|
+
const { payment, skipHandlerResponse } = await handler(request);
|
|
45
|
+
if (payment.status !== 402)
|
|
46
|
+
return skipHandlerResponse
|
|
47
|
+
? { response: payment.withReceipt(skipHandlerResponse), status: 'handled' }
|
|
48
|
+
: { status: 'paid', withReceipt: payment.withReceipt };
|
|
49
|
+
const response = mergeChallenge(payment.challenge, x402Response);
|
|
50
|
+
return { response, status: 'handled' };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const facilitatorError = getFacilitatorResponseError(error);
|
|
54
|
+
if (!facilitatorError)
|
|
55
|
+
throw error;
|
|
56
|
+
return {
|
|
57
|
+
response: Response.json({ error: facilitatorError.message }, { status: 502 }),
|
|
58
|
+
status: 'handled',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { httpServer, negotiate };
|
|
63
|
+
}
|
|
64
|
+
/** Reads a current or legacy x402 payment credential. */
|
|
65
|
+
export function getX402Credential(request) {
|
|
66
|
+
return (request.headers.get(Types.paymentSignatureHeader) ??
|
|
67
|
+
request.headers.get(Types.legacyPaymentSignatureHeader) ??
|
|
68
|
+
undefined);
|
|
69
|
+
}
|
|
70
|
+
/** Converts a Fetch request to the context consumed by x402 core. */
|
|
71
|
+
export function createContext(request, body) {
|
|
72
|
+
const paymentHeader = getX402Credential(request);
|
|
73
|
+
return {
|
|
74
|
+
adapter: new FetchAdapter(request, body),
|
|
75
|
+
method: request.method,
|
|
76
|
+
path: new URL(request.url).pathname,
|
|
77
|
+
...(paymentHeader ? { paymentHeader } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function hasMppCredential(request) {
|
|
81
|
+
const authorization = request.headers.get(Constants.Headers.authorization);
|
|
82
|
+
return authorization ? Credential.extractPaymentScheme(authorization) !== null : false;
|
|
83
|
+
}
|
|
84
|
+
function withoutX402Credential(context) {
|
|
85
|
+
const { paymentHeader: _, ...withoutCredential } = context;
|
|
86
|
+
return withoutCredential;
|
|
87
|
+
}
|
|
88
|
+
function getPaymentRequired(result) {
|
|
89
|
+
if (result.type !== 'payment-error')
|
|
90
|
+
return undefined;
|
|
91
|
+
const target = Types.paymentRequiredHeader.toLowerCase();
|
|
92
|
+
const encoded = Object.entries(result.response.headers).find(([name]) => name.toLowerCase() === target)?.[1];
|
|
93
|
+
return encoded ? decodePaymentRequiredHeader(encoded) : undefined;
|
|
94
|
+
}
|
|
95
|
+
function responseFromInstructions(instructions) {
|
|
96
|
+
const headers = new Headers(instructions.headers);
|
|
97
|
+
let body = null;
|
|
98
|
+
if (instructions.body !== undefined) {
|
|
99
|
+
if (instructions.isHtml || typeof instructions.body === 'string')
|
|
100
|
+
body = String(instructions.body);
|
|
101
|
+
else {
|
|
102
|
+
body = JSON.stringify(instructions.body);
|
|
103
|
+
if (!headers.has('Content-Type'))
|
|
104
|
+
headers.set('Content-Type', 'application/json');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return new Response(body, { headers, status: instructions.status });
|
|
108
|
+
}
|
|
109
|
+
function mergeChallenge(mpp, x402) {
|
|
110
|
+
const headers = new Headers(x402.headers);
|
|
111
|
+
const challenge = mpp.headers.get('WWW-Authenticate');
|
|
112
|
+
if (challenge)
|
|
113
|
+
headers.append('WWW-Authenticate', challenge);
|
|
114
|
+
headers.set('Cache-Control', 'no-store');
|
|
115
|
+
return new Response(x402.body, {
|
|
116
|
+
headers,
|
|
117
|
+
status: x402.status,
|
|
118
|
+
statusText: x402.statusText,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** Fetch implementation used only by the MCP compatibility wrapper. */
|
|
122
|
+
class FetchAdapter {
|
|
123
|
+
request;
|
|
124
|
+
body;
|
|
125
|
+
constructor(request, body) {
|
|
126
|
+
this.request = request;
|
|
127
|
+
this.body = body;
|
|
128
|
+
}
|
|
129
|
+
getAcceptHeader() {
|
|
130
|
+
return this.request.headers.get('Accept') ?? '';
|
|
131
|
+
}
|
|
132
|
+
getHeader(name) {
|
|
133
|
+
return this.request.headers.get(name) ?? undefined;
|
|
134
|
+
}
|
|
135
|
+
getBody() {
|
|
136
|
+
return this.body;
|
|
137
|
+
}
|
|
138
|
+
getMethod() {
|
|
139
|
+
return this.request.method;
|
|
140
|
+
}
|
|
141
|
+
getPath() {
|
|
142
|
+
return new URL(this.request.url).pathname;
|
|
143
|
+
}
|
|
144
|
+
getQueryParam(name) {
|
|
145
|
+
const values = new URL(this.request.url).searchParams.getAll(name);
|
|
146
|
+
if (values.length === 0)
|
|
147
|
+
return undefined;
|
|
148
|
+
return values.length === 1 ? values[0] : values;
|
|
149
|
+
}
|
|
150
|
+
getQueryParams() {
|
|
151
|
+
const parameters = {};
|
|
152
|
+
for (const key of new URL(this.request.url).searchParams.keys()) {
|
|
153
|
+
const values = new URL(this.request.url).searchParams.getAll(key);
|
|
154
|
+
parameters[key] = values.length === 1 ? values[0] : values;
|
|
155
|
+
}
|
|
156
|
+
return parameters;
|
|
157
|
+
}
|
|
158
|
+
getUrl() {
|
|
159
|
+
return this.request.url;
|
|
160
|
+
}
|
|
161
|
+
getUserAgent() {
|
|
162
|
+
return this.request.headers.get('User-Agent') ?? '';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=Negotiator.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as Credential from '../../Credential.js';
|
|
2
|
+
/** Parsed MCP credential and its canonical HTTP Authorization value. */
|
|
3
|
+
export type Parsed = {
|
|
4
|
+
header: string;
|
|
5
|
+
value: Credential.Credential;
|
|
6
|
+
};
|
|
7
|
+
/** Validates an MCP metadata credential through the canonical wire serializer. */
|
|
8
|
+
export declare function parse(value: unknown): Parsed | undefined;
|
|
9
|
+
//# sourceMappingURL=Credential.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Credential from '../../Credential.js';
|
|
2
|
+
/** Validates an MCP metadata credential through the canonical wire serializer. */
|
|
3
|
+
export function parse(value) {
|
|
4
|
+
if (!value || typeof value !== 'object')
|
|
5
|
+
return undefined;
|
|
6
|
+
if (!('challenge' in value) || !('payload' in value))
|
|
7
|
+
return undefined;
|
|
8
|
+
try {
|
|
9
|
+
const header = Credential.serialize(value);
|
|
10
|
+
return { header, value: Credential.deserialize(header) };
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=Credential.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as core_Mcp from '../../Mcp.js';
|
|
2
2
|
import * as Transport from '../../server/Transport.js';
|
|
3
|
+
import * as McpCredential from '../internal/Credential.js';
|
|
3
4
|
/**
|
|
4
5
|
* MCP SDK transport for server-side payment handling with `@modelcontextprotocol/sdk`.
|
|
5
6
|
*
|
|
@@ -40,10 +41,7 @@ export function mcpSdk() {
|
|
|
40
41
|
};
|
|
41
42
|
},
|
|
42
43
|
getCredential(extra) {
|
|
43
|
-
|
|
44
|
-
if (!credential)
|
|
45
|
-
return null;
|
|
46
|
-
return credential;
|
|
44
|
+
return McpCredential.parse(extra._meta?.[core_Mcp.credentialMetaKey])?.value ?? null;
|
|
47
45
|
},
|
|
48
46
|
async respondChallenge({ challenge, error }) {
|
|
49
47
|
if (!McpErrorClass) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { generate } from '../discovery/OpenApi.js';
|
|
2
2
|
import * as Mppx_core from '../server/Mppx.js';
|
|
3
|
+
import * as ExpressAdapter from './internal/express.js';
|
|
3
4
|
import * as Mppx_internal from './internal/mppx.js';
|
|
4
5
|
export * from '../server/Methods.js';
|
|
5
6
|
export var Mppx;
|
|
@@ -48,17 +49,11 @@ export var Mppx;
|
|
|
48
49
|
*/
|
|
49
50
|
export function payment(intent, options) {
|
|
50
51
|
return async (req, res, next) => {
|
|
51
|
-
const request =
|
|
52
|
-
method: req.method,
|
|
53
|
-
headers: req.headers,
|
|
54
|
-
});
|
|
52
|
+
const request = ExpressAdapter.toRequest(req);
|
|
55
53
|
const result = await intent(options)(request);
|
|
56
54
|
if (result.status === 402) {
|
|
57
55
|
const challenge = result.challenge;
|
|
58
|
-
|
|
59
|
-
for (const [key, value] of challenge.headers)
|
|
60
|
-
res.setHeader(key, value);
|
|
61
|
-
res.send(await challenge.text());
|
|
56
|
+
await ExpressAdapter.sendResponse(res, challenge);
|
|
62
57
|
return;
|
|
63
58
|
}
|
|
64
59
|
const managementResponse = (() => {
|
|
@@ -73,8 +68,7 @@ export function payment(intent, options) {
|
|
|
73
68
|
})();
|
|
74
69
|
if (managementResponse) {
|
|
75
70
|
res.status(managementResponse.status);
|
|
76
|
-
|
|
77
|
-
res.setHeader(key, value);
|
|
71
|
+
ExpressAdapter.copyHeaders(res, managementResponse.headers);
|
|
78
72
|
if (managementResponse.body === null) {
|
|
79
73
|
res.end();
|
|
80
74
|
return;
|
|
@@ -85,8 +79,7 @@ export function payment(intent, options) {
|
|
|
85
79
|
const originalJson = res.json.bind(res);
|
|
86
80
|
res.json = (body) => {
|
|
87
81
|
const wrapped = result.withReceipt(Response.json(body));
|
|
88
|
-
|
|
89
|
-
res.setHeader(key, value);
|
|
82
|
+
ExpressAdapter.copyHeaders(res, wrapped.headers);
|
|
90
83
|
return originalJson(body);
|
|
91
84
|
};
|
|
92
85
|
next();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Request as ExpressRequest, Response as ExpressResponse } from 'express';
|
|
2
|
+
/** Copies Fetch response headers to an Express response. */
|
|
3
|
+
export declare function copyHeaders(response: ExpressResponse, headers: Headers): void;
|
|
4
|
+
/** Sends a Fetch response through Express without changing its status, headers, or bytes. */
|
|
5
|
+
export declare function sendResponse(response: ExpressResponse, fetchResponse: Response): Promise<ExpressResponse<any, Record<string, any>>>;
|
|
6
|
+
/** Converts an Express parsed request into the Fetch request consumed by mppx core. */
|
|
7
|
+
export declare function toRequest(request: ExpressRequest): Request;
|
|
8
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as NodeRequest from './node.js';
|
|
2
|
+
/** Copies Fetch response headers to an Express response. */
|
|
3
|
+
export function copyHeaders(response, headers) {
|
|
4
|
+
for (const [name, value] of headers)
|
|
5
|
+
response.setHeader(name, value);
|
|
6
|
+
}
|
|
7
|
+
/** Sends a Fetch response through Express without changing its status, headers, or bytes. */
|
|
8
|
+
export async function sendResponse(response, fetchResponse) {
|
|
9
|
+
copyHeaders(response, fetchResponse.headers);
|
|
10
|
+
response.status(fetchResponse.status);
|
|
11
|
+
if (fetchResponse.body === null)
|
|
12
|
+
return response.end();
|
|
13
|
+
return response.send(Buffer.from(await fetchResponse.arrayBuffer()));
|
|
14
|
+
}
|
|
15
|
+
/** Converts an Express parsed request into the Fetch request consumed by mppx core. */
|
|
16
|
+
export function toRequest(request) {
|
|
17
|
+
return NodeRequest.toRequest({
|
|
18
|
+
body: request.body,
|
|
19
|
+
headers: request.headers,
|
|
20
|
+
method: request.method,
|
|
21
|
+
url: `${request.protocol}://${request.get('host')}${request.originalUrl}`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Parsed Node framework request fields needed to construct a Fetch request. */
|
|
2
|
+
export type ParsedRequest = {
|
|
3
|
+
body?: unknown;
|
|
4
|
+
headers: Record<string, string | string[] | undefined>;
|
|
5
|
+
method: string;
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
/** Converts a parsed Node framework request into the Fetch request consumed by mppx core. */
|
|
9
|
+
export declare function toRequest(request: ParsedRequest): Request;
|
|
10
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Converts a parsed Node framework request into the Fetch request consumed by mppx core. */
|
|
2
|
+
export function toRequest(request) {
|
|
3
|
+
const headers = new Headers();
|
|
4
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
5
|
+
if (value === undefined)
|
|
6
|
+
continue;
|
|
7
|
+
if (Array.isArray(value))
|
|
8
|
+
for (const entry of value)
|
|
9
|
+
headers.append(name, entry);
|
|
10
|
+
else
|
|
11
|
+
headers.set(name, value);
|
|
12
|
+
}
|
|
13
|
+
const body = request.method === 'GET' || request.method === 'HEAD' || request.body === undefined
|
|
14
|
+
? undefined
|
|
15
|
+
: toBody(request.body);
|
|
16
|
+
return new Request(request.url, {
|
|
17
|
+
...(body === undefined ? {} : { body }),
|
|
18
|
+
headers,
|
|
19
|
+
method: request.method,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Preserves Fetch-compatible bodies and serializes parsed JSON values. */
|
|
23
|
+
function toBody(body) {
|
|
24
|
+
if (typeof body === 'string' ||
|
|
25
|
+
body instanceof ArrayBuffer ||
|
|
26
|
+
ArrayBuffer.isView(body) ||
|
|
27
|
+
body instanceof Blob ||
|
|
28
|
+
body instanceof FormData ||
|
|
29
|
+
body instanceof URLSearchParams ||
|
|
30
|
+
body instanceof ReadableStream)
|
|
31
|
+
return body;
|
|
32
|
+
return JSON.stringify(body);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=node.js.map
|
package/dist/proxy/Proxy.js
CHANGED
|
@@ -310,16 +310,22 @@ function withBasePath(basePath, path) {
|
|
|
310
310
|
return `${trimmed}${path}`;
|
|
311
311
|
}
|
|
312
312
|
function getPaymentBinding(request) {
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
for (const [, value] of request.headers) {
|
|
314
|
+
const payment = Credential.extractPaymentScheme(value);
|
|
315
|
+
if (!payment)
|
|
316
|
+
continue;
|
|
317
|
+
try {
|
|
318
|
+
const credential = Credential.deserialize(payment);
|
|
319
|
+
return {
|
|
320
|
+
intent: credential.challenge.intent,
|
|
321
|
+
method: credential.challenge.method,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// A malformed payment header is handled by the selected route.
|
|
326
|
+
}
|
|
322
327
|
}
|
|
328
|
+
return null;
|
|
323
329
|
}
|
|
324
330
|
function matchesPaymentBinding(endpoint, binding) {
|
|
325
331
|
if (endpoint === true)
|
package/dist/server/Mppx.d.ts
CHANGED
|
@@ -315,6 +315,8 @@ export declare namespace create {
|
|
|
315
315
|
attestation?: transport extends Transport.Http ? Attestation.VerifierMap | undefined : never;
|
|
316
316
|
/** Array of configured methods. @example [tempo()] */
|
|
317
317
|
methods: methods;
|
|
318
|
+
/** Uses `Payment-Authorization` for Payment credentials so `Authorization` remains available for application authentication. */
|
|
319
|
+
requiresAuth?: transport extends Transport.Http ? boolean | undefined : never;
|
|
318
320
|
/** Server realm (e.g., hostname). Resolution order: explicit value > env vars (`MPP_REALM`, `FLY_APP_NAME`, `VERCEL_URL`, etc.) > request URL hostname > `"MPP Payment"`. */
|
|
319
321
|
realm?: string | undefined;
|
|
320
322
|
/** Secret key for HMAC-bound challenge IDs for stateless verification. Must be at least 32 bytes. Auto-detected from `MPP_SECRET_KEY` environment variable. */
|
package/dist/server/Mppx.js
CHANGED
|
@@ -14,11 +14,10 @@ import * as z from '../zod.js';
|
|
|
14
14
|
import * as Html from './internal/html/config.js';
|
|
15
15
|
import { serviceWorker } from './internal/html/serviceWorker.gen.js';
|
|
16
16
|
import * as Scope from './internal/scope.js';
|
|
17
|
+
import * as SecretKey from './internal/SecretKey.js';
|
|
17
18
|
import * as NodeListener from './NodeListener.js';
|
|
18
19
|
import * as Request from './Request.js';
|
|
19
20
|
import * as Transport from './Transport.js';
|
|
20
|
-
const minimumSecretKeyBytes = 32;
|
|
21
|
-
const secretKeyGenerationCommand = 'openssl rand -base64 32';
|
|
22
21
|
const reservedMppxKeyValues = [
|
|
23
22
|
'challenge',
|
|
24
23
|
'compose',
|
|
@@ -52,11 +51,12 @@ export const reservedMppxKeys = new Set(reservedMppxKeyValues);
|
|
|
52
51
|
* ```
|
|
53
52
|
*/
|
|
54
53
|
export function create(config) {
|
|
55
|
-
const { attestation, realm = Env.get('realm'), selectOffers, secretKey = Env.get('secretKey'), transport = Transport.http(), } = config;
|
|
54
|
+
const { attestation, requiresAuth, realm = Env.get('realm'), selectOffers, secretKey = Env.get('secretKey'), transport = Transport.http({ requiresAuth }), } = config;
|
|
55
|
+
const credentialHeader = requiresAuth ? Constants.Headers.paymentAuthorization : undefined;
|
|
56
56
|
if (!secretKey) {
|
|
57
57
|
throw new Error('Missing secret key. Set the MPP_SECRET_KEY environment variable or pass `secretKey` to Mppx.create().');
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
SecretKey.assert(secretKey);
|
|
60
60
|
const methods = config.methods.flat();
|
|
61
61
|
const serverEvents = createServerEventDispatcher();
|
|
62
62
|
const verifyAttestation = attestation
|
|
@@ -83,6 +83,7 @@ export function create(config) {
|
|
|
83
83
|
const fn = createMethodFn({
|
|
84
84
|
...(verifyAttestation && { attest: verifyAttestation }),
|
|
85
85
|
authorize: mi.authorize,
|
|
86
|
+
credentialHeader,
|
|
86
87
|
defaults: mi.defaults,
|
|
87
88
|
method: mi,
|
|
88
89
|
realm,
|
|
@@ -143,6 +144,7 @@ export function create(config) {
|
|
|
143
144
|
if (!challengeHandlers[mi.name])
|
|
144
145
|
challengeHandlers[mi.name] = {};
|
|
145
146
|
challengeHandlers[mi.name][mi.alias ?? mi.intent] = createChallengeFn({
|
|
147
|
+
credentialHeader,
|
|
146
148
|
defaults: mi.defaults,
|
|
147
149
|
method: mi,
|
|
148
150
|
realm,
|
|
@@ -235,6 +237,7 @@ export function create(config) {
|
|
|
235
237
|
credential: parsedCredential,
|
|
236
238
|
defaults: mi.defaults,
|
|
237
239
|
expires: credential.challenge.expires,
|
|
240
|
+
header: credential.challenge.header,
|
|
238
241
|
meta: expectedMeta,
|
|
239
242
|
method: mi,
|
|
240
243
|
realm: expectedRealm,
|
|
@@ -375,15 +378,9 @@ export function create(config) {
|
|
|
375
378
|
...handlers,
|
|
376
379
|
};
|
|
377
380
|
}
|
|
378
|
-
function assertSecretKey(secretKey) {
|
|
379
|
-
const byteLength = new TextEncoder().encode(secretKey).byteLength;
|
|
380
|
-
if (byteLength >= minimumSecretKeyBytes)
|
|
381
|
-
return;
|
|
382
|
-
throw new Error(`Secret key must be at least ${minimumSecretKeyBytes} bytes. Generate one with \`${secretKeyGenerationCommand}\` and set MPP_SECRET_KEY or pass it to Mppx.create().`);
|
|
383
|
-
}
|
|
384
381
|
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
385
382
|
function createMethodFn(parameters) {
|
|
386
|
-
const { attest, authorize, defaults, events, method, preflight, realm, respond, secretKey, broadcast, stableBinding, transport, validate, verify, } = parameters;
|
|
383
|
+
const { attest, authorize, credentialHeader, defaults, events, method, preflight, realm, respond, secretKey, broadcast, stableBinding, transport, validate, verify, } = parameters;
|
|
387
384
|
return (options) => {
|
|
388
385
|
const { description, meta, scope, ...rest } = options;
|
|
389
386
|
const staticMeta = Scope.merge({ meta, scope });
|
|
@@ -491,6 +488,7 @@ function createMethodFn(parameters) {
|
|
|
491
488
|
defaults,
|
|
492
489
|
description,
|
|
493
490
|
expires,
|
|
491
|
+
header: credentialHeader,
|
|
494
492
|
meta: effectiveMeta,
|
|
495
493
|
method,
|
|
496
494
|
realm,
|
|
@@ -505,6 +503,7 @@ function createMethodFn(parameters) {
|
|
|
505
503
|
defaults: defaults ?? {},
|
|
506
504
|
description,
|
|
507
505
|
expires,
|
|
506
|
+
header: credentialHeader,
|
|
508
507
|
meta: effectiveMeta,
|
|
509
508
|
method,
|
|
510
509
|
realm,
|
|
@@ -850,7 +849,7 @@ function createMethodFn(parameters) {
|
|
|
850
849
|
* but returns a Challenge object directly instead of a request handler.
|
|
851
850
|
*/
|
|
852
851
|
function createChallengeFn(parameters) {
|
|
853
|
-
const { defaults, method, realm, secretKey } = parameters;
|
|
852
|
+
const { credentialHeader, defaults, method, realm, secretKey } = parameters;
|
|
854
853
|
return async (options) => {
|
|
855
854
|
const { description, meta, scope, ...rest } = options;
|
|
856
855
|
const effectiveMeta = Scope.merge({ meta, scope });
|
|
@@ -861,6 +860,7 @@ function createChallengeFn(parameters) {
|
|
|
861
860
|
defaults,
|
|
862
861
|
description,
|
|
863
862
|
expires,
|
|
863
|
+
header: credentialHeader,
|
|
864
864
|
meta: effectiveMeta,
|
|
865
865
|
method,
|
|
866
866
|
realm,
|
|
@@ -1163,6 +1163,7 @@ async function resolveRouteChallenge(parameters) {
|
|
|
1163
1163
|
const challenge = Challenge.fromMethod(parameters.method, {
|
|
1164
1164
|
description: parameters.description,
|
|
1165
1165
|
expires: parameters.expires,
|
|
1166
|
+
header: parameters.header,
|
|
1166
1167
|
meta: parameters.meta,
|
|
1167
1168
|
realm: effectiveRealm,
|
|
1168
1169
|
request: request,
|
|
@@ -1178,6 +1179,7 @@ function createFallbackChallenge(parameters) {
|
|
|
1178
1179
|
return Challenge.fromMethod(parameters.method, {
|
|
1179
1180
|
description: parameters.description,
|
|
1180
1181
|
expires: parameters.expires,
|
|
1182
|
+
header: parameters.header,
|
|
1181
1183
|
meta: parameters.meta,
|
|
1182
1184
|
realm: parameters.realm ??
|
|
1183
1185
|
(parameters.capturedRequest
|
|
@@ -1405,8 +1407,9 @@ function composeHandlers(handlers, composeOptions, selectOffers) {
|
|
|
1405
1407
|
// Try to extract a Payment credential to decide whether to dispatch or challenge.
|
|
1406
1408
|
// Only gate on the Payment scheme — other auth schemes (Bearer, Basic, etc.)
|
|
1407
1409
|
// should fall through to the merged-402 path so all offers are presented.
|
|
1408
|
-
const
|
|
1409
|
-
|
|
1410
|
+
const paymentHeader = Array.from(input.headers.values())
|
|
1411
|
+
.map((value) => Credential.extractPaymentScheme(value))
|
|
1412
|
+
.find((value) => value !== null);
|
|
1410
1413
|
if (paymentHeader) {
|
|
1411
1414
|
// Parse the credential to find method+intent for dispatch.
|
|
1412
1415
|
let credential;
|
|
@@ -89,11 +89,17 @@ export declare function from<input = unknown, challengeOutput = unknown, receipt
|
|
|
89
89
|
/**
|
|
90
90
|
* HTTP transport for server-side payment handling.
|
|
91
91
|
*
|
|
92
|
-
* - Reads credentials from the
|
|
92
|
+
* - Reads credentials from the configured payment credential header
|
|
93
93
|
* - Issues challenges via `WWW-Authenticate` header with 402 status
|
|
94
94
|
* - Attaches receipts via `Payment-Receipt` header
|
|
95
95
|
*/
|
|
96
|
-
export declare function http(): Http;
|
|
96
|
+
export declare function http(options?: http.Options): Http;
|
|
97
|
+
export declare namespace http {
|
|
98
|
+
type Options = {
|
|
99
|
+
/** Uses `Payment-Authorization` for Payment credentials so `Authorization` remains available for application authentication. */
|
|
100
|
+
requiresAuth?: boolean | undefined;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
97
103
|
/**
|
|
98
104
|
* MCP transport for server-side payment handling with raw JSON-RPC.
|
|
99
105
|
*
|
package/dist/server/Transport.js
CHANGED
|
@@ -29,11 +29,14 @@ export function from(transport) {
|
|
|
29
29
|
/**
|
|
30
30
|
* HTTP transport for server-side payment handling.
|
|
31
31
|
*
|
|
32
|
-
* - Reads credentials from the
|
|
32
|
+
* - Reads credentials from the configured payment credential header
|
|
33
33
|
* - Issues challenges via `WWW-Authenticate` header with 402 status
|
|
34
34
|
* - Attaches receipts via `Payment-Receipt` header
|
|
35
35
|
*/
|
|
36
|
-
export function http() {
|
|
36
|
+
export function http(options = {}) {
|
|
37
|
+
const credentialHeader = options.requiresAuth
|
|
38
|
+
? Constants.Headers.paymentAuthorization
|
|
39
|
+
: Constants.Headers.authorization;
|
|
37
40
|
return from({
|
|
38
41
|
name: 'http',
|
|
39
42
|
captureRequest(request) {
|
|
@@ -45,7 +48,7 @@ export function http() {
|
|
|
45
48
|
};
|
|
46
49
|
},
|
|
47
50
|
getCredential(request) {
|
|
48
|
-
const header = request.headers.get(
|
|
51
|
+
const header = request.headers.get(credentialHeader);
|
|
49
52
|
if (!header)
|
|
50
53
|
return null;
|
|
51
54
|
const payment = Credential.extractPaymentScheme(header);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const minimumBytes = 32;
|
|
2
|
+
const generationCommand = 'openssl rand -base64 32';
|
|
3
|
+
/** Validates the minimum entropy required for HMAC-bound payment challenges. */
|
|
4
|
+
export function assert(secretKey) {
|
|
5
|
+
if (new TextEncoder().encode(secretKey).byteLength >= minimumBytes)
|
|
6
|
+
return;
|
|
7
|
+
throw new Error(`Secret key must be at least ${minimumBytes} bytes. Generate one with \`${generationCommand}\` and set MPP_SECRET_KEY or pass it to Mppx.create().`);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=SecretKey.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function submitCredential(credential: string): Promise<void>;
|
|
1
|
+
export declare function submitCredential(credential: string, header?: string): Promise<void>;
|
|
2
2
|
//# sourceMappingURL=serviceWorker.client.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { params } from './constants.js';
|
|
2
|
-
export async function submitCredential(credential) {
|
|
2
|
+
export async function submitCredential(credential, header = 'Authorization') {
|
|
3
3
|
const url = new URL(location.href);
|
|
4
4
|
url.searchParams.set(params.serviceWorker, '');
|
|
5
5
|
const registration = await navigator.serviceWorker.register(url.pathname + url.search);
|
|
@@ -19,7 +19,7 @@ export async function submitCredential(credential) {
|
|
|
19
19
|
await new Promise((resolve) => {
|
|
20
20
|
const channel = new MessageChannel();
|
|
21
21
|
channel.port1.onmessage = () => resolve();
|
|
22
|
-
serviceWorker.postMessage({ credential }, [channel.port2]);
|
|
22
|
+
serviceWorker.postMessage({ credential, header }, [channel.port2]);
|
|
23
23
|
});
|
|
24
24
|
location.reload();
|
|
25
25
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const serviceWorker = "(function(){let e=self,t
|
|
1
|
+
export declare const serviceWorker = "(function(){let e=self,t,n=`Authorization`;e.addEventListener(`activate`,t=>{t.waitUntil(e.clients.claim())}),e.addEventListener(`message`,e=>{if(!e.source)return;let r=e.data?.credential;typeof r!=`string`||!r.startsWith(`Payment `)||(t=r,typeof e.data?.header==`string`&&(n=e.data.header),e.ports[0]?.postMessage(`ack`))}),e.addEventListener(`fetch`,r=>{if(!t||r.request.mode!==`navigate`||new URL(r.request.url).origin!==e.location.origin)return;let i=new Headers(r.request.headers);i.set(n,t),t=void 0,r.respondWith(fetch(r.request,{headers:i})),e.registration.unregister()})})();";
|
|
2
2
|
//# sourceMappingURL=serviceWorker.gen.d.ts.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated — do not edit.
|
|
2
|
-
export const serviceWorker = "(function(){let e=self,t
|
|
2
|
+
export const serviceWorker = "(function(){let e=self,t,n=`Authorization`;e.addEventListener(`activate`,t=>{t.waitUntil(e.clients.claim())}),e.addEventListener(`message`,e=>{if(!e.source)return;let r=e.data?.credential;typeof r!=`string`||!r.startsWith(`Payment `)||(t=r,typeof e.data?.header==`string`&&(n=e.data.header),e.ports[0]?.postMessage(`ack`))}),e.addEventListener(`fetch`,r=>{if(!t||r.request.mode!==`navigate`||new URL(r.request.url).origin!==e.location.origin)return;let i=new Headers(r.request.headers);i.set(n,t),t=void 0,r.respondWith(fetch(r.request,{headers:i})),e.registration.unregister()})})();";
|
|
3
3
|
//# sourceMappingURL=serviceWorker.gen.js.map
|