mppx 0.8.19 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -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/Errors.d.ts +10 -0
- package/dist/Errors.js +12 -0
- package/dist/Html.js +1 -1
- package/dist/Mcp.d.ts +9 -1
- package/dist/Mcp.js +18 -0
- package/dist/Method.d.ts +7 -0
- 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/Mcp.js +2 -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/client/McpClient.js +22 -10
- package/dist/mcp/internal/Credential.d.ts +9 -0
- package/dist/mcp/internal/Credential.js +16 -0
- package/dist/mcp/server/Transport.d.ts +2 -2
- package/dist/mcp/server/Transport.js +11 -6
- 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 +8 -0
- package/dist/server/Mppx.js +92 -20
- package/dist/server/Transport.d.ts +8 -2
- package/dist/server/Transport.js +14 -18
- 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/Methods.d.ts +6 -0
- package/dist/stripe/Methods.js +3 -1
- package/dist/stripe/client/Charge.d.ts +6 -0
- package/dist/stripe/client/Methods.d.ts +6 -0
- package/dist/stripe/internal/payment-intent.d.ts +7 -0
- package/dist/stripe/internal/payment-intent.js +6 -0
- package/dist/stripe/server/Charge.d.ts +6 -0
- package/dist/stripe/server/Charge.js +8 -3
- package/dist/stripe/server/Methods.js +62 -7
- 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/Tokens.d.ts +13 -0
- package/dist/tempo/Tokens.js +13 -0
- package/dist/tempo/client/Charge.d.ts +1 -4
- package/dist/tempo/client/Charge.js +20 -13
- package/dist/tempo/client/Methods.d.ts +0 -10
- package/dist/tempo/index.d.ts +1 -0
- package/dist/tempo/index.js +1 -0
- 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 +22 -4
- package/dist/tempo/internal/fee-payer.js +12 -31
- package/dist/tempo/internal/fee-token.d.ts +2 -0
- package/dist/tempo/internal/fee-token.js +7 -0
- 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 +7 -27
- 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 +13 -39
- 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 +154 -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
|
|
@@ -5,6 +5,7 @@ import * as AcceptPayment from '../../internal/AcceptPayment.js';
|
|
|
5
5
|
import * as core_Mcp from '../../Mcp.js';
|
|
6
6
|
import * as z from '../../zod.js';
|
|
7
7
|
const MPPX_MCP_CLIENT_WRAPPER = Symbol.for('mppx.mcp.client.wrapper');
|
|
8
|
+
const maxPaymentAttempts = 3;
|
|
8
9
|
/**
|
|
9
10
|
* Adds automatic payment handling to an MCP SDK client.
|
|
10
11
|
*
|
|
@@ -80,7 +81,8 @@ export function isPaymentRequiredError(error) {
|
|
|
80
81
|
return false;
|
|
81
82
|
if (!('code' in error) || !('message' in error))
|
|
82
83
|
return false;
|
|
83
|
-
|
|
84
|
+
const code = error.code;
|
|
85
|
+
if (code !== core_Mcp.paymentRequiredCode && code !== core_Mcp.paymentVerificationFailedCode)
|
|
84
86
|
return false;
|
|
85
87
|
return isPaymentRequiredData(error.data);
|
|
86
88
|
}
|
|
@@ -98,7 +100,7 @@ async function createCredential(challenge, config) {
|
|
|
98
100
|
function createPaymentAwareCallTool(callTool, config) {
|
|
99
101
|
const methods = config.methods.flat();
|
|
100
102
|
const paymentPreferences = AcceptPayment.resolve(methods, config.paymentPreferences);
|
|
101
|
-
const retryWithPayment = async (params, call, paymentRequired, cause) => {
|
|
103
|
+
const retryWithPayment = async (params, call, paymentRequired, cause, attemptsRemaining = maxPaymentAttempts) => {
|
|
102
104
|
const challenges = paymentRequired.challenges;
|
|
103
105
|
const candidates = AcceptPayment.selectChallengeCandidates(challenges, methods, paymentPreferences.entries);
|
|
104
106
|
const orderedCandidates = config.orderChallenges
|
|
@@ -122,14 +124,24 @@ function createPaymentAwareCallTool(callTool, config) {
|
|
|
122
124
|
methods,
|
|
123
125
|
});
|
|
124
126
|
const parsed = Credential.deserialize(credential);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
try {
|
|
128
|
+
const retryResult = await callTool({
|
|
129
|
+
...params,
|
|
130
|
+
_meta: {
|
|
131
|
+
...params._meta,
|
|
132
|
+
[core_Mcp.credentialMetaKey]: parsed,
|
|
133
|
+
},
|
|
134
|
+
}, call.resultSchema, call.requestOptions);
|
|
135
|
+
const nextPaymentRequired = getPaymentRequiredMeta(retryResult);
|
|
136
|
+
if (nextPaymentRequired && attemptsRemaining > 1)
|
|
137
|
+
return retryWithPayment(params, call, nextPaymentRequired, retryResult, attemptsRemaining - 1);
|
|
138
|
+
return withReceipt(retryResult);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (!isPaymentRequiredError(error) || attemptsRemaining <= 1)
|
|
142
|
+
throw error;
|
|
143
|
+
return retryWithPayment(params, call, error.data, error, attemptsRemaining - 1);
|
|
144
|
+
}
|
|
133
145
|
};
|
|
134
146
|
return async (params, call) => {
|
|
135
147
|
try {
|
|
@@ -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,5 @@
|
|
|
1
1
|
import type { CallToolResult, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Credential from '../../Credential.js';
|
|
3
3
|
import * as core_Mcp from '../../Mcp.js';
|
|
4
4
|
import * as Transport from '../../server/Transport.js';
|
|
5
5
|
/**
|
|
@@ -18,7 +18,7 @@ export type McpSdk = Transport.Transport<Extra, McpError, CallToolResult>;
|
|
|
18
18
|
* MCP SDK transport for server-side payment handling with `@modelcontextprotocol/sdk`.
|
|
19
19
|
*
|
|
20
20
|
* - Reads credentials from `_meta["org.paymentauth/credential"]`
|
|
21
|
-
* -
|
|
21
|
+
* - Maps payment errors to their specification-defined JSON-RPC codes
|
|
22
22
|
* - Attaches receipts via `_meta["org.paymentauth/receipt"]` on tool results
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import * as Credential from '../../Credential.js';
|
|
1
2
|
import * as core_Mcp from '../../Mcp.js';
|
|
2
3
|
import * as Transport from '../../server/Transport.js';
|
|
4
|
+
import * as McpCredential from '../internal/Credential.js';
|
|
3
5
|
/**
|
|
4
6
|
* MCP SDK transport for server-side payment handling with `@modelcontextprotocol/sdk`.
|
|
5
7
|
*
|
|
6
8
|
* - Reads credentials from `_meta["org.paymentauth/credential"]`
|
|
7
|
-
* -
|
|
9
|
+
* - Maps payment errors to their specification-defined JSON-RPC codes
|
|
8
10
|
* - Attaches receipts via `_meta["org.paymentauth/receipt"]` on tool results
|
|
9
11
|
*
|
|
10
12
|
* @example
|
|
@@ -40,10 +42,13 @@ export function mcpSdk() {
|
|
|
40
42
|
};
|
|
41
43
|
},
|
|
42
44
|
getCredential(extra) {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
+
const value = extra._meta?.[core_Mcp.credentialMetaKey];
|
|
46
|
+
if (value === undefined)
|
|
45
47
|
return null;
|
|
46
|
-
|
|
48
|
+
const parsed = McpCredential.parse(value);
|
|
49
|
+
if (!parsed)
|
|
50
|
+
throw new Credential.InvalidCredentialEncodingError();
|
|
51
|
+
return parsed.value;
|
|
47
52
|
},
|
|
48
53
|
async respondChallenge({ challenge, error }) {
|
|
49
54
|
if (!McpErrorClass) {
|
|
@@ -57,8 +62,8 @@ export function mcpSdk() {
|
|
|
57
62
|
throw err;
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
|
-
return new McpErrorClass(core_Mcp.
|
|
61
|
-
httpStatus: 402,
|
|
65
|
+
return new McpErrorClass(core_Mcp.errorCode(error), error?.message ?? 'Payment Required', {
|
|
66
|
+
httpStatus: error?.status ?? 402,
|
|
62
67
|
challenges: [challenge],
|
|
63
68
|
...(error && { problem: error.toProblemDetails(challenge.id) }),
|
|
64
69
|
});
|
|
@@ -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
|
@@ -99,7 +99,13 @@ export type PaymentSuccessContext<method extends Method.Method = Method.Method,
|
|
|
99
99
|
input?: Transport.InputOf<transport> | undefined;
|
|
100
100
|
method: ServerMethodDescriptor<method>;
|
|
101
101
|
receipt: Receipt.Receipt;
|
|
102
|
+
/** Canonical request represented by the challenge. */
|
|
102
103
|
request: z.output<method['schema']['request']>;
|
|
104
|
+
/**
|
|
105
|
+
* Resolved method input before request-schema output transforms. Absent during
|
|
106
|
+
* standalone credential verification when no route options are supplied.
|
|
107
|
+
*/
|
|
108
|
+
requestInput?: z.input<method['schema']['request']> | undefined;
|
|
103
109
|
}>;
|
|
104
110
|
/** Options for standalone credential verification. */
|
|
105
111
|
export type VerifyCredentialOptions = {
|
|
@@ -315,6 +321,8 @@ export declare namespace create {
|
|
|
315
321
|
attestation?: transport extends Transport.Http ? Attestation.VerifierMap | undefined : never;
|
|
316
322
|
/** Array of configured methods. @example [tempo()] */
|
|
317
323
|
methods: methods;
|
|
324
|
+
/** Uses `Payment-Authorization` for Payment credentials so `Authorization` remains available for application authentication. */
|
|
325
|
+
requiresAuth?: transport extends Transport.Http ? boolean | undefined : never;
|
|
318
326
|
/** 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
327
|
realm?: string | undefined;
|
|
320
328
|
/** Secret key for HMAC-bound challenge IDs for stateless verification. Must be at least 32 bytes. Auto-detected from `MPP_SECRET_KEY` environment variable. */
|