@atxp/server 0.2.22 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/atxpContext.js +9 -6
  2. package/dist/atxpContext.js.map +1 -1
  3. package/dist/core/mcp.js +39 -0
  4. package/dist/core/mcp.js.map +1 -0
  5. package/dist/{oAuthChallenge.js → core/oauth.js} +21 -12
  6. package/dist/core/oauth.js.map +1 -0
  7. package/dist/{token.js → core/token.js} +13 -7
  8. package/dist/core/token.js.map +1 -0
  9. package/dist/getResource.js +23 -4
  10. package/dist/getResource.js.map +1 -1
  11. package/dist/index.cjs +654 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.ts +197 -5
  14. package/dist/index.js +609 -5
  15. package/dist/index.js.map +1 -1
  16. package/dist/node/getRawBody.js +35 -0
  17. package/dist/node/getRawBody.js.map +1 -0
  18. package/dist/node/http.js +49 -0
  19. package/dist/node/http.js.map +1 -0
  20. package/dist/node/oauth.js +41 -0
  21. package/dist/node/oauth.js.map +1 -0
  22. package/dist/node/token.js +15 -0
  23. package/dist/node/token.js.map +1 -0
  24. package/dist/oAuthMetadata.js +6 -12
  25. package/dist/oAuthMetadata.js.map +1 -1
  26. package/dist/paymentServer.js +5 -20
  27. package/dist/paymentServer.js.map +1 -1
  28. package/dist/protectedResourceMetadata.js +10 -16
  29. package/dist/protectedResourceMetadata.js.map +1 -1
  30. package/dist/requirePayment.js +7 -4
  31. package/dist/requirePayment.js.map +1 -1
  32. package/dist/serverConfig.js +37 -0
  33. package/dist/serverConfig.js.map +1 -0
  34. package/dist/serverTestHelpers.d.ts +70 -21
  35. package/dist/serverTestHelpers.js +55 -24
  36. package/dist/serverTestHelpers.js.map +1 -1
  37. package/dist/types.js +4 -2
  38. package/dist/types.js.map +1 -1
  39. package/dist/webapi/mcp.js +25 -0
  40. package/dist/webapi/mcp.js.map +1 -0
  41. package/dist/webapi/oauth.js +43 -0
  42. package/dist/webapi/oauth.js.map +1 -0
  43. package/dist/webapi/token.js +15 -0
  44. package/dist/webapi/token.js.map +1 -0
  45. package/package.json +24 -10
  46. package/dist/atxpContext.d.ts +0 -6
  47. package/dist/atxpContext.d.ts.map +0 -1
  48. package/dist/atxpServer.d.ts +0 -12
  49. package/dist/atxpServer.d.ts.map +0 -1
  50. package/dist/atxpServer.js +0 -101
  51. package/dist/atxpServer.js.map +0 -1
  52. package/dist/getResource.d.ts +0 -4
  53. package/dist/getResource.d.ts.map +0 -1
  54. package/dist/http.d.ts +0 -7
  55. package/dist/http.d.ts.map +0 -1
  56. package/dist/http.js +0 -51
  57. package/dist/http.js.map +0 -1
  58. package/dist/index.d.ts.map +0 -1
  59. package/dist/oAuthChallenge.d.ts +0 -4
  60. package/dist/oAuthChallenge.d.ts.map +0 -1
  61. package/dist/oAuthChallenge.js.map +0 -1
  62. package/dist/oAuthMetadata.d.ts +0 -6
  63. package/dist/oAuthMetadata.d.ts.map +0 -1
  64. package/dist/paymentServer.d.ts +0 -62
  65. package/dist/paymentServer.d.ts.map +0 -1
  66. package/dist/protectedResourceMetadata.d.ts +0 -5
  67. package/dist/protectedResourceMetadata.d.ts.map +0 -1
  68. package/dist/requirePayment.d.ts +0 -3
  69. package/dist/requirePayment.d.ts.map +0 -1
  70. package/dist/serverTestHelpers.d.ts.map +0 -1
  71. package/dist/token.d.ts +0 -4
  72. package/dist/token.d.ts.map +0 -1
  73. package/dist/token.js.map +0 -1
  74. package/dist/types.d.ts +0 -60
  75. package/dist/types.d.ts.map +0 -1
package/dist/index.cjs ADDED
@@ -0,0 +1,654 @@
1
+ 'use strict';
2
+
3
+ var async_hooks = require('async_hooks');
4
+ var contentType = require('content-type');
5
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
6
+ var common = require('@atxp/common');
7
+
8
+ function _interopNamespaceDefault(e) {
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var contentType__namespace = /*#__PURE__*/_interopNamespaceDefault(contentType);
26
+
27
+ exports.TokenProblem = void 0;
28
+ (function (TokenProblem) {
29
+ TokenProblem["NO_TOKEN"] = "NO-TOKEN";
30
+ TokenProblem["NON_BEARER_AUTH_HEADER"] = "NON-BEARER-AUTH-HEADER";
31
+ TokenProblem["INVALID_TOKEN"] = "INVALID-TOKEN";
32
+ TokenProblem["INVALID_AUDIENCE"] = "INVALID-AUDIENCE";
33
+ TokenProblem["NON_SUFFICIENT_FUNDS"] = "NON-SUFFICIENT-FUNDS";
34
+ TokenProblem["INTROSPECT_ERROR"] = "INTROSPECT-ERROR";
35
+ })(exports.TokenProblem || (exports.TokenProblem = {}));
36
+
37
+ const contextStorage = new async_hooks.AsyncLocalStorage();
38
+ function getATXPConfig() {
39
+ const context = contextStorage.getStore();
40
+ return context?.config ?? null;
41
+ }
42
+ function getATXPResource() {
43
+ const context = contextStorage.getStore();
44
+ return context?.resource ?? null;
45
+ }
46
+ // Helper function to get the current request's user
47
+ function atxpAccountId() {
48
+ const context = contextStorage.getStore();
49
+ return context?.tokenData?.sub ?? null;
50
+ }
51
+ // Helper function to run code within a user context
52
+ async function withATXPContext(config, resource, tokenInfo, next) {
53
+ config.logger.debug(`Setting user context to ${tokenInfo?.data?.sub ?? 'null'}`);
54
+ if (tokenInfo && tokenInfo.data?.sub) {
55
+ if (tokenInfo.token) {
56
+ const dbData = {
57
+ accessToken: tokenInfo.token,
58
+ resourceUrl: ''
59
+ };
60
+ // Save the token to the oAuthDB so that other users of the DB can access it
61
+ // if needed (ie, for token-exchange for downstream services)
62
+ await config.oAuthDb.saveAccessToken(tokenInfo.data.sub, '', dbData);
63
+ }
64
+ else {
65
+ config.logger.warn(`Setting user context with token data, but there was no token provided. This probably indicates a bug, since the data should be derived from the token`);
66
+ config.logger.debug(`Token data: ${JSON.stringify(tokenInfo.data)}`);
67
+ }
68
+ }
69
+ const ctx = {
70
+ tokenData: tokenInfo?.data || null,
71
+ config,
72
+ resource
73
+ };
74
+ return contextStorage.run(ctx, next);
75
+ }
76
+
77
+ /**
78
+ * Core platform-agnostic token checking logic
79
+ * Takes an authorization header string instead of platform-specific request objects
80
+ */
81
+ async function checkTokenCore(config, resourceURL, authorizationHeader) {
82
+ const protocol = resourceURL.protocol;
83
+ const host = resourceURL.host;
84
+ const pathname = resourceURL.pathname;
85
+ const protectedResourceMetadataUrl = `${protocol}//${host}/.well-known/oauth-protected-resource${pathname}`;
86
+ const failure = {
87
+ passes: false,
88
+ resourceMetadataUrl: protectedResourceMetadataUrl,
89
+ };
90
+ // Extract the Bearer token from the Authorization header
91
+ if (!authorizationHeader) {
92
+ return { ...failure, problem: exports.TokenProblem.NO_TOKEN, data: null, token: null };
93
+ }
94
+ if (!authorizationHeader.startsWith('Bearer ')) {
95
+ return { ...failure, problem: exports.TokenProblem.NON_BEARER_AUTH_HEADER, data: null, token: null };
96
+ }
97
+ const token = authorizationHeader.substring(7);
98
+ try {
99
+ const introspectionResult = await config.oAuthClient.introspectToken(config.server, token);
100
+ if (!introspectionResult.active) {
101
+ return { ...failure, problem: exports.TokenProblem.INVALID_TOKEN, data: null, token };
102
+ }
103
+ return {
104
+ passes: true,
105
+ data: introspectionResult,
106
+ token,
107
+ };
108
+ }
109
+ catch (error) {
110
+ config.logger.error(`Error during token introspection: ${error}`);
111
+ return { ...failure, problem: exports.TokenProblem.INTROSPECT_ERROR, data: null, token };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Core platform-agnostic OAuth challenge response creation
117
+ * Returns the response data instead of writing to platform-specific response objects
118
+ */
119
+ function createOAuthChallengeResponseCore(tokenCheck) {
120
+ if (tokenCheck.passes) {
121
+ return null;
122
+ }
123
+ let status = 401;
124
+ let body = {};
125
+ // https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
126
+ switch (tokenCheck.problem) {
127
+ case exports.TokenProblem.NO_TOKEN:
128
+ break;
129
+ case exports.TokenProblem.NON_BEARER_AUTH_HEADER:
130
+ status = 400;
131
+ body = { error: 'invalid_request', error_description: 'Authorization header did not include a Bearer token' };
132
+ break;
133
+ case exports.TokenProblem.INVALID_TOKEN:
134
+ body = { error: 'invalid_token', error_description: 'Token is not active' };
135
+ break;
136
+ case exports.TokenProblem.INVALID_AUDIENCE:
137
+ body = { error: 'invalid_token', error_description: 'Token does not match the expected audience' };
138
+ break;
139
+ case exports.TokenProblem.NON_SUFFICIENT_FUNDS:
140
+ status = 403;
141
+ body = { error: 'insufficient_scope', error_description: 'Non sufficient funds' };
142
+ break;
143
+ case exports.TokenProblem.INTROSPECT_ERROR:
144
+ status = 502;
145
+ body = { error: 'server_error', error_description: 'An internal server error occurred' };
146
+ break;
147
+ }
148
+ const wwwAuthenticate = `Bearer resource_metadata="${tokenCheck.resourceMetadataUrl}"`;
149
+ return {
150
+ status,
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ 'WWW-Authenticate': wwwAuthenticate
154
+ },
155
+ body: JSON.stringify(body)
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Core platform-agnostic MCP request parsing logic
161
+ * Takes parsed JSON and request metadata instead of platform-specific request objects
162
+ */
163
+ function parseMcpRequestsCore(config, requestUrl, method, parsedBody) {
164
+ if (!method || method.toLowerCase() !== 'post') {
165
+ return [];
166
+ }
167
+ // The middleware has to be mounted at the root to serve the protected resource metadata,
168
+ // but the actual MCP server it's controlling is specified by the mountPath.
169
+ const path = requestUrl.pathname.replace(/\/$/, '');
170
+ const mountPath = config.mountPath.replace(/\/$/, '');
171
+ if (path !== mountPath && path !== `${mountPath}/message`) {
172
+ config.logger.debug(`Request path (${path}) does not match the mountPath (${mountPath}), skipping MCP middleware`);
173
+ return [];
174
+ }
175
+ if (!parsedBody || typeof parsedBody !== 'object') {
176
+ return [];
177
+ }
178
+ // Check if it's a JSON-RPC request
179
+ if (Array.isArray(parsedBody)) {
180
+ // Batch request
181
+ return parsedBody.filter(msg => msg && typeof msg === 'object' &&
182
+ msg.jsonrpc === '2.0' &&
183
+ msg.method &&
184
+ msg.id !== undefined);
185
+ }
186
+ else {
187
+ // Single request
188
+ const body = parsedBody;
189
+ if (body.jsonrpc === '2.0' && body.method && body.id !== undefined) {
190
+ return [body];
191
+ }
192
+ }
193
+ return [];
194
+ }
195
+
196
+ /**
197
+ * Node.js HTTP implementation of token checking
198
+ * Extracts data from Node.js IncomingMessage and delegates to core logic
199
+ */
200
+ async function checkToken(config, resourceURL, req) {
201
+ // Extract the authorization header from Node.js request
202
+ const authorizationHeader = req.headers.authorization || null;
203
+ // Use the shared core logic
204
+ return checkTokenCore(config, resourceURL, authorizationHeader);
205
+ }
206
+
207
+ /**
208
+ * Node.js HTTP implementation of OAuth challenge sending
209
+ * Uses Node.js ServerResponse and delegates to core logic
210
+ */
211
+ function sendOAuthChallenge(res, tokenCheck) {
212
+ // Use the shared core logic to get response data
213
+ const responseData = createOAuthChallengeResponseCore(tokenCheck);
214
+ if (!responseData) {
215
+ return false;
216
+ }
217
+ // Apply the response data to Node.js ServerResponse
218
+ Object.entries(responseData.headers).forEach(([key, value]) => {
219
+ res.setHeader(key, value);
220
+ });
221
+ res.writeHead(responseData.status);
222
+ res.end(responseData.body);
223
+ return true;
224
+ }
225
+ function sendProtectedResourceMetadata$1(res, metadata) {
226
+ if (!metadata) {
227
+ return false;
228
+ }
229
+ res.setHeader('Content-Type', 'application/json');
230
+ res.writeHead(200);
231
+ res.end(JSON.stringify(metadata));
232
+ return true;
233
+ }
234
+ function sendOAuthMetadata$1(res, metadata) {
235
+ if (!metadata) {
236
+ return false;
237
+ }
238
+ res.setHeader('Content-Type', 'application/json');
239
+ res.writeHead(200);
240
+ res.end(JSON.stringify(metadata));
241
+ return true;
242
+ }
243
+
244
+ // Helper function to parse size strings like "4mb" to bytes
245
+ function parseSize(size) {
246
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*([kmgt]?b?)$/i);
247
+ if (!match) {
248
+ throw new Error(`Invalid size format: ${size}`);
249
+ }
250
+ const value = parseFloat(match[1]);
251
+ const unit = (match[2] || 'b').toLowerCase();
252
+ const multipliers = {
253
+ 'b': 1,
254
+ 'kb': 1024,
255
+ 'mb': 1024 * 1024,
256
+ 'gb': 1024 * 1024 * 1024,
257
+ 'tb': 1024 * 1024 * 1024 * 1024,
258
+ };
259
+ return Math.floor(value * (multipliers[unit] || 1));
260
+ }
261
+ async function getRawBody(req, encoding, maxSize) {
262
+ // Use native Node.js approach to read request body
263
+ const chunks = [];
264
+ let totalSize = 0;
265
+ const maxSizeBytes = parseSize(maxSize);
266
+ for await (const chunk of req) {
267
+ totalSize += chunk.length;
268
+ if (totalSize > maxSizeBytes) {
269
+ throw new Error(`Request body too large. Maximum size is ${maxSize}`);
270
+ }
271
+ chunks.push(chunk);
272
+ }
273
+ const body = Buffer.concat(chunks);
274
+ return body.toString(encoding);
275
+ }
276
+
277
+ // Useful reference for dealing with low-level http requests:
278
+ // https://github.com/modelcontextprotocol/typescript-sdk/blob/c6ac083b1b37b222b5bfba5563822daa5d03372e/src/server/streamableHttp.ts#L375
279
+ // Using the same value as MCP SDK
280
+ const MAXIMUM_MESSAGE_SIZE = "4mb";
281
+ /**
282
+ * Node.js HTTP implementation of MCP request parsing
283
+ * Handles Node.js IncomingMessage parsing and delegates to core logic
284
+ */
285
+ async function parseMcpRequests(config, requestUrl, req, parsedBody) {
286
+ parsedBody = parsedBody ?? await parseBody(req, config.logger);
287
+ // Use the shared core logic for basic validation and filtering
288
+ const basicMessages = parseMcpRequestsCore(config, requestUrl, req.method || '', parsedBody);
289
+ // Only proceed with MCP processing if the basic validation passed
290
+ if (basicMessages.length === 0) {
291
+ return [];
292
+ }
293
+ // Apply additional MCP-specific processing (parseMcpMessages handles SSE and other formats)
294
+ const messages = await common.parseMcpMessages(parsedBody, config.logger);
295
+ const requests = messages.filter(msg => types_js.isJSONRPCRequest(msg));
296
+ if (requests.length !== messages.length) {
297
+ config.logger.debug(`Dropped ${messages.length - requests.length} MCP messages that were not MCP requests`);
298
+ }
299
+ return requests;
300
+ }
301
+ async function parseBody(req, logger) {
302
+ try {
303
+ const ct = req.headers["content-type"];
304
+ let encoding = "utf-8";
305
+ if (ct) {
306
+ const parsedCt = contentType__namespace.parse(ct);
307
+ encoding = parsedCt.parameters.charset ?? "utf-8";
308
+ }
309
+ const body = await getRawBody(req, encoding, MAXIMUM_MESSAGE_SIZE);
310
+ return JSON.parse(body);
311
+ }
312
+ catch (error) {
313
+ logger.error(error.message);
314
+ return undefined;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Web API implementation of token checking for Cloudflare Workers, Deno, etc.
320
+ * Extracts data from Web API Request and delegates to core logic
321
+ */
322
+ async function checkTokenWebApi(config, resourceURL, request) {
323
+ // Extract authorization header from Web API request
324
+ const authorizationHeader = request.headers.get('authorization');
325
+ // Use the shared core logic
326
+ return checkTokenCore(config, resourceURL, authorizationHeader);
327
+ }
328
+
329
+ /**
330
+ * Web API implementation of OAuth challenge sending for Cloudflare Workers, Deno, etc.
331
+ * Uses Web API Response and delegates to core logic
332
+ */
333
+ function sendOAuthChallengeWebApi(tokenCheck) {
334
+ // Use the shared core logic to get response data
335
+ const responseData = createOAuthChallengeResponseCore(tokenCheck);
336
+ if (!responseData) {
337
+ return null;
338
+ }
339
+ // Convert to Web API Response
340
+ return new Response(responseData.body, {
341
+ status: responseData.status,
342
+ headers: responseData.headers
343
+ });
344
+ }
345
+ function sendProtectedResourceMetadata(metadata) {
346
+ if (!metadata) {
347
+ return null;
348
+ }
349
+ return new Response(JSON.stringify(metadata), {
350
+ status: 200,
351
+ headers: {
352
+ 'Content-Type': 'application/json'
353
+ }
354
+ });
355
+ }
356
+ function sendOAuthMetadata(metadata) {
357
+ if (!metadata) {
358
+ return null;
359
+ }
360
+ return new Response(JSON.stringify(metadata), {
361
+ status: 200,
362
+ headers: {
363
+ 'Content-Type': 'application/json'
364
+ }
365
+ });
366
+ }
367
+
368
+ /**
369
+ * Web API implementation of MCP request parsing for Cloudflare Workers, Deno, etc.
370
+ * Handles Web API Request parsing and delegates to core logic
371
+ */
372
+ async function parseMcpRequestsWebApi(config, request) {
373
+ const requestUrl = new URL(request.url);
374
+ try {
375
+ const text = await request.text();
376
+ if (!text) {
377
+ return [];
378
+ }
379
+ const parsedBody = JSON.parse(text);
380
+ // Use the shared core logic
381
+ return parseMcpRequestsCore(config, requestUrl, request.method, parsedBody);
382
+ }
383
+ catch (error) {
384
+ config.logger.debug(`Error parsing MCP request: ${error instanceof Error ? error.message : String(error)}`);
385
+ return [];
386
+ }
387
+ }
388
+
389
+ async function requirePayment(paymentConfig) {
390
+ const config = getATXPConfig();
391
+ if (!config) {
392
+ throw new Error('No config found');
393
+ }
394
+ const user = atxpAccountId();
395
+ if (!user) {
396
+ config.logger.error('No user found');
397
+ throw new Error('No user found');
398
+ }
399
+ const charge = {
400
+ amount: paymentConfig.price,
401
+ currency: config.currency,
402
+ network: config.network,
403
+ destination: config.destination,
404
+ source: user,
405
+ payeeName: config.payeeName,
406
+ };
407
+ config.logger.debug(`Charging amount ${charge.amount}, destination ${charge.destination}, source ${charge.source}`);
408
+ const chargeResponse = await config.paymentServer.charge(charge);
409
+ if (chargeResponse.success) {
410
+ config.logger.info(`Charged ${charge.amount} for source ${charge.source}`);
411
+ return;
412
+ }
413
+ const existingPaymentId = await paymentConfig.getExistingPaymentId?.();
414
+ if (existingPaymentId) {
415
+ config.logger.info(`Found existing payment ID ${existingPaymentId}`);
416
+ throw common.paymentRequiredError(config.server, existingPaymentId, charge.amount);
417
+ }
418
+ const paymentId = await config.paymentServer.createPaymentRequest(charge);
419
+ config.logger.info(`Created payment request ${paymentId}`);
420
+ throw common.paymentRequiredError(config.server, paymentId, charge.amount);
421
+ }
422
+
423
+ /**
424
+ * ATXP Payment Server implementation
425
+ *
426
+ * This class handles payment operations with the ATXP authorization server.
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const paymentServer = new ATXPPaymentServer(
431
+ * 'https://auth.atxp.ai',
432
+ * logger
433
+ * );
434
+ * ```
435
+ */
436
+ class ATXPPaymentServer {
437
+ constructor(server, logger, fetchFn = fetch) {
438
+ this.server = server;
439
+ this.logger = logger;
440
+ this.fetchFn = fetchFn;
441
+ this.charge = async ({ source, destination, network, currency, amount }) => {
442
+ const body = { source, destination, network, currency, amount };
443
+ const chargeResponse = await this.makeRequest('POST', '/charge', body);
444
+ const json = await chargeResponse.json();
445
+ if (chargeResponse.status === 200) {
446
+ return { success: true, requiredPayment: null };
447
+ }
448
+ else if (chargeResponse.status === 402) {
449
+ return { success: false, requiredPayment: json };
450
+ }
451
+ else {
452
+ const msg = `Unexpected status code ${chargeResponse.status} from payment server POST /charge endpoint`;
453
+ this.logger.warn(msg);
454
+ this.logger.debug(`Response body: ${JSON.stringify(json)}`);
455
+ throw new Error(msg);
456
+ }
457
+ };
458
+ this.createPaymentRequest = async (charge) => {
459
+ const response = await this.makeRequest('POST', '/payment-request', charge);
460
+ const json = await response.json();
461
+ if (response.status !== 200) {
462
+ this.logger.warn(`POST /payment-request responded with unexpected HTTP status ${response.status}`);
463
+ this.logger.debug(`Response body: ${JSON.stringify(json)}`);
464
+ throw new Error(`POST /payment-request responded with unexpected HTTP status ${response.status}`);
465
+ }
466
+ if (!json.id) {
467
+ throw new Error(`POST /payment-request response did not contain an id`);
468
+ }
469
+ return json.id;
470
+ };
471
+ /**
472
+ * Makes authenticated requests to the ATXP authorization server
473
+ *
474
+ * @param method - HTTP method ('GET' or 'POST')
475
+ * @param path - API endpoint path
476
+ * @param body - Request body (for POST requests)
477
+ * @returns Promise<Response> - The HTTP response from the server
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * const response = await paymentServer.makeRequest('POST', '/charge', {
482
+ * source: 'user123',
483
+ * destination: 'merchant456',
484
+ * amount: new BigNumber('0.01')
485
+ * });
486
+ * ```
487
+ */
488
+ this.makeRequest = async (method, path, body) => {
489
+ const url = new URL(path, this.server);
490
+ const response = await this.fetchFn(url, {
491
+ method,
492
+ headers: {
493
+ 'Content-Type': 'application/json'
494
+ },
495
+ body: JSON.stringify(body)
496
+ });
497
+ return response;
498
+ };
499
+ }
500
+ }
501
+
502
+ const DEFAULT_CONFIG = {
503
+ mountPath: '/',
504
+ currency: 'USDC',
505
+ network: 'base',
506
+ server: common.DEFAULT_AUTHORIZATION_SERVER,
507
+ payeeName: 'An ATXP Server',
508
+ allowHttp: false, // May be overridden in buildServerConfig by process.env.NODE_ENV
509
+ resource: null, // Set dynamically from the request URL
510
+ };
511
+ function buildServerConfig(args) {
512
+ if (!args.destination) {
513
+ throw new Error('destination is required');
514
+ }
515
+ // Read environment variables at runtime, not module load time
516
+ const envDefaults = {
517
+ ...DEFAULT_CONFIG,
518
+ atxpAuthClientToken: process.env.ATXP_AUTH_CLIENT_TOKEN,
519
+ allowHttp: process.env.NODE_ENV === 'development',
520
+ };
521
+ const withDefaults = { ...envDefaults, ...args };
522
+ const oAuthDb = withDefaults.oAuthDb ?? new common.MemoryOAuthDb();
523
+ const oAuthClient = withDefaults.oAuthClient ?? new common.OAuthResourceClient({
524
+ db: oAuthDb,
525
+ allowInsecureRequests: withDefaults.allowHttp,
526
+ clientName: withDefaults.payeeName,
527
+ });
528
+ const logger = withDefaults.logger ?? new common.ConsoleLogger();
529
+ const paymentServer = withDefaults.paymentServer ?? new ATXPPaymentServer(withDefaults.server, logger);
530
+ const built = { oAuthDb, oAuthClient, paymentServer, logger };
531
+ return Object.freeze({ ...withDefaults, ...built });
532
+ }
533
+
534
+ function getPath(url) {
535
+ const fullPath = url.pathname.replace(/^\/$/, '');
536
+ return fullPath;
537
+ }
538
+ function getProtocolFromHeaders(headers, requestProtocol) {
539
+ // Check for X-Forwarded-Proto header (common proxy header)
540
+ const forwardedProto = headers['x-forwarded-proto'] || headers['X-Forwarded-Proto'];
541
+ if (forwardedProto) {
542
+ const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
543
+ return proto === 'https' ? 'https:' : 'http:';
544
+ }
545
+ // Check for X-Forwarded-Protocol header (alternative)
546
+ const forwardedProtocol = headers['x-forwarded-protocol'] || headers['X-Forwarded-Protocol'];
547
+ if (forwardedProtocol) {
548
+ const proto = Array.isArray(forwardedProtocol) ? forwardedProtocol[0] : forwardedProtocol;
549
+ return proto === 'https' ? 'https:' : 'http:';
550
+ }
551
+ // Fall back to request protocol
552
+ return requestProtocol;
553
+ }
554
+ function getResource(config, requestUrl, headers) {
555
+ if (config.resource) {
556
+ return new URL(config.resource);
557
+ }
558
+ const originalProtocol = headers ? getProtocolFromHeaders(headers, requestUrl.protocol) : requestUrl.protocol;
559
+ const protocol = config.allowHttp ? originalProtocol : 'https:';
560
+ const url = new URL(`${protocol}//${requestUrl.host}${requestUrl.pathname}`);
561
+ const fullPath = getPath(url);
562
+ // If this is a PRM path, convert it into the path for the resource this is the metadata for
563
+ const resourcePath = fullPath.replace('/.well-known/oauth-protected-resource', '').replace(/\/$/, '');
564
+ const resource = new URL(`${protocol}//${requestUrl.host}${resourcePath}`);
565
+ return resource;
566
+ }
567
+
568
+ async function getOAuthMetadata(config, requestUrl) {
569
+ if (isOAuthMetadataRequest(config, requestUrl)) {
570
+ try {
571
+ const authServer = await config.oAuthClient.authorizationServerFromUrl(new URL(config.server));
572
+ return {
573
+ issuer: config.server,
574
+ authorization_endpoint: authServer.authorization_endpoint,
575
+ response_types_supported: authServer.response_types_supported,
576
+ grant_types_supported: authServer.grant_types_supported,
577
+ token_endpoint: authServer.token_endpoint,
578
+ token_endpoint_auth_methods_supported: authServer.token_endpoint_auth_methods_supported,
579
+ registration_endpoint: authServer.registration_endpoint,
580
+ revocation_endpoint: authServer.revocation_endpoint,
581
+ introspection_endpoint: authServer.introspection_endpoint,
582
+ introspection_endpoint_auth_methods_supported: authServer.introspection_endpoint_auth_methods_supported,
583
+ code_challenge_methods_supported: authServer.code_challenge_methods_supported,
584
+ scopes_supported: authServer.scopes_supported
585
+ };
586
+ }
587
+ catch (error) {
588
+ config.logger.error(`Error fetching authorization server configuration from ${config.server}: ${error}`);
589
+ throw error;
590
+ }
591
+ }
592
+ return null;
593
+ }
594
+ function isOAuthMetadataRequest(config, requestUrl) {
595
+ const path = getPath(requestUrl).replace(/\/$/, '');
596
+ return path === '/.well-known/oauth-authorization-server';
597
+ }
598
+
599
+ function getProtectedResourceMetadata(config, requestUrl, headers) {
600
+ if (isProtectedResourceMetadataRequest(config, requestUrl, headers)) {
601
+ const resource = getResource(config, requestUrl, headers);
602
+ return {
603
+ resource,
604
+ resource_name: config.payeeName || resource.toString(),
605
+ authorization_servers: [config.server],
606
+ bearer_methods_supported: ['header'],
607
+ scopes_supported: ['read', 'write'],
608
+ };
609
+ }
610
+ return null;
611
+ }
612
+ function isProtectedResourceMetadataRequest(config, requestUrl, headers) {
613
+ const path = getPath(requestUrl);
614
+ if (!path.startsWith('/.well-known/oauth-protected-resource')) {
615
+ return false;
616
+ }
617
+ const resource = getResource(config, requestUrl, headers);
618
+ const resourcePath = getPath(resource);
619
+ const mountPath = config.mountPath.replace(/\/$/, '');
620
+ if (resourcePath === mountPath) {
621
+ return true;
622
+ }
623
+ if (resourcePath === `${mountPath}/message`) {
624
+ return true;
625
+ }
626
+ return false;
627
+ }
628
+
629
+ exports.ATXPPaymentServer = ATXPPaymentServer;
630
+ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
631
+ exports.atxpAccountId = atxpAccountId;
632
+ exports.buildServerConfig = buildServerConfig;
633
+ exports.checkTokenCore = checkTokenCore;
634
+ exports.checkTokenNode = checkToken;
635
+ exports.checkTokenWebApi = checkTokenWebApi;
636
+ exports.createOAuthChallengeResponseCore = createOAuthChallengeResponseCore;
637
+ exports.getATXPConfig = getATXPConfig;
638
+ exports.getATXPResource = getATXPResource;
639
+ exports.getOAuthMetadata = getOAuthMetadata;
640
+ exports.getProtectedResourceMetadata = getProtectedResourceMetadata;
641
+ exports.getResource = getResource;
642
+ exports.parseBodyNode = parseBody;
643
+ exports.parseMcpRequestsCore = parseMcpRequestsCore;
644
+ exports.parseMcpRequestsNode = parseMcpRequests;
645
+ exports.parseMcpRequestsWebApi = parseMcpRequestsWebApi;
646
+ exports.requirePayment = requirePayment;
647
+ exports.sendOAuthChallenge = sendOAuthChallenge;
648
+ exports.sendOAuthChallengeWebApi = sendOAuthChallengeWebApi;
649
+ exports.sendOAuthMetadataNode = sendOAuthMetadata$1;
650
+ exports.sendOAuthMetadataWebApi = sendOAuthMetadata;
651
+ exports.sendProtectedResourceMetadataNode = sendProtectedResourceMetadata$1;
652
+ exports.sendProtectedResourceMetadataWebApi = sendProtectedResourceMetadata;
653
+ exports.withATXPContext = withATXPContext;
654
+ //# sourceMappingURL=index.cjs.map