@nevermined-io/payments 1.0.3 → 1.0.4
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/dist/mcp/core/auth.d.ts +12 -0
- package/dist/mcp/core/auth.d.ts.map +1 -1
- package/dist/mcp/core/auth.js +107 -197
- package/dist/mcp/core/auth.js.map +1 -1
- package/dist/mcp/core/paywall.d.ts.map +1 -1
- package/dist/mcp/core/paywall.js +42 -20
- package/dist/mcp/core/paywall.js.map +1 -1
- package/dist/mcp/types/paywall.types.d.ts +6 -0
- package/dist/mcp/types/paywall.types.d.ts.map +1 -1
- package/dist/mcp/types/paywall.types.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/core/auth.d.ts
CHANGED
|
@@ -23,11 +23,22 @@ export declare class PaywallAuthenticator {
|
|
|
23
23
|
* @returns HTTP endpoint URL or undefined if context is not available
|
|
24
24
|
*/
|
|
25
25
|
private buildHttpUrlFromContext;
|
|
26
|
+
/**
|
|
27
|
+
* Core verification logic shared by authenticate and authenticateMeta.
|
|
28
|
+
* Tries logical URL first, falls back to HTTP URL if available.
|
|
29
|
+
*/
|
|
30
|
+
private verifyWithFallback;
|
|
31
|
+
/**
|
|
32
|
+
* Verify permissions against a single endpoint URL.
|
|
33
|
+
* Resolves planId from the token or from the agent's plans as fallback.
|
|
34
|
+
*/
|
|
35
|
+
private verifyWithEndpoint;
|
|
26
36
|
/**
|
|
27
37
|
* Authenticate an MCP request
|
|
28
38
|
*/
|
|
29
39
|
authenticate(extra: any, options: {
|
|
30
40
|
planId?: string;
|
|
41
|
+
maxAmount?: bigint;
|
|
31
42
|
} | undefined, agentId: string, serverName: string, name: string, kind: 'tool' | 'resource' | 'prompt', argsOrVars: any): Promise<AuthResult>;
|
|
32
43
|
/**
|
|
33
44
|
* Authenticate generic MCP meta operations (e.g., initialize, tools/list, resources/list, prompts/list).
|
|
@@ -35,6 +46,7 @@ export declare class PaywallAuthenticator {
|
|
|
35
46
|
*/
|
|
36
47
|
authenticateMeta(extra: any, options: {
|
|
37
48
|
planId?: string;
|
|
49
|
+
maxAmount?: bigint;
|
|
38
50
|
} | undefined, agentId: string, serverName: string, method: string): Promise<AuthResult>;
|
|
39
51
|
}
|
|
40
52
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/mcp/core/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAGjD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/mcp/core/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAGjD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAgBtD;;GAEG;AACH,qBAAa,oBAAoB;IACnB,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,QAAQ;IAEtC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAepC;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAuB/B;;;OAGG;YACW,kBAAkB;IAoEhC;;;OAGG;YACW,kBAAkB;IAoDhC;;OAEG;IACG,YAAY,CAChB,KAAK,EAAE,GAAG,EACV,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,YAAK,EACrD,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,EACpC,UAAU,EAAE,GAAG,GACd,OAAO,CAAC,UAAU,CAAC;IAkBtB;;;OAGG;IACG,gBAAgB,CACpB,KAAK,EAAE,GAAG,EACV,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,YAAK,EACrD,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC;CAiBvB"}
|
package/dist/mcp/core/auth.js
CHANGED
|
@@ -56,126 +56,122 @@ export class PaywallAuthenticator {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
59
|
+
* Core verification logic shared by authenticate and authenticateMeta.
|
|
60
|
+
* Tries logical URL first, falls back to HTTP URL if available.
|
|
60
61
|
*/
|
|
61
|
-
async
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const authHeader = this.extractAuthHeaderFromContext(extra);
|
|
65
|
-
if (!authHeader) {
|
|
66
|
-
throw createRpcError(ERROR_CODES.PaymentRequired, 'Authorization required', {
|
|
67
|
-
reason: 'missing',
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
const accessToken = stripBearer(authHeader);
|
|
71
|
-
const logicalUrl = buildLogicalUrl({ kind, serverName, name, argsOrVars });
|
|
72
|
-
// Validate access with Nevermined - try logical URL first
|
|
62
|
+
async verifyWithFallback(ctx) {
|
|
63
|
+
const { accessToken, logicalUrl, httpUrl, maxAmount, agentId, planIdOverride } = ctx;
|
|
64
|
+
// Try logical URL first
|
|
73
65
|
try {
|
|
74
|
-
const
|
|
75
|
-
if (!decodedAccessToken) {
|
|
76
|
-
throw new Error('Invalid access token');
|
|
77
|
-
}
|
|
78
|
-
const planId = decodedAccessToken.accepted?.planId;
|
|
79
|
-
// Extract subscriberAddress from payload.authorization.from per x402 spec
|
|
80
|
-
const subscriberAddress = decodedAccessToken.payload?.authorization?.from;
|
|
81
|
-
if (!planId || !subscriberAddress) {
|
|
82
|
-
throw new Error('Cannot determine plan_id or subscriber_address from token (expected payload.authorization.from)');
|
|
83
|
-
}
|
|
84
|
-
const paymentRequired = buildPaymentRequired(planId, {
|
|
85
|
-
endpoint: logicalUrl,
|
|
86
|
-
agentId,
|
|
87
|
-
httpVerb: 'POST',
|
|
88
|
-
});
|
|
89
|
-
const result = await this.payments.facilitator.verifyPermissions({
|
|
90
|
-
paymentRequired,
|
|
91
|
-
x402AccessToken: accessToken,
|
|
92
|
-
maxAmount: 1n,
|
|
93
|
-
});
|
|
94
|
-
if (!result.isValid) {
|
|
95
|
-
throw new Error('Permission verification failed');
|
|
96
|
-
}
|
|
66
|
+
const result = await this.verifyWithEndpoint(accessToken, logicalUrl, agentId, maxAmount, planIdOverride);
|
|
97
67
|
return {
|
|
98
68
|
token: accessToken,
|
|
99
69
|
agentId,
|
|
100
70
|
logicalUrl,
|
|
101
|
-
|
|
102
|
-
|
|
71
|
+
httpUrl,
|
|
72
|
+
planId: result.planId,
|
|
73
|
+
subscriberAddress: result.subscriberAddress,
|
|
103
74
|
agentRequest: result.agentRequest,
|
|
104
75
|
};
|
|
105
76
|
}
|
|
106
|
-
catch
|
|
107
|
-
// If logical URL
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
planId = agentPlans.plans[0].planId || agentPlans.plans[0].id;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch (planError) {
|
|
128
|
-
// Ignore errors fetching plans
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (!planId || !subscriberAddress) {
|
|
132
|
-
throw new Error('Cannot determine plan_id or subscriber_address from token (expected accepted.planId and payload.authorization.from)');
|
|
133
|
-
}
|
|
134
|
-
const paymentRequired = buildPaymentRequired(planId, {
|
|
135
|
-
endpoint: httpUrl,
|
|
136
|
-
agentId,
|
|
137
|
-
httpVerb: 'POST',
|
|
138
|
-
});
|
|
139
|
-
const result = await this.payments.facilitator.verifyPermissions({
|
|
140
|
-
paymentRequired,
|
|
141
|
-
x402AccessToken: accessToken,
|
|
142
|
-
maxAmount: 1n,
|
|
143
|
-
});
|
|
144
|
-
if (!result.isValid) {
|
|
145
|
-
throw new Error('Permission verification failed');
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
token: accessToken,
|
|
149
|
-
agentId,
|
|
150
|
-
logicalUrl,
|
|
151
|
-
planId,
|
|
152
|
-
subscriberAddress,
|
|
153
|
-
agentRequest: result.agentRequest,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
catch (httpError) {
|
|
157
|
-
void httpError;
|
|
158
|
-
}
|
|
77
|
+
catch {
|
|
78
|
+
// If logical URL fails and we have an HTTP URL, try that
|
|
79
|
+
}
|
|
80
|
+
if (httpUrl) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await this.verifyWithEndpoint(accessToken, httpUrl, agentId, maxAmount, planIdOverride);
|
|
83
|
+
return {
|
|
84
|
+
token: accessToken,
|
|
85
|
+
agentId,
|
|
86
|
+
logicalUrl,
|
|
87
|
+
httpUrl,
|
|
88
|
+
planId: result.planId,
|
|
89
|
+
subscriberAddress: result.subscriberAddress,
|
|
90
|
+
agentRequest: result.agentRequest,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// HTTP fallback also failed
|
|
159
95
|
}
|
|
160
|
-
|
|
161
|
-
|
|
96
|
+
}
|
|
97
|
+
// Both attempts failed — enrich denial with suggested plans (best-effort)
|
|
98
|
+
let plansMsg = '';
|
|
99
|
+
try {
|
|
100
|
+
const plans = await this.payments.agents.getAgentPlans(agentId);
|
|
101
|
+
if (plans && Array.isArray(plans.plans) && plans.plans.length > 0) {
|
|
102
|
+
const top = plans.plans.slice(0, 3);
|
|
103
|
+
const summary = top
|
|
104
|
+
.map((p) => `${p.planId || p.id || 'plan'}${p.name ? ` (${p.name})` : ''}`)
|
|
105
|
+
.join(', ');
|
|
106
|
+
plansMsg = summary ? ` Available plans: ${summary}...` : '';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Ignore errors fetching plans - best effort only
|
|
111
|
+
}
|
|
112
|
+
throw createRpcError(ERROR_CODES.PaymentRequired, `Payment required.${plansMsg}`, {
|
|
113
|
+
reason: 'invalid',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Verify permissions against a single endpoint URL.
|
|
118
|
+
* Resolves planId from the token or from the agent's plans as fallback.
|
|
119
|
+
*/
|
|
120
|
+
async verifyWithEndpoint(accessToken, endpoint, agentId, maxAmount, planIdOverride) {
|
|
121
|
+
const decodedAccessToken = decodeAccessToken(accessToken);
|
|
122
|
+
if (!decodedAccessToken) {
|
|
123
|
+
throw new Error('Invalid access token');
|
|
124
|
+
}
|
|
125
|
+
let planId = planIdOverride ?? decodedAccessToken.accepted?.planId;
|
|
126
|
+
const subscriberAddress = decodedAccessToken.payload?.authorization?.from;
|
|
127
|
+
// If planId is not available, try to get it from the agent's plans
|
|
128
|
+
if (!planId) {
|
|
162
129
|
try {
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
const summary = top
|
|
167
|
-
.map((p) => `${p.planId || p.id || 'plan'}${p.name ? ` (${p.name})` : ''}`)
|
|
168
|
-
.join(', ');
|
|
169
|
-
plansMsg = summary ? ` Available plans: ${summary}...` : '';
|
|
130
|
+
const agentPlans = await this.payments.agents.getAgentPlans(agentId);
|
|
131
|
+
if (agentPlans && Array.isArray(agentPlans.plans) && agentPlans.plans.length > 0) {
|
|
132
|
+
planId = agentPlans.plans[0].planId || agentPlans.plans[0].id;
|
|
170
133
|
}
|
|
171
134
|
}
|
|
172
135
|
catch {
|
|
173
|
-
// Ignore errors fetching plans
|
|
136
|
+
// Ignore errors fetching plans
|
|
174
137
|
}
|
|
175
|
-
|
|
176
|
-
|
|
138
|
+
}
|
|
139
|
+
if (!planId || !subscriberAddress) {
|
|
140
|
+
throw new Error('Cannot determine plan_id or subscriber_address from token (expected accepted.planId and payload.authorization.from)');
|
|
141
|
+
}
|
|
142
|
+
const paymentRequired = buildPaymentRequired(planId, {
|
|
143
|
+
endpoint,
|
|
144
|
+
agentId,
|
|
145
|
+
httpVerb: 'POST',
|
|
146
|
+
});
|
|
147
|
+
const result = await this.payments.facilitator.verifyPermissions({
|
|
148
|
+
paymentRequired,
|
|
149
|
+
x402AccessToken: accessToken,
|
|
150
|
+
maxAmount,
|
|
151
|
+
});
|
|
152
|
+
if (!result.isValid) {
|
|
153
|
+
throw new Error('Permission verification failed');
|
|
154
|
+
}
|
|
155
|
+
return { planId, subscriberAddress, agentRequest: result.agentRequest };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Authenticate an MCP request
|
|
159
|
+
*/
|
|
160
|
+
async authenticate(extra, options = {}, agentId, serverName, name, kind, argsOrVars) {
|
|
161
|
+
const authHeader = this.extractAuthHeaderFromContext(extra);
|
|
162
|
+
if (!authHeader) {
|
|
163
|
+
throw createRpcError(ERROR_CODES.PaymentRequired, 'Authorization required', {
|
|
164
|
+
reason: 'missing',
|
|
177
165
|
});
|
|
178
166
|
}
|
|
167
|
+
return this.verifyWithFallback({
|
|
168
|
+
accessToken: stripBearer(authHeader),
|
|
169
|
+
logicalUrl: buildLogicalUrl({ kind, serverName, name, argsOrVars }),
|
|
170
|
+
httpUrl: this.buildHttpUrlFromContext(),
|
|
171
|
+
maxAmount: options.maxAmount ?? 1n,
|
|
172
|
+
agentId,
|
|
173
|
+
planIdOverride: options.planId,
|
|
174
|
+
});
|
|
179
175
|
}
|
|
180
176
|
/**
|
|
181
177
|
* Authenticate generic MCP meta operations (e.g., initialize, tools/list, resources/list, prompts/list).
|
|
@@ -188,100 +184,14 @@ export class PaywallAuthenticator {
|
|
|
188
184
|
reason: 'missing',
|
|
189
185
|
});
|
|
190
186
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Extract subscriberAddress from payload.authorization.from per x402 spec
|
|
200
|
-
const subscriberAddress = decodedAccessToken.payload?.authorization?.from;
|
|
201
|
-
if (!planId || !subscriberAddress) {
|
|
202
|
-
throw new Error('Cannot determine plan_id or subscriber_address from token (expected payload.authorization.from)');
|
|
203
|
-
}
|
|
204
|
-
const paymentRequired = buildPaymentRequired(planId, {
|
|
205
|
-
endpoint: logicalUrl,
|
|
206
|
-
agentId,
|
|
207
|
-
httpVerb: 'POST',
|
|
208
|
-
});
|
|
209
|
-
const result = await this.payments.facilitator.verifyPermissions({
|
|
210
|
-
paymentRequired,
|
|
211
|
-
x402AccessToken: accessToken,
|
|
212
|
-
maxAmount: 1n,
|
|
213
|
-
});
|
|
214
|
-
if (!result.isValid) {
|
|
215
|
-
throw new Error('Permission verification failed');
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
token: accessToken,
|
|
219
|
-
agentId,
|
|
220
|
-
logicalUrl,
|
|
221
|
-
planId,
|
|
222
|
-
subscriberAddress,
|
|
223
|
-
agentRequest: result.agentRequest,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
catch (e) {
|
|
227
|
-
const httpUrl = this.buildHttpUrlFromContext();
|
|
228
|
-
if (httpUrl) {
|
|
229
|
-
try {
|
|
230
|
-
const decodedAccessToken = decodeAccessToken(accessToken);
|
|
231
|
-
if (!decodedAccessToken) {
|
|
232
|
-
throw new Error('Invalid access token');
|
|
233
|
-
}
|
|
234
|
-
// Extract planId from accepted.planId per x402 spec
|
|
235
|
-
const planId = decodedAccessToken.accepted?.planId;
|
|
236
|
-
// Extract subscriberAddress from payload.authorization.from per x402 spec
|
|
237
|
-
const subscriberAddress = decodedAccessToken.payload?.authorization?.from;
|
|
238
|
-
if (!planId || !subscriberAddress) {
|
|
239
|
-
throw new Error('Cannot determine plan_id or subscriber_address from token (expected accepted.planId and payload.authorization.from)');
|
|
240
|
-
}
|
|
241
|
-
const paymentRequired = buildPaymentRequired(planId, {
|
|
242
|
-
endpoint: httpUrl,
|
|
243
|
-
agentId,
|
|
244
|
-
httpVerb: 'POST',
|
|
245
|
-
});
|
|
246
|
-
const result = await this.payments.facilitator.verifyPermissions({
|
|
247
|
-
paymentRequired,
|
|
248
|
-
x402AccessToken: accessToken,
|
|
249
|
-
maxAmount: 1n,
|
|
250
|
-
});
|
|
251
|
-
if (!result.isValid) {
|
|
252
|
-
throw new Error('Permission verification failed');
|
|
253
|
-
}
|
|
254
|
-
return {
|
|
255
|
-
token: accessToken,
|
|
256
|
-
agentId,
|
|
257
|
-
logicalUrl: httpUrl,
|
|
258
|
-
planId,
|
|
259
|
-
subscriberAddress,
|
|
260
|
-
agentRequest: result.agentRequest,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
catch (httpError) {
|
|
264
|
-
void httpError;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
let plansMsg = '';
|
|
268
|
-
try {
|
|
269
|
-
const plans = await this.payments.agents.getAgentPlans(agentId);
|
|
270
|
-
if (plans && Array.isArray(plans.plans) && plans.plans.length > 0) {
|
|
271
|
-
const top = plans.plans.slice(0, 3);
|
|
272
|
-
const summary = top
|
|
273
|
-
.map((p) => `${p.planId || p.id || 'plan'}${p.name ? ` (${p.name})` : ''}`)
|
|
274
|
-
.join(', ');
|
|
275
|
-
plansMsg = summary ? ` Available plans: ${summary}...` : '';
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch (_err) {
|
|
279
|
-
void _err;
|
|
280
|
-
}
|
|
281
|
-
throw createRpcError(ERROR_CODES.PaymentRequired, `Payment required.${plansMsg}`, {
|
|
282
|
-
reason: 'invalid',
|
|
283
|
-
});
|
|
284
|
-
}
|
|
187
|
+
return this.verifyWithFallback({
|
|
188
|
+
accessToken: stripBearer(authHeader),
|
|
189
|
+
logicalUrl: buildLogicalMetaUrl(serverName, method),
|
|
190
|
+
httpUrl: this.buildHttpUrlFromContext(),
|
|
191
|
+
maxAmount: options.maxAmount ?? 1n,
|
|
192
|
+
agentId,
|
|
193
|
+
planIdOverride: options.planId,
|
|
194
|
+
});
|
|
285
195
|
}
|
|
286
196
|
}
|
|
287
197
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/mcp/core/auth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authentication handler for MCP paywall using X402 tokens\n */\nimport type { Payments } from '../../payments.js'\nimport { decodeAccessToken } from '../../utils.js'\nimport { getCurrentRequestContext } from '../http/mcp-handler.js'\nimport { AuthResult } from '../types/paywall.types.js'\nimport { ERROR_CODES, createRpcError } from '../utils/errors.js'\nimport { buildLogicalMetaUrl, buildLogicalUrl } from '../utils/logical-url.js'\nimport { extractAuthHeader, stripBearer } from '../utils/request.js'\nimport { buildPaymentRequired, type X402PaymentRequired } from '../../x402/facilitator-api.js'\n\n/**\n * Handles authentication and authorization for MCP requests\n */\nexport class PaywallAuthenticator {\n constructor(private payments: Payments) {}\n\n /**\n * Extract authorization header from extra context or AsyncLocalStorage.\n * Tries SDK's extra context first, then falls back to HTTP request context.\n *\n * @param extra - MCP extra context from SDK\n * @returns Authorization header value or undefined\n */\n private extractAuthHeaderFromContext(extra: any): string | undefined {\n // Try to extract auth header from SDK's extra context first\n let authHeader = extractAuthHeader(extra)\n\n if (!authHeader) {\n const requestContext = getCurrentRequestContext()\n if (requestContext?.headers) {\n // Build an extra-like object for extractAuthHeader\n authHeader = extractAuthHeader({ requestInfo: { headers: requestContext.headers } })\n }\n }\n\n return authHeader\n }\n\n /**\n * Build HTTP endpoint URL from request context.\n *\n * @returns HTTP endpoint URL or undefined if context is not available\n */\n private buildHttpUrlFromContext(): string | undefined {\n const requestContext = getCurrentRequestContext()\n if (!requestContext) {\n return undefined\n }\n\n try {\n const host = requestContext.headers?.['host'] || requestContext.headers?.['x-forwarded-host']\n if (!host || typeof host !== 'string') {\n return undefined\n }\n\n const protocol = requestContext.headers?.['x-forwarded-proto'] || 'http'\n const baseUrl = `${protocol}://${host}`\n\n // Use requestContext.url if available (e.g., '/mcp'), otherwise default to '/mcp'\n const path = requestContext.url || '/mcp'\n return `${baseUrl}${path}`\n } catch {\n return undefined\n }\n }\n\n /**\n * Authenticate an MCP request\n */\n async authenticate(\n extra: any,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n options: { planId?: string } = {},\n agentId: string,\n serverName: string,\n name: string,\n kind: 'tool' | 'resource' | 'prompt',\n argsOrVars: any,\n ): Promise<AuthResult> {\n const authHeader = this.extractAuthHeaderFromContext(extra)\n if (!authHeader) {\n throw createRpcError(ERROR_CODES.PaymentRequired, 'Authorization required', {\n reason: 'missing',\n })\n }\n\n const accessToken = stripBearer(authHeader)\n const logicalUrl = buildLogicalUrl({ kind, serverName, name, argsOrVars })\n\n // Validate access with Nevermined - try logical URL first\n try {\n const decodedAccessToken = decodeAccessToken(accessToken)\n if (!decodedAccessToken) {\n throw new Error('Invalid access token')\n }\n\n const planId = decodedAccessToken.accepted?.planId\n\n // Extract subscriberAddress from payload.authorization.from per x402 spec\n const subscriberAddress = decodedAccessToken.payload?.authorization?.from\n\n if (!planId || !subscriberAddress) {\n throw new Error(\n 'Cannot determine plan_id or subscriber_address from token (expected payload.authorization.from)',\n )\n }\n\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: logicalUrl,\n agentId,\n httpVerb: 'POST',\n })\n\n const result = await this.payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: accessToken,\n maxAmount: 1n,\n })\n\n if (!result.isValid) {\n throw new Error('Permission verification failed')\n }\n\n return {\n token: accessToken,\n agentId,\n logicalUrl,\n planId,\n subscriberAddress,\n agentRequest: result.agentRequest,\n }\n } catch (e) {\n // If logical URL validation fails, try with HTTP endpoint\n const httpUrl = this.buildHttpUrlFromContext()\n if (httpUrl) {\n try {\n const decodedAccessToken = decodeAccessToken(accessToken)\n if (!decodedAccessToken) {\n throw new Error('Invalid access token')\n }\n // Extract planId from accepted.planId per x402 spec\n let planId = decodedAccessToken.accepted?.planId\n // Extract subscriberAddress from payload.authorization.from per x402 spec\n const subscriberAddress = decodedAccessToken.payload?.authorization?.from\n\n // If planId is not in the token, try to get it from the agent's plans\n if (!planId) {\n try {\n const agentPlans = await this.payments.agents.getAgentPlans(agentId)\n if (agentPlans && Array.isArray(agentPlans.plans) && agentPlans.plans.length > 0) {\n planId = agentPlans.plans[0].planId || agentPlans.plans[0].id\n }\n } catch (planError) {\n // Ignore errors fetching plans\n }\n }\n\n if (!planId || !subscriberAddress) {\n throw new Error(\n 'Cannot determine plan_id or subscriber_address from token (expected accepted.planId and payload.authorization.from)',\n )\n }\n\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: httpUrl,\n agentId,\n httpVerb: 'POST',\n })\n\n const result = await this.payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: accessToken,\n maxAmount: 1n,\n })\n\n if (!result.isValid) {\n throw new Error('Permission verification failed')\n }\n\n return {\n token: accessToken,\n agentId,\n logicalUrl,\n planId,\n subscriberAddress,\n agentRequest: result.agentRequest,\n }\n } catch (httpError) {\n void httpError\n }\n }\n\n // Enrich denial with suggested plans (best-effort)\n let plansMsg = ''\n try {\n const plans = await this.payments.agents.getAgentPlans(agentId)\n if (plans && Array.isArray(plans.plans) && plans.plans.length > 0) {\n const top = plans.plans.slice(0, 3)\n const summary = top\n .map((p: any) => `${p.planId || p.id || 'plan'}${p.name ? ` (${p.name})` : ''}`)\n .join(', ')\n plansMsg = summary ? ` Available plans: ${summary}...` : ''\n }\n } catch {\n // Ignore errors fetching plans - best effort only\n }\n\n throw createRpcError(ERROR_CODES.PaymentRequired, `Payment required.${plansMsg}`, {\n reason: 'invalid',\n })\n }\n }\n\n /**\n * Authenticate generic MCP meta operations (e.g., initialize, tools/list, resources/list, prompts/list).\n * Returns an AuthResult compatible with paywall flows (without redeem step).\n */\n async authenticateMeta(\n extra: any,\n options: { planId?: string } = {},\n agentId: string,\n serverName: string,\n method: string,\n ): Promise<AuthResult> {\n const authHeader = this.extractAuthHeaderFromContext(extra)\n if (!authHeader) {\n throw createRpcError(ERROR_CODES.PaymentRequired, 'Authorization required', {\n reason: 'missing',\n })\n }\n const accessToken = stripBearer(authHeader)\n const logicalUrl = buildLogicalMetaUrl(serverName, method)\n\n try {\n const decodedAccessToken = decodeAccessToken(accessToken)\n if (!decodedAccessToken) {\n throw new Error('Invalid access token')\n }\n const planId = options.planId\n // Extract subscriberAddress from payload.authorization.from per x402 spec\n const subscriberAddress = decodedAccessToken.payload?.authorization?.from\n if (!planId || !subscriberAddress) {\n throw new Error(\n 'Cannot determine plan_id or subscriber_address from token (expected payload.authorization.from)',\n )\n }\n\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: logicalUrl,\n agentId,\n httpVerb: 'POST',\n })\n\n const result = await this.payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: accessToken,\n maxAmount: 1n,\n })\n if (!result.isValid) {\n throw new Error('Permission verification failed')\n }\n return {\n token: accessToken,\n agentId,\n logicalUrl,\n planId,\n subscriberAddress,\n agentRequest: result.agentRequest,\n }\n } catch (e) {\n const httpUrl = this.buildHttpUrlFromContext()\n if (httpUrl) {\n try {\n const decodedAccessToken = decodeAccessToken(accessToken)\n if (!decodedAccessToken) {\n throw new Error('Invalid access token')\n }\n // Extract planId from accepted.planId per x402 spec\n const planId = decodedAccessToken.accepted?.planId\n // Extract subscriberAddress from payload.authorization.from per x402 spec\n const subscriberAddress = decodedAccessToken.payload?.authorization?.from\n if (!planId || !subscriberAddress) {\n throw new Error(\n 'Cannot determine plan_id or subscriber_address from token (expected accepted.planId and payload.authorization.from)',\n )\n }\n\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: httpUrl,\n agentId,\n httpVerb: 'POST',\n })\n\n const result = await this.payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: accessToken,\n maxAmount: 1n,\n })\n if (!result.isValid) {\n throw new Error('Permission verification failed')\n }\n return {\n token: accessToken,\n agentId,\n logicalUrl: httpUrl,\n planId,\n subscriberAddress,\n agentRequest: result.agentRequest,\n }\n } catch (httpError) {\n void httpError\n }\n }\n\n let plansMsg = ''\n try {\n const plans = await this.payments.agents.getAgentPlans(agentId)\n if (plans && Array.isArray(plans.plans) && plans.plans.length > 0) {\n const top = plans.plans.slice(0, 3)\n const summary = top\n .map((p: any) => `${p.planId || p.id || 'plan'}${p.name ? ` (${p.name})` : ''}`)\n .join(', ')\n plansMsg = summary ? ` Available plans: ${summary}...` : ''\n }\n } catch (_err) {\n void _err\n }\n\n throw createRpcError(ERROR_CODES.PaymentRequired, `Payment required.${plansMsg}`, {\n reason: 'invalid',\n })\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/mcp/core/auth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authentication handler for MCP paywall using X402 tokens\n */\nimport type { Payments } from '../../payments.js'\nimport { decodeAccessToken } from '../../utils.js'\nimport { getCurrentRequestContext } from '../http/mcp-handler.js'\nimport { AuthResult } from '../types/paywall.types.js'\nimport { ERROR_CODES, createRpcError } from '../utils/errors.js'\nimport { Address } from '../../common/types.js'\nimport { buildLogicalMetaUrl, buildLogicalUrl } from '../utils/logical-url.js'\nimport { extractAuthHeader, stripBearer } from '../utils/request.js'\nimport { buildPaymentRequired, type X402PaymentRequired } from '../../x402/facilitator-api.js'\n\ninterface VerifyContext {\n accessToken: string\n logicalUrl: string\n httpUrl: string | undefined\n maxAmount: bigint\n agentId: string\n planIdOverride?: string\n}\n\n/**\n * Handles authentication and authorization for MCP requests\n */\nexport class PaywallAuthenticator {\n constructor(private payments: Payments) {}\n\n /**\n * Extract authorization header from extra context or AsyncLocalStorage.\n * Tries SDK's extra context first, then falls back to HTTP request context.\n *\n * @param extra - MCP extra context from SDK\n * @returns Authorization header value or undefined\n */\n private extractAuthHeaderFromContext(extra: any): string | undefined {\n // Try to extract auth header from SDK's extra context first\n let authHeader = extractAuthHeader(extra)\n\n if (!authHeader) {\n const requestContext = getCurrentRequestContext()\n if (requestContext?.headers) {\n // Build an extra-like object for extractAuthHeader\n authHeader = extractAuthHeader({ requestInfo: { headers: requestContext.headers } })\n }\n }\n\n return authHeader\n }\n\n /**\n * Build HTTP endpoint URL from request context.\n *\n * @returns HTTP endpoint URL or undefined if context is not available\n */\n private buildHttpUrlFromContext(): string | undefined {\n const requestContext = getCurrentRequestContext()\n if (!requestContext) {\n return undefined\n }\n\n try {\n const host = requestContext.headers?.['host'] || requestContext.headers?.['x-forwarded-host']\n if (!host || typeof host !== 'string') {\n return undefined\n }\n\n const protocol = requestContext.headers?.['x-forwarded-proto'] || 'http'\n const baseUrl = `${protocol}://${host}`\n\n // Use requestContext.url if available (e.g., '/mcp'), otherwise default to '/mcp'\n const path = requestContext.url || '/mcp'\n return `${baseUrl}${path}`\n } catch {\n return undefined\n }\n }\n\n /**\n * Core verification logic shared by authenticate and authenticateMeta.\n * Tries logical URL first, falls back to HTTP URL if available.\n */\n private async verifyWithFallback(ctx: VerifyContext): Promise<AuthResult> {\n const { accessToken, logicalUrl, httpUrl, maxAmount, agentId, planIdOverride } = ctx\n\n // Try logical URL first\n try {\n const result = await this.verifyWithEndpoint(\n accessToken,\n logicalUrl,\n agentId,\n maxAmount,\n planIdOverride,\n )\n return {\n token: accessToken,\n agentId,\n logicalUrl,\n httpUrl,\n planId: result.planId,\n subscriberAddress: result.subscriberAddress,\n agentRequest: result.agentRequest,\n }\n } catch {\n // If logical URL fails and we have an HTTP URL, try that\n }\n\n if (httpUrl) {\n try {\n const result = await this.verifyWithEndpoint(\n accessToken,\n httpUrl,\n agentId,\n maxAmount,\n planIdOverride,\n )\n return {\n token: accessToken,\n agentId,\n logicalUrl,\n httpUrl,\n planId: result.planId,\n subscriberAddress: result.subscriberAddress,\n agentRequest: result.agentRequest,\n }\n } catch {\n // HTTP fallback also failed\n }\n }\n\n // Both attempts failed — enrich denial with suggested plans (best-effort)\n let plansMsg = ''\n try {\n const plans = await this.payments.agents.getAgentPlans(agentId)\n if (plans && Array.isArray(plans.plans) && plans.plans.length > 0) {\n const top = plans.plans.slice(0, 3)\n const summary = top\n .map((p: any) => `${p.planId || p.id || 'plan'}${p.name ? ` (${p.name})` : ''}`)\n .join(', ')\n plansMsg = summary ? ` Available plans: ${summary}...` : ''\n }\n } catch {\n // Ignore errors fetching plans - best effort only\n }\n\n throw createRpcError(ERROR_CODES.PaymentRequired, `Payment required.${plansMsg}`, {\n reason: 'invalid',\n })\n }\n\n /**\n * Verify permissions against a single endpoint URL.\n * Resolves planId from the token or from the agent's plans as fallback.\n */\n private async verifyWithEndpoint(\n accessToken: string,\n endpoint: string,\n agentId: string,\n maxAmount: bigint,\n planIdOverride?: string,\n ): Promise<{ planId: string; subscriberAddress: Address; agentRequest?: any }> {\n const decodedAccessToken = decodeAccessToken(accessToken)\n if (!decodedAccessToken) {\n throw new Error('Invalid access token')\n }\n\n let planId = planIdOverride ?? decodedAccessToken.accepted?.planId\n const subscriberAddress = decodedAccessToken.payload?.authorization?.from\n\n // If planId is not available, try to get it from the agent's plans\n if (!planId) {\n try {\n const agentPlans = await this.payments.agents.getAgentPlans(agentId)\n if (agentPlans && Array.isArray(agentPlans.plans) && agentPlans.plans.length > 0) {\n planId = agentPlans.plans[0].planId || agentPlans.plans[0].id\n }\n } catch {\n // Ignore errors fetching plans\n }\n }\n\n if (!planId || !subscriberAddress) {\n throw new Error(\n 'Cannot determine plan_id or subscriber_address from token (expected accepted.planId and payload.authorization.from)',\n )\n }\n\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint,\n agentId,\n httpVerb: 'POST',\n })\n\n const result = await this.payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: accessToken,\n maxAmount,\n })\n\n if (!result.isValid) {\n throw new Error('Permission verification failed')\n }\n\n return { planId, subscriberAddress, agentRequest: result.agentRequest }\n }\n\n /**\n * Authenticate an MCP request\n */\n async authenticate(\n extra: any,\n options: { planId?: string; maxAmount?: bigint } = {},\n agentId: string,\n serverName: string,\n name: string,\n kind: 'tool' | 'resource' | 'prompt',\n argsOrVars: any,\n ): Promise<AuthResult> {\n const authHeader = this.extractAuthHeaderFromContext(extra)\n if (!authHeader) {\n throw createRpcError(ERROR_CODES.PaymentRequired, 'Authorization required', {\n reason: 'missing',\n })\n }\n\n return this.verifyWithFallback({\n accessToken: stripBearer(authHeader),\n logicalUrl: buildLogicalUrl({ kind, serverName, name, argsOrVars }),\n httpUrl: this.buildHttpUrlFromContext(),\n maxAmount: options.maxAmount ?? 1n,\n agentId,\n planIdOverride: options.planId,\n })\n }\n\n /**\n * Authenticate generic MCP meta operations (e.g., initialize, tools/list, resources/list, prompts/list).\n * Returns an AuthResult compatible with paywall flows (without redeem step).\n */\n async authenticateMeta(\n extra: any,\n options: { planId?: string; maxAmount?: bigint } = {},\n agentId: string,\n serverName: string,\n method: string,\n ): Promise<AuthResult> {\n const authHeader = this.extractAuthHeaderFromContext(extra)\n if (!authHeader) {\n throw createRpcError(ERROR_CODES.PaymentRequired, 'Authorization required', {\n reason: 'missing',\n })\n }\n\n return this.verifyWithFallback({\n accessToken: stripBearer(authHeader),\n logicalUrl: buildLogicalMetaUrl(serverName, method),\n httpUrl: this.buildHttpUrlFromContext(),\n maxAmount: options.maxAmount ?? 1n,\n agentId,\n planIdOverride: options.planId,\n })\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../../../src/mcp/core/paywall.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAMjD,OAAO,EACL,SAAS,EAET,aAAa,EACb,eAAe,EACf,WAAW,EACZ,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAE7D;;GAEG;AACH,qBAAa,gBAAgB;IAQzB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,cAAc;IARxB,OAAO,CAAC,MAAM,CAGb;gBAGS,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,oBAAoB,EACnC,cAAc,EAAE,sBAAsB;IAGhD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAOnC;;OAEG;IAEH,OAAO,CAAC,KAAK,GAAG,GAAG,EACjB,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,EACzD,OAAO,EAAE,WAAW,GAAG,aAAa,GACnC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;IAC7C,OAAO,CACL,OAAO,EAAE,CACP,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC5C,KAAK,CAAC,EAAE,GAAG,KACR,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,EACvB,OAAO,EAAE,eAAe,GACvB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;IAKxF;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiH5B;;OAEG;YACW,aAAa;
|
|
1
|
+
{"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../../../src/mcp/core/paywall.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAMjD,OAAO,EACL,SAAS,EAET,aAAa,EACb,eAAe,EACf,WAAW,EACZ,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAE7D;;GAEG;AACH,qBAAa,gBAAgB;IAQzB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,cAAc;IARxB,OAAO,CAAC,MAAM,CAGb;gBAGS,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,oBAAoB,EACnC,cAAc,EAAE,sBAAsB;IAGhD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAOnC;;OAEG;IAEH,OAAO,CAAC,KAAK,GAAG,GAAG,EACjB,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,EACzD,OAAO,EAAE,WAAW,GAAG,aAAa,GACnC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;IAC7C,OAAO,CACL,OAAO,EAAE,CACP,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC5C,KAAK,CAAC,EAAE,GAAG,KACR,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,EACvB,OAAO,EAAE,eAAe,GACvB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;IAKxF;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiH5B;;OAEG;YACW,aAAa;CA8D5B"}
|
package/dist/mcp/core/paywall.js
CHANGED
|
@@ -42,7 +42,7 @@ export class PaywallDecorator {
|
|
|
42
42
|
const extra = isResource ? allArgs[2] : allArgs[1];
|
|
43
43
|
const argsOrVars = isResource ? allArgs[1] : allArgs[0];
|
|
44
44
|
// 1. Authenticate request
|
|
45
|
-
const authResult = await this.authenticator.authenticate(extra, { planId: options?.planId }, this.config.agentId, this.config.serverName, name, kind, argsOrVars);
|
|
45
|
+
const authResult = await this.authenticator.authenticate(extra, { planId: options?.planId, maxAmount: options?.maxAmount }, this.config.agentId, this.config.serverName, name, kind, argsOrVars);
|
|
46
46
|
// 2. Pre-calculate credits if they are fixed (not a function)
|
|
47
47
|
// This allows handlers to access credits during execution
|
|
48
48
|
const creditsOption = options?.credits;
|
|
@@ -71,31 +71,29 @@ export class PaywallDecorator {
|
|
|
71
71
|
// 6. If the result is an AsyncIterable (stream), redeem on completion
|
|
72
72
|
if (isAsyncIterable(result)) {
|
|
73
73
|
const onFinally = async () => {
|
|
74
|
-
return await this.redeemCredits(effectivePlanId, authResult.token, authResult.subscriberAddress, credits, options, authResult.agentId, authResult.logicalUrl, 'POST');
|
|
74
|
+
return await this.redeemCredits(effectivePlanId, authResult.token, authResult.subscriberAddress, credits, options, authResult.agentId, authResult.logicalUrl, authResult.httpUrl, 'POST');
|
|
75
75
|
};
|
|
76
76
|
return wrapAsyncIterable(result, onFinally, effectivePlanId, authResult.subscriberAddress, credits);
|
|
77
77
|
}
|
|
78
78
|
// 7. Non-streaming: redeem immediately
|
|
79
|
-
const creditsResult = await this.redeemCredits(effectivePlanId, authResult.token, authResult.subscriberAddress, credits, options, authResult.agentId, authResult.logicalUrl, 'POST');
|
|
80
|
-
|
|
81
|
-
result._meta
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
}
|
|
79
|
+
const creditsResult = await this.redeemCredits(effectivePlanId, authResult.token, authResult.subscriberAddress, credits, options, authResult.agentId, authResult.logicalUrl, 'POST', authResult.httpUrl);
|
|
80
|
+
result._meta = {
|
|
81
|
+
...result._meta,
|
|
82
|
+
...(creditsResult.transaction && { txHash: creditsResult.transaction }),
|
|
83
|
+
creditsRedeemed: creditsResult.success ? (creditsResult.creditsRedeemed ?? credits.toString()) : '0',
|
|
84
|
+
remainingBalance: creditsResult.remainingBalance,
|
|
85
|
+
planId: authResult.planId,
|
|
86
|
+
subscriberAddress: authResult.subscriberAddress,
|
|
87
|
+
success: creditsResult.success,
|
|
88
|
+
...(creditsResult.errorReason && { errorReason: creditsResult.errorReason }),
|
|
89
|
+
};
|
|
92
90
|
return result;
|
|
93
91
|
};
|
|
94
92
|
}
|
|
95
93
|
/**
|
|
96
94
|
* Redeem credits after successful request
|
|
97
95
|
*/
|
|
98
|
-
async redeemCredits(planId, token, subscriberAddress, credits, options, agentId, endpoint, httpVerb) {
|
|
96
|
+
async redeemCredits(planId, token, subscriberAddress, credits, options, agentId, endpoint, fallbackEndpoint, httpVerb) {
|
|
99
97
|
let ret = {
|
|
100
98
|
success: true,
|
|
101
99
|
transaction: '',
|
|
@@ -115,11 +113,34 @@ export class PaywallDecorator {
|
|
|
115
113
|
});
|
|
116
114
|
}
|
|
117
115
|
}
|
|
118
|
-
catch (
|
|
116
|
+
catch (primaryError) {
|
|
117
|
+
// If logical URL fails and we have an HTTP URL fallback, retry with it
|
|
118
|
+
let lastError = primaryError;
|
|
119
|
+
if (fallbackEndpoint) {
|
|
120
|
+
try {
|
|
121
|
+
const paymentRequired = buildPaymentRequired(planId, {
|
|
122
|
+
endpoint: fallbackEndpoint,
|
|
123
|
+
agentId,
|
|
124
|
+
httpVerb: httpVerb,
|
|
125
|
+
});
|
|
126
|
+
ret = await this.payments.facilitator.settlePermissions({
|
|
127
|
+
paymentRequired,
|
|
128
|
+
x402AccessToken: token,
|
|
129
|
+
maxAmount: credits,
|
|
130
|
+
});
|
|
131
|
+
return ret;
|
|
132
|
+
}
|
|
133
|
+
catch (fallbackError) {
|
|
134
|
+
// Fallback also failed, use fallback error as the reported error
|
|
135
|
+
lastError = fallbackError;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
ret.success = false;
|
|
139
|
+
ret.errorReason = lastError instanceof Error ? lastError.message : String(lastError);
|
|
119
140
|
if (options.onRedeemError === 'propagate') {
|
|
120
|
-
throw createRpcError(ERROR_CODES.Misconfiguration,
|
|
141
|
+
throw createRpcError(ERROR_CODES.Misconfiguration, `Failed to redeem credits: ${ret.errorReason}`);
|
|
121
142
|
}
|
|
122
|
-
// Default:
|
|
143
|
+
// Default: attach error to result but don't throw
|
|
123
144
|
}
|
|
124
145
|
return ret;
|
|
125
146
|
}
|
|
@@ -149,11 +170,12 @@ function wrapAsyncIterable(iterable, onFinally, planId, subscriberAddress, credi
|
|
|
149
170
|
_meta: {
|
|
150
171
|
// Only include txHash if it has a value
|
|
151
172
|
...(creditsResult?.transaction && { txHash: creditsResult.transaction }),
|
|
152
|
-
creditsRedeemed: creditsResult?.creditsRedeemed ?? credits.toString(),
|
|
173
|
+
creditsRedeemed: creditsResult?.success ? (creditsResult.creditsRedeemed ?? credits.toString()) : '0',
|
|
153
174
|
remainingBalance: creditsResult?.remainingBalance,
|
|
154
175
|
planId: planId,
|
|
155
176
|
subscriberAddress: subscriberAddress,
|
|
156
177
|
success: creditsResult?.success || false,
|
|
178
|
+
...(creditsResult?.errorReason && { errorReason: creditsResult.errorReason }),
|
|
157
179
|
},
|
|
158
180
|
};
|
|
159
181
|
yield metadataChunk;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paywall.js","sourceRoot":"","sources":["../../../src/mcp/core/paywall.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main paywall decorator for MCP handlers (tools, resources, prompts)\n */\nimport { Address } from '../../common/types.js'\nimport type { Payments } from '../../payments.js'\nimport {\n buildPaymentRequired,\n type SettlePermissionsResult,\n type X402PaymentRequired,\n} from '../../x402/facilitator-api.js'\nimport {\n McpConfig,\n PaywallOptions,\n PromptOptions,\n ResourceOptions,\n ToolOptions,\n} from '../types/paywall.types.js'\nimport { ERROR_CODES, createRpcError } from '../utils/errors.js'\nimport { PaywallAuthenticator } from './auth.js'\nimport { CreditsContextProvider } from './credits-context.js'\n\n/**\n * Main class for creating paywall-protected MCP handlers\n */\nexport class PaywallDecorator {\n // Internal config ensures serverName is always a concrete string\n private config: { agentId: string; serverName: string } = {\n agentId: '',\n serverName: 'mcp-server',\n }\n\n constructor(\n private payments: Payments,\n private authenticator: PaywallAuthenticator,\n private creditsContext: CreditsContextProvider,\n ) {}\n\n /**\n * Configure the paywall with agent and server information\n */\n configure(options: McpConfig): void {\n this.config = {\n agentId: options.agentId || this.config.agentId,\n serverName: options.serverName ?? this.config.serverName,\n }\n }\n\n /**\n * Create a paywall-protected handler (uncurried version only)\n */\n // Overloads per kind for stronger typing\n protect<TArgs = any>(\n handler: (args: TArgs, extra?: any) => Promise<any> | any,\n options: ToolOptions | PromptOptions,\n ): (args: TArgs, extra?: any) => Promise<any>\n protect(\n handler: (\n uri: URL,\n variables: Record<string, string | string[]>,\n extra?: any,\n ) => Promise<any> | any,\n options: ResourceOptions,\n ): (uri: URL, variables: Record<string, string | string[]>, extra?: any) => Promise<any>\n protect(handler: any, options: PaywallOptions): any {\n return this.createWrappedHandler(handler, options)\n }\n\n /**\n * Internal method to create the wrapped handler\n */\n private createWrappedHandler<TArgs = any>(\n handler: (args: TArgs, extra?: any) => Promise<any> | any,\n options: PaywallOptions,\n ): (...allArgs: any[]) => Promise<any> {\n return async (...allArgs: any[]): Promise<any> => {\n // Validate configuration\n if (!this.config.agentId) {\n throw createRpcError(\n ERROR_CODES.Misconfiguration,\n 'Server misconfiguration: missing agentId',\n )\n }\n\n const kind = options?.kind ?? 'tool'\n const name = options?.name ?? 'unnamed'\n\n // Detect resource signature: (url, variables, extra)\n const isResource = allArgs.length >= 2 && allArgs[0] instanceof URL\n const extra = isResource ? allArgs[2] : allArgs[1]\n const argsOrVars = isResource ? allArgs[1] : allArgs[0]\n\n // 1. Authenticate request\n const authResult = await this.authenticator.authenticate(\n extra,\n { planId: options?.planId },\n this.config.agentId,\n this.config.serverName,\n name,\n kind,\n argsOrVars,\n )\n\n // 2. Pre-calculate credits if they are fixed (not a function)\n // This allows handlers to access credits during execution\n const creditsOption = options?.credits\n const isFixedCredits = typeof creditsOption === 'bigint' || creditsOption === undefined\n const preCalculatedCredits = isFixedCredits\n ? this.creditsContext.resolve(creditsOption, argsOrVars, null, authResult)\n : undefined\n\n // Determine effective planId: explicit option overrides token-derived value\n const effectivePlanId = options?.planId ?? authResult.planId\n\n // 3. Build PaywallContext for handler (with extra wrapper for backward compatibility)\n const paywallContext = {\n authResult,\n credits: preCalculatedCredits,\n planId: authResult.planId,\n subscriberAddress: authResult.subscriberAddress,\n agentRequest: authResult.agentRequest,\n }\n\n // 4. Execute original handler with context\n const result = await (handler as any)(...allArgs, paywallContext)\n\n // 5. Resolve final credits to burn (may be different if credits are dynamic)\n const credits = isFixedCredits\n ? (preCalculatedCredits ?? 1n)\n : this.creditsContext.resolve(creditsOption, argsOrVars, result, authResult)\n\n // Update context with final resolved credits\n paywallContext.credits = credits\n\n // 6. If the result is an AsyncIterable (stream), redeem on completion\n if (isAsyncIterable(result)) {\n const onFinally = async () => {\n return await this.redeemCredits(\n effectivePlanId,\n authResult.token,\n authResult.subscriberAddress,\n credits,\n options,\n authResult.agentId,\n authResult.logicalUrl,\n 'POST',\n )\n }\n return wrapAsyncIterable(\n result,\n onFinally,\n effectivePlanId,\n authResult.subscriberAddress,\n credits,\n )\n }\n\n // 7. Non-streaming: redeem immediately\n const creditsResult = await this.redeemCredits(\n effectivePlanId,\n authResult.token,\n authResult.subscriberAddress,\n credits,\n options,\n authResult.agentId,\n authResult.logicalUrl,\n 'POST',\n )\n if (creditsResult.success) {\n result._meta = {\n ...result._meta,\n // Only include txHash if it has a value\n ...(creditsResult.transaction && { txHash: creditsResult.transaction }),\n creditsRedeemed: creditsResult.creditsRedeemed ?? credits.toString(),\n remainingBalance: creditsResult.remainingBalance,\n planId: authResult.planId,\n subscriberAddress: authResult.subscriberAddress,\n success: true,\n }\n }\n return result\n }\n }\n\n /**\n * Redeem credits after successful request\n */\n private async redeemCredits(\n planId: string,\n token: string,\n subscriberAddress: Address,\n credits: bigint,\n options: PaywallOptions,\n agentId?: string,\n endpoint?: string,\n httpVerb?: string,\n ): Promise<SettlePermissionsResult> {\n let ret: SettlePermissionsResult = {\n success: true,\n transaction: '',\n network: '',\n }\n try {\n if (credits && credits > 0n && subscriberAddress && planId) {\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: endpoint || '',\n agentId,\n httpVerb: httpVerb,\n })\n\n ret = await this.payments.facilitator.settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: credits,\n })\n }\n } catch (e) {\n if (options.onRedeemError === 'propagate') {\n throw createRpcError(ERROR_CODES.Misconfiguration, 'Failed to redeem credits')\n }\n // Default: ignore redemption errors\n }\n return ret\n }\n}\n\n/**\n * Type guard to detect AsyncIterable values.\n */\nfunction isAsyncIterable<T = unknown>(value: any): value is AsyncIterable<T> {\n return value != null && typeof value[Symbol.asyncIterator] === 'function'\n}\n\n/**\n * Wrap an AsyncIterable with metadata injection at the end of the stream\n */\nfunction wrapAsyncIterable<T>(\n iterable: AsyncIterable<T>,\n onFinally: () => Promise<any>,\n planId: string,\n subscriberAddress: Address,\n credits: bigint,\n) {\n async function* generator() {\n let creditsResult: any = null\n try {\n for await (const chunk of iterable) {\n yield chunk as T\n }\n } finally {\n creditsResult = await onFinally()\n }\n\n // Yield a _meta chunk at the end with the redemption result\n const metadataChunk = {\n _meta: {\n // Only include txHash if it has a value\n ...(creditsResult?.transaction && { txHash: creditsResult.transaction }),\n creditsRedeemed: creditsResult?.creditsRedeemed ?? credits.toString(),\n remainingBalance: creditsResult?.remainingBalance,\n planId: planId,\n subscriberAddress: subscriberAddress,\n success: creditsResult?.success || false,\n },\n }\n yield metadataChunk as T\n }\n return generator()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"paywall.js","sourceRoot":"","sources":["../../../src/mcp/core/paywall.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main paywall decorator for MCP handlers (tools, resources, prompts)\n */\nimport { Address } from '../../common/types.js'\nimport type { Payments } from '../../payments.js'\nimport {\n buildPaymentRequired,\n type SettlePermissionsResult,\n type X402PaymentRequired,\n} from '../../x402/facilitator-api.js'\nimport {\n McpConfig,\n PaywallOptions,\n PromptOptions,\n ResourceOptions,\n ToolOptions,\n} from '../types/paywall.types.js'\nimport { ERROR_CODES, createRpcError } from '../utils/errors.js'\nimport { PaywallAuthenticator } from './auth.js'\nimport { CreditsContextProvider } from './credits-context.js'\n\n/**\n * Main class for creating paywall-protected MCP handlers\n */\nexport class PaywallDecorator {\n // Internal config ensures serverName is always a concrete string\n private config: { agentId: string; serverName: string } = {\n agentId: '',\n serverName: 'mcp-server',\n }\n\n constructor(\n private payments: Payments,\n private authenticator: PaywallAuthenticator,\n private creditsContext: CreditsContextProvider,\n ) {}\n\n /**\n * Configure the paywall with agent and server information\n */\n configure(options: McpConfig): void {\n this.config = {\n agentId: options.agentId || this.config.agentId,\n serverName: options.serverName ?? this.config.serverName,\n }\n }\n\n /**\n * Create a paywall-protected handler (uncurried version only)\n */\n // Overloads per kind for stronger typing\n protect<TArgs = any>(\n handler: (args: TArgs, extra?: any) => Promise<any> | any,\n options: ToolOptions | PromptOptions,\n ): (args: TArgs, extra?: any) => Promise<any>\n protect(\n handler: (\n uri: URL,\n variables: Record<string, string | string[]>,\n extra?: any,\n ) => Promise<any> | any,\n options: ResourceOptions,\n ): (uri: URL, variables: Record<string, string | string[]>, extra?: any) => Promise<any>\n protect(handler: any, options: PaywallOptions): any {\n return this.createWrappedHandler(handler, options)\n }\n\n /**\n * Internal method to create the wrapped handler\n */\n private createWrappedHandler<TArgs = any>(\n handler: (args: TArgs, extra?: any) => Promise<any> | any,\n options: PaywallOptions,\n ): (...allArgs: any[]) => Promise<any> {\n return async (...allArgs: any[]): Promise<any> => {\n // Validate configuration\n if (!this.config.agentId) {\n throw createRpcError(\n ERROR_CODES.Misconfiguration,\n 'Server misconfiguration: missing agentId',\n )\n }\n\n const kind = options?.kind ?? 'tool'\n const name = options?.name ?? 'unnamed'\n\n // Detect resource signature: (url, variables, extra)\n const isResource = allArgs.length >= 2 && allArgs[0] instanceof URL\n const extra = isResource ? allArgs[2] : allArgs[1]\n const argsOrVars = isResource ? allArgs[1] : allArgs[0]\n\n // 1. Authenticate request\n const authResult = await this.authenticator.authenticate(\n extra,\n { planId: options?.planId, maxAmount: options?.maxAmount },\n this.config.agentId,\n this.config.serverName,\n name,\n kind,\n argsOrVars,\n )\n\n // 2. Pre-calculate credits if they are fixed (not a function)\n // This allows handlers to access credits during execution\n const creditsOption = options?.credits\n const isFixedCredits = typeof creditsOption === 'bigint' || creditsOption === undefined\n const preCalculatedCredits = isFixedCredits\n ? this.creditsContext.resolve(creditsOption, argsOrVars, null, authResult)\n : undefined\n\n // Determine effective planId: explicit option overrides token-derived value\n const effectivePlanId = options?.planId ?? authResult.planId\n\n // 3. Build PaywallContext for handler (with extra wrapper for backward compatibility)\n const paywallContext = {\n authResult,\n credits: preCalculatedCredits,\n planId: authResult.planId,\n subscriberAddress: authResult.subscriberAddress,\n agentRequest: authResult.agentRequest,\n }\n\n // 4. Execute original handler with context\n const result = await (handler as any)(...allArgs, paywallContext)\n\n // 5. Resolve final credits to burn (may be different if credits are dynamic)\n const credits = isFixedCredits\n ? (preCalculatedCredits ?? 1n)\n : this.creditsContext.resolve(creditsOption, argsOrVars, result, authResult)\n\n // Update context with final resolved credits\n paywallContext.credits = credits\n\n // 6. If the result is an AsyncIterable (stream), redeem on completion\n if (isAsyncIterable(result)) {\n const onFinally = async () => {\n return await this.redeemCredits(\n effectivePlanId,\n authResult.token,\n authResult.subscriberAddress,\n credits,\n options,\n authResult.agentId,\n authResult.logicalUrl,\n authResult.httpUrl,\n 'POST',\n )\n }\n return wrapAsyncIterable(\n result,\n onFinally,\n effectivePlanId,\n authResult.subscriberAddress,\n credits,\n )\n }\n\n // 7. Non-streaming: redeem immediately\n const creditsResult = await this.redeemCredits(\n effectivePlanId,\n authResult.token,\n authResult.subscriberAddress,\n credits,\n options,\n authResult.agentId,\n authResult.logicalUrl,\n 'POST',\n authResult.httpUrl,\n )\n result._meta = {\n ...result._meta,\n ...(creditsResult.transaction && { txHash: creditsResult.transaction }),\n creditsRedeemed: creditsResult.success ? (creditsResult.creditsRedeemed ?? credits.toString()) : '0',\n remainingBalance: creditsResult.remainingBalance,\n planId: authResult.planId,\n subscriberAddress: authResult.subscriberAddress,\n success: creditsResult.success,\n ...(creditsResult.errorReason && { errorReason: creditsResult.errorReason }),\n }\n return result\n }\n }\n\n /**\n * Redeem credits after successful request\n */\n private async redeemCredits(\n planId: string,\n token: string,\n subscriberAddress: Address,\n credits: bigint,\n options: PaywallOptions,\n agentId?: string,\n endpoint?: string,\n fallbackEndpoint?: string,\n httpVerb?: string,\n ): Promise<SettlePermissionsResult> {\n let ret: SettlePermissionsResult = {\n success: true,\n transaction: '',\n network: '',\n }\n try {\n if (credits && credits > 0n && subscriberAddress && planId) {\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: endpoint || '',\n agentId,\n httpVerb: httpVerb,\n })\n\n ret = await this.payments.facilitator.settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: credits,\n })\n }\n } catch (primaryError) {\n // If logical URL fails and we have an HTTP URL fallback, retry with it\n let lastError: unknown = primaryError\n if (fallbackEndpoint) {\n try {\n const paymentRequired: X402PaymentRequired = buildPaymentRequired(planId, {\n endpoint: fallbackEndpoint,\n agentId,\n httpVerb: httpVerb,\n })\n\n ret = await this.payments.facilitator.settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: credits,\n })\n return ret\n } catch (fallbackError) {\n // Fallback also failed, use fallback error as the reported error\n lastError = fallbackError\n }\n }\n\n ret.success = false\n ret.errorReason = lastError instanceof Error ? lastError.message : String(lastError)\n if (options.onRedeemError === 'propagate') {\n throw createRpcError(ERROR_CODES.Misconfiguration, `Failed to redeem credits: ${ret.errorReason}`)\n }\n // Default: attach error to result but don't throw\n }\n return ret\n }\n}\n\n/**\n * Type guard to detect AsyncIterable values.\n */\nfunction isAsyncIterable<T = unknown>(value: any): value is AsyncIterable<T> {\n return value != null && typeof value[Symbol.asyncIterator] === 'function'\n}\n\n/**\n * Wrap an AsyncIterable with metadata injection at the end of the stream\n */\nfunction wrapAsyncIterable<T>(\n iterable: AsyncIterable<T>,\n onFinally: () => Promise<any>,\n planId: string,\n subscriberAddress: Address,\n credits: bigint,\n) {\n async function* generator() {\n let creditsResult: any = null\n try {\n for await (const chunk of iterable) {\n yield chunk as T\n }\n } finally {\n creditsResult = await onFinally()\n }\n\n // Yield a _meta chunk at the end with the redemption result\n const metadataChunk = {\n _meta: {\n // Only include txHash if it has a value\n ...(creditsResult?.transaction && { txHash: creditsResult.transaction }),\n creditsRedeemed: creditsResult?.success ? (creditsResult.creditsRedeemed ?? credits.toString()) : '0',\n remainingBalance: creditsResult?.remainingBalance,\n planId: planId,\n subscriberAddress: subscriberAddress,\n success: creditsResult?.success || false,\n ...(creditsResult?.errorReason && { errorReason: creditsResult.errorReason }),\n },\n }\n yield metadataChunk as T\n }\n return generator()\n}\n"]}
|
|
@@ -39,6 +39,11 @@ export interface BasePaywallOptions {
|
|
|
39
39
|
* If omitted, the plan is inferred from the X402 access token.
|
|
40
40
|
*/
|
|
41
41
|
planId?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum amount of credits to verify during authentication.
|
|
44
|
+
* Defaults to 1n if not specified.
|
|
45
|
+
*/
|
|
46
|
+
maxAmount?: bigint;
|
|
42
47
|
}
|
|
43
48
|
export interface ToolOptions extends BasePaywallOptions {
|
|
44
49
|
kind: 'tool';
|
|
@@ -60,6 +65,7 @@ export interface AuthResult {
|
|
|
60
65
|
token: string;
|
|
61
66
|
agentId: string;
|
|
62
67
|
logicalUrl: string;
|
|
68
|
+
httpUrl?: string;
|
|
63
69
|
planId: string;
|
|
64
70
|
subscriberAddress: Address;
|
|
65
71
|
agentRequest?: StartAgentRequest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paywall.types.d.ts","sourceRoot":"","sources":["../../../src/mcp/types/paywall.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAEvE;;GAEG;AACH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,GAAG,CAAA;IACX,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;CACF;AAED;;GAEG;AACH;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,CAAC,CAAA;AAEtE;;GAEG;AACH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,aAAa,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;IACtC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"paywall.types.d.ts","sourceRoot":"","sources":["../../../src/mcp/types/paywall.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAEvE;;GAEG;AACH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,GAAG,CAAA;IACX,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;CACF;AAED;;GAEG;AACH;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,CAAC,CAAA;AAEtE;;GAEG;AACH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,aAAa,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;IACtC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,kBAAkB;IACrD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACvD,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,eAAe,GAAG,aAAa,CAAA;AAE1E;;GAEG;AAGH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,iBAAiB,CAAA;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,UAAU,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,iBAAiB,CAAA;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paywall.types.js","sourceRoot":"","sources":["../../../src/mcp/types/paywall.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Type definitions for MCP paywall functionality\n */\nimport type { Address, StartAgentRequest } from '../../common/types.js'\n\n/**\n * Context provided to dynamic credits functions\n */\n/**\n * Context provided to dynamic credits functions.\n */\nexport interface CreditsContext {\n args: unknown\n result: any\n request: {\n authHeader: string\n logicalUrl: string\n toolName: string\n }\n}\n\n/**\n * Credits calculation option - can be fixed amount or dynamic function\n */\n/**\n * Credits option: fixed bigint or a function receiving {@link CreditsContext}.\n */\nexport type CreditsOption = bigint | ((ctx: CreditsContext) => bigint)\n\n/**\n * Configuration options for paywall protection\n */\n/**\n * Unified paywall options for tools, resources and prompts.\n */\nexport interface BasePaywallOptions {\n name: string\n credits?: CreditsOption\n onRedeemError?: 'ignore' | 'propagate'\n /**\n * Optional override for the Nevermined plan to charge against.\n * If omitted, the plan is inferred from the X402 access token.\n */\n planId?: string\n}\n\nexport interface ToolOptions extends BasePaywallOptions {\n kind: 'tool'\n}\n\nexport interface ResourceOptions extends BasePaywallOptions {\n kind: 'resource'\n}\n\nexport interface PromptOptions extends BasePaywallOptions {\n kind: 'prompt'\n}\n\nexport type PaywallOptions = ToolOptions | ResourceOptions | PromptOptions\n\n/**\n * Options for decorating tools with paywall protection\n */\n// decorate* helpers removed\n\n/**\n * Authentication result from paywall validation\n */\nexport interface AuthResult {\n token: string\n agentId: string\n logicalUrl: string\n planId: string\n subscriberAddress: Address\n agentRequest?: StartAgentRequest\n}\n\n/**\n * Context provided to paywall-protected handlers\n */\nexport interface PaywallContext {\n authResult: AuthResult\n credits?: bigint\n planId: string\n subscriberAddress: Address\n agentRequest?: StartAgentRequest\n}\n\n/**\n * MCP integration configuration\n */\nexport interface McpConfig {\n agentId: string\n serverName?: string\n}\n"]}
|
|
1
|
+
{"version":3,"file":"paywall.types.js","sourceRoot":"","sources":["../../../src/mcp/types/paywall.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Type definitions for MCP paywall functionality\n */\nimport type { Address, StartAgentRequest } from '../../common/types.js'\n\n/**\n * Context provided to dynamic credits functions\n */\n/**\n * Context provided to dynamic credits functions.\n */\nexport interface CreditsContext {\n args: unknown\n result: any\n request: {\n authHeader: string\n logicalUrl: string\n toolName: string\n }\n}\n\n/**\n * Credits calculation option - can be fixed amount or dynamic function\n */\n/**\n * Credits option: fixed bigint or a function receiving {@link CreditsContext}.\n */\nexport type CreditsOption = bigint | ((ctx: CreditsContext) => bigint)\n\n/**\n * Configuration options for paywall protection\n */\n/**\n * Unified paywall options for tools, resources and prompts.\n */\nexport interface BasePaywallOptions {\n name: string\n credits?: CreditsOption\n onRedeemError?: 'ignore' | 'propagate'\n /**\n * Optional override for the Nevermined plan to charge against.\n * If omitted, the plan is inferred from the X402 access token.\n */\n planId?: string\n /**\n * Maximum amount of credits to verify during authentication.\n * Defaults to 1n if not specified.\n */\n maxAmount?: bigint\n}\n\nexport interface ToolOptions extends BasePaywallOptions {\n kind: 'tool'\n}\n\nexport interface ResourceOptions extends BasePaywallOptions {\n kind: 'resource'\n}\n\nexport interface PromptOptions extends BasePaywallOptions {\n kind: 'prompt'\n}\n\nexport type PaywallOptions = ToolOptions | ResourceOptions | PromptOptions\n\n/**\n * Options for decorating tools with paywall protection\n */\n// decorate* helpers removed\n\n/**\n * Authentication result from paywall validation\n */\nexport interface AuthResult {\n token: string\n agentId: string\n logicalUrl: string\n httpUrl?: string\n planId: string\n subscriberAddress: Address\n agentRequest?: StartAgentRequest\n}\n\n/**\n * Context provided to paywall-protected handlers\n */\nexport interface PaywallContext {\n authResult: AuthResult\n credits?: bigint\n planId: string\n subscriberAddress: Address\n agentRequest?: StartAgentRequest\n}\n\n/**\n * MCP integration configuration\n */\nexport interface McpConfig {\n agentId: string\n serverName?: string\n}\n"]}
|