agentpay-mcp 1.2.0 → 4.0.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 (79) hide show
  1. package/.env.example +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +732 -33
  4. package/claude_desktop_config.json +17 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +248 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/session/manager.d.ts +90 -0
  10. package/dist/session/manager.d.ts.map +1 -0
  11. package/dist/session/manager.js +262 -0
  12. package/dist/session/manager.js.map +1 -0
  13. package/dist/session/types.d.ts +113 -0
  14. package/dist/session/types.d.ts.map +1 -0
  15. package/dist/session/types.js +16 -0
  16. package/dist/session/types.js.map +1 -0
  17. package/dist/tools/bridge.d.ts +52 -0
  18. package/dist/tools/bridge.d.ts.map +1 -0
  19. package/dist/tools/bridge.js +97 -0
  20. package/dist/tools/bridge.js.map +1 -0
  21. package/dist/tools/budget.d.ts +84 -0
  22. package/dist/tools/budget.d.ts.map +1 -0
  23. package/dist/tools/budget.js +163 -0
  24. package/dist/tools/budget.js.map +1 -0
  25. package/dist/tools/deploy.d.ts +49 -0
  26. package/dist/tools/deploy.d.ts.map +1 -0
  27. package/dist/tools/deploy.js +123 -0
  28. package/dist/tools/deploy.js.map +1 -0
  29. package/dist/tools/escrow.d.ts +73 -0
  30. package/dist/tools/escrow.d.ts.map +1 -0
  31. package/dist/tools/escrow.js +146 -0
  32. package/dist/tools/escrow.js.map +1 -0
  33. package/dist/tools/history.d.ts +59 -0
  34. package/dist/tools/history.d.ts.map +1 -0
  35. package/dist/tools/history.js +202 -0
  36. package/dist/tools/history.js.map +1 -0
  37. package/dist/tools/identity.d.ts +65 -0
  38. package/dist/tools/identity.d.ts.map +1 -0
  39. package/dist/tools/identity.js +158 -0
  40. package/dist/tools/identity.js.map +1 -0
  41. package/dist/tools/payments.d.ts +71 -0
  42. package/dist/tools/payments.d.ts.map +1 -0
  43. package/dist/tools/payments.js +158 -0
  44. package/dist/tools/payments.js.map +1 -0
  45. package/dist/tools/session.d.ts +240 -0
  46. package/dist/tools/session.d.ts.map +1 -0
  47. package/dist/tools/session.js +678 -0
  48. package/dist/tools/session.js.map +1 -0
  49. package/dist/tools/swap.d.ts +65 -0
  50. package/dist/tools/swap.d.ts.map +1 -0
  51. package/dist/tools/swap.js +101 -0
  52. package/dist/tools/swap.js.map +1 -0
  53. package/dist/tools/tokens.d.ts +129 -0
  54. package/dist/tools/tokens.d.ts.map +1 -0
  55. package/dist/tools/tokens.js +138 -0
  56. package/dist/tools/tokens.js.map +1 -0
  57. package/dist/tools/transfers.d.ts +86 -0
  58. package/dist/tools/transfers.d.ts.map +1 -0
  59. package/dist/tools/transfers.js +136 -0
  60. package/dist/tools/transfers.js.map +1 -0
  61. package/dist/tools/wallet.d.ts +107 -0
  62. package/dist/tools/wallet.d.ts.map +1 -0
  63. package/dist/tools/wallet.js +271 -0
  64. package/dist/tools/wallet.js.map +1 -0
  65. package/dist/tools/x402.d.ts +90 -0
  66. package/dist/tools/x402.d.ts.map +1 -0
  67. package/dist/tools/x402.js +268 -0
  68. package/dist/tools/x402.js.map +1 -0
  69. package/dist/utils/client.d.ts +46 -0
  70. package/dist/utils/client.d.ts.map +1 -0
  71. package/dist/utils/client.js +123 -0
  72. package/dist/utils/client.js.map +1 -0
  73. package/dist/utils/format.d.ts +59 -0
  74. package/dist/utils/format.d.ts.map +1 -0
  75. package/dist/utils/format.js +161 -0
  76. package/dist/utils/format.js.map +1 -0
  77. package/package.json +62 -12
  78. package/index.d.ts +0 -1
  79. package/index.js +0 -13
@@ -0,0 +1,678 @@
1
+ "use strict";
2
+ /**
3
+ * session.ts — x402 V2 session payment tools.
4
+ *
5
+ * Implements the x402 V2 "wallet-based access & reusable sessions" pattern:
6
+ * 1. x402_session_start — pay once, receive a signed session token
7
+ * 2. x402_session_fetch — make N calls within the session (no new payments)
8
+ * 3. x402_session_status — inspect active sessions and TTL remaining
9
+ * 4. x402_session_end — explicitly close a session
10
+ *
11
+ * Non-custodial design:
12
+ * Agents sign session tokens locally with their private key.
13
+ * No third party holds or validates keys at any point.
14
+ * Session tokens are self-contained ECDSA-signed claims that any server
15
+ * implementing x402 V2 can independently verify.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.buildSessionHeaders = exports.findSessionForUrl = exports.x402SessionEndTool = exports.X402SessionEndSchema = exports.x402SessionStatusTool = exports.X402SessionStatusSchema = exports.x402SessionFetchTool = exports.X402SessionFetchSchema = exports.x402SessionStartTool = exports.X402SessionStartSchema = void 0;
19
+ exports.handleX402SessionStart = handleX402SessionStart;
20
+ exports.handleX402SessionFetch = handleX402SessionFetch;
21
+ exports.handleX402SessionStatus = handleX402SessionStatus;
22
+ exports.handleX402SessionEnd = handleX402SessionEnd;
23
+ const zod_1 = require("zod");
24
+ const agentwallet_sdk_1 = require("agentwallet-sdk");
25
+ const client_js_1 = require("../utils/client.js");
26
+ const format_js_1 = require("../utils/format.js");
27
+ const manager_js_1 = require("../session/manager.js");
28
+ Object.defineProperty(exports, "findSessionForUrl", { enumerable: true, get: function () { return manager_js_1.findSessionForUrl; } });
29
+ Object.defineProperty(exports, "buildSessionHeaders", { enumerable: true, get: function () { return manager_js_1.buildSessionHeaders; } });
30
+ // ─── x402_session_start ────────────────────────────────────────────────────
31
+ exports.X402SessionStartSchema = zod_1.z.object({
32
+ endpoint: zod_1.z
33
+ .string()
34
+ .url()
35
+ .describe('The base URL or endpoint to establish a session for. ' +
36
+ 'The agent pays once and the session covers all subsequent requests to this endpoint.'),
37
+ scope: zod_1.z
38
+ .enum(['prefix', 'exact'])
39
+ .optional()
40
+ .default('prefix')
41
+ .describe('"prefix" (default): session covers all paths under the endpoint URL. ' +
42
+ '"exact": session only covers this exact URL.'),
43
+ ttl_seconds: zod_1.z
44
+ .number()
45
+ .int()
46
+ .min(60)
47
+ .max(86400 * 30)
48
+ .optional()
49
+ .describe('Session lifetime in seconds (default: SESSION_TTL_SECONDS env var or 3600). ' +
50
+ 'Min: 60 seconds. Max: 30 days.'),
51
+ label: zod_1.z
52
+ .string()
53
+ .max(100)
54
+ .optional()
55
+ .describe('Optional human-readable label for this session (e.g. "Premium API session")'),
56
+ max_payment_eth: zod_1.z
57
+ .string()
58
+ .optional()
59
+ .describe('Maximum ETH to pay for session establishment. Rejects if exceeded.'),
60
+ method: zod_1.z
61
+ .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
62
+ .optional()
63
+ .default('GET')
64
+ .describe('HTTP method for the initial payment request (default: GET)'),
65
+ headers: zod_1.z
66
+ .record(zod_1.z.string())
67
+ .optional()
68
+ .describe('Additional headers for the session-start request'),
69
+ body: zod_1.z
70
+ .string()
71
+ .optional()
72
+ .describe('Request body for the initial session-start request (if POST/PUT/PATCH)'),
73
+ timeout_ms: zod_1.z
74
+ .number()
75
+ .int()
76
+ .min(1000)
77
+ .max(60000)
78
+ .optional()
79
+ .default(30000)
80
+ .describe('Request timeout in milliseconds (default: 30000)'),
81
+ });
82
+ exports.x402SessionStartTool = {
83
+ name: 'x402_session_start',
84
+ description: 'Establish an x402 V2 payment session: make a SINGLE on-chain payment and receive ' +
85
+ 'a cryptographically signed session token. All subsequent calls to the same endpoint ' +
86
+ 'within the session lifetime use x402_session_fetch — no additional payments required. ' +
87
+ 'Agents pay once per session rather than once per API call. ' +
88
+ 'Session tokens are signed locally by your wallet key (non-custodial). ' +
89
+ 'Returns a session_id you pass to x402_session_fetch for all future calls.',
90
+ inputSchema: {
91
+ type: 'object',
92
+ properties: {
93
+ endpoint: {
94
+ type: 'string',
95
+ description: 'Base URL to establish a session for (e.g., "https://api.example.com/v1")',
96
+ },
97
+ scope: {
98
+ type: 'string',
99
+ enum: ['prefix', 'exact'],
100
+ description: '"prefix": covers all paths under this URL (default). "exact": single URL only.',
101
+ default: 'prefix',
102
+ },
103
+ ttl_seconds: {
104
+ type: 'number',
105
+ description: 'Session TTL in seconds (default: 3600 / 1 hour). Max: 30 days.',
106
+ },
107
+ label: {
108
+ type: 'string',
109
+ description: 'Optional label for this session (e.g., "Premium API session")',
110
+ },
111
+ max_payment_eth: {
112
+ type: 'string',
113
+ description: 'Maximum ETH to pay for this session. Rejects if price exceeds this.',
114
+ },
115
+ method: {
116
+ type: 'string',
117
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
118
+ description: 'HTTP method for the initial request (default: GET)',
119
+ default: 'GET',
120
+ },
121
+ headers: {
122
+ type: 'object',
123
+ additionalProperties: { type: 'string' },
124
+ description: 'Additional request headers',
125
+ },
126
+ body: {
127
+ type: 'string',
128
+ description: 'Request body for POST/PUT/PATCH session-start requests',
129
+ },
130
+ timeout_ms: {
131
+ type: 'number',
132
+ description: 'Request timeout in milliseconds (default: 30000)',
133
+ default: 30000,
134
+ },
135
+ },
136
+ required: ['endpoint'],
137
+ },
138
+ };
139
+ async function handleX402SessionStart(input) {
140
+ try {
141
+ const wallet = (0, client_js_1.getWallet)();
142
+ const config = (0, client_js_1.getConfig)();
143
+ const timeoutMs = input.timeout_ms ?? 30000;
144
+ // Parse optional payment cap
145
+ let maxPaymentWei;
146
+ if (input.max_payment_eth) {
147
+ const cap = parseFloat(input.max_payment_eth);
148
+ if (isNaN(cap) || cap <= 0) {
149
+ throw new Error(`Invalid max_payment_eth: "${input.max_payment_eth}"`);
150
+ }
151
+ maxPaymentWei = BigInt(Math.round(cap * 1e18));
152
+ }
153
+ // Track payment
154
+ let paymentMade = false;
155
+ let paymentAmount = 0n;
156
+ let paymentTxHash = '';
157
+ let paymentRecipient = '';
158
+ let paymentToken = '0x0000000000000000000000000000000000000000';
159
+ // x402 client to handle the one-time session payment
160
+ const x402Client = (0, agentwallet_sdk_1.createX402Client)(wallet, {
161
+ autoPay: true,
162
+ maxRetries: 1,
163
+ globalPerRequestMax: maxPaymentWei,
164
+ onBeforePayment: (req) => {
165
+ const amount = BigInt(req.amount);
166
+ if (maxPaymentWei && amount > maxPaymentWei) {
167
+ throw new Error(`Session payment (${amount} wei) exceeds max_payment_eth cap ` +
168
+ `(${maxPaymentWei} wei = ${input.max_payment_eth} ETH). ` +
169
+ `Set a higher max_payment_eth to proceed.`);
170
+ }
171
+ return true;
172
+ },
173
+ onPaymentComplete: (log) => {
174
+ paymentMade = true;
175
+ paymentAmount = log.amount;
176
+ paymentTxHash = log.txHash;
177
+ paymentRecipient = log.recipient;
178
+ paymentToken = log.token ?? '0x0000000000000000000000000000000000000000';
179
+ },
180
+ });
181
+ const method = input.method ?? 'GET';
182
+ const reqHeaders = {
183
+ 'Accept': 'application/json, text/plain, */*',
184
+ ...(input.headers ?? {}),
185
+ };
186
+ if (input.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
187
+ if (!reqHeaders['Content-Type']) {
188
+ reqHeaders['Content-Type'] = 'application/json';
189
+ }
190
+ }
191
+ const requestInit = {
192
+ method,
193
+ headers: reqHeaders,
194
+ ...(input.body ? { body: input.body } : {}),
195
+ signal: AbortSignal.timeout(timeoutMs),
196
+ };
197
+ // Make the payment request
198
+ const response = await x402Client.fetch(input.endpoint, requestInit);
199
+ const responseText = await response.text();
200
+ if (!paymentMade) {
201
+ // Endpoint didn't require payment — still create a session if response was OK
202
+ // This allows agents to pre-establish sessions for endpoints that may start
203
+ // requiring payment, or to track usage even for free endpoints.
204
+ return {
205
+ content: [
206
+ (0, format_js_1.textContent)(`ℹ️ **No Payment Required**\n\n` +
207
+ ` Endpoint: ${input.endpoint}\n` +
208
+ ` Status: ${response.status} ${response.statusText}\n\n` +
209
+ `No x402 payment was needed. The endpoint responded without requiring payment.\n` +
210
+ `You do not need a session token — use x402_pay directly for free endpoints.\n\n` +
211
+ `📄 **Response Body**\n` +
212
+ '```\n' + responseText.slice(0, 4000) + (responseText.length > 4000 ? '\n... [truncated]' : '') + '\n```'),
213
+ ],
214
+ };
215
+ }
216
+ // Create signed session record
217
+ // The signMessage function uses viem's wallet client, signing locally with the agent's key
218
+ const signMessage = async (message) => {
219
+ // Access the walletClient from the wallet instance for local signing
220
+ const wc = wallet.walletClient;
221
+ return wc.signMessage({ message });
222
+ };
223
+ const session = await (0, manager_js_1.createSession)({
224
+ endpoint: input.endpoint,
225
+ scope: input.scope ?? 'prefix',
226
+ ttlSeconds: input.ttl_seconds,
227
+ label: input.label,
228
+ paymentTxHash,
229
+ paymentAmount,
230
+ paymentToken,
231
+ paymentRecipient,
232
+ walletAddress: config.walletAddress,
233
+ signMessage,
234
+ });
235
+ const ttlRemaining = session.expiresAt - Math.floor(Date.now() / 1000);
236
+ const expiresAt = new Date(session.expiresAt * 1000).toISOString();
237
+ let out = `🔐 **x402 Session Established**\n\n`;
238
+ out += ` Session ID: ${session.sessionId}\n`;
239
+ out += ` Endpoint: ${session.endpoint}\n`;
240
+ out += ` Scope: ${session.scope}\n`;
241
+ if (session.label)
242
+ out += ` Label: ${session.label}\n`;
243
+ out += ` Network: ${(0, format_js_1.chainName)(config.chainId)}\n`;
244
+ out += ` TTL: ${Math.ceil(ttlRemaining / 60)}m (expires ${expiresAt})\n\n`;
245
+ out += `💳 **Session Payment**\n`;
246
+ out += ` Amount: ${paymentAmount.toString()} (base units)\n`;
247
+ out += ` Recipient: ${paymentRecipient}\n`;
248
+ out += ` TX Hash: ${paymentTxHash}\n\n`;
249
+ out += `✅ **Next Steps**\n`;
250
+ out += ` Use \`x402_session_fetch\` with session_id="${session.sessionId}" for all subsequent\n`;
251
+ out += ` requests to ${input.endpoint} — no further payments will be made during this session.\n`;
252
+ out += ` Check session status with \`x402_session_status\`.\n\n`;
253
+ out += `📄 **Initial Response** (${response.status})\n`;
254
+ const truncated = responseText.length > 4000;
255
+ out += '```\n' + responseText.slice(0, 4000) + (truncated ? '\n... [truncated]' : '') + '\n```';
256
+ return { content: [(0, format_js_1.textContent)(out)] };
257
+ }
258
+ catch (error) {
259
+ if (error instanceof Error && error.name === 'AbortError') {
260
+ return {
261
+ content: [(0, format_js_1.textContent)(`❌ x402_session_start failed: Request timed out after ${input.timeout_ms ?? 30000}ms`)],
262
+ isError: true,
263
+ };
264
+ }
265
+ return {
266
+ content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'x402_session_start'))],
267
+ isError: true,
268
+ };
269
+ }
270
+ }
271
+ // ─── x402_session_fetch ────────────────────────────────────────────────────
272
+ exports.X402SessionFetchSchema = zod_1.z.object({
273
+ session_id: zod_1.z
274
+ .string()
275
+ .uuid()
276
+ .describe('Session ID returned by x402_session_start.'),
277
+ url: zod_1.z
278
+ .string()
279
+ .url()
280
+ .describe('URL to fetch within the session. Must be covered by the session endpoint/scope.'),
281
+ method: zod_1.z
282
+ .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
283
+ .optional()
284
+ .default('GET')
285
+ .describe('HTTP method (default: GET)'),
286
+ headers: zod_1.z
287
+ .record(zod_1.z.string())
288
+ .optional()
289
+ .describe('Additional HTTP headers (session token is injected automatically)'),
290
+ body: zod_1.z
291
+ .string()
292
+ .optional()
293
+ .describe('Request body for POST/PUT/PATCH requests'),
294
+ timeout_ms: zod_1.z
295
+ .number()
296
+ .int()
297
+ .min(1000)
298
+ .max(60000)
299
+ .optional()
300
+ .default(30000)
301
+ .describe('Request timeout in milliseconds (default: 30000)'),
302
+ });
303
+ exports.x402SessionFetchTool = {
304
+ name: 'x402_session_fetch',
305
+ description: 'Make an HTTP request within an established x402 V2 session — NO payment required. ' +
306
+ 'The session token (signed by your wallet) is automatically attached to the request. ' +
307
+ 'The server recognises your session and grants access without a new on-chain payment. ' +
308
+ 'Requires a session_id from x402_session_start. ' +
309
+ 'Returns an error if the session has expired (call x402_session_start again to renew).',
310
+ inputSchema: {
311
+ type: 'object',
312
+ properties: {
313
+ session_id: {
314
+ type: 'string',
315
+ description: 'Session ID from x402_session_start',
316
+ },
317
+ url: {
318
+ type: 'string',
319
+ description: 'URL to fetch (must be covered by the session)',
320
+ },
321
+ method: {
322
+ type: 'string',
323
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
324
+ description: 'HTTP method (default: GET)',
325
+ default: 'GET',
326
+ },
327
+ headers: {
328
+ type: 'object',
329
+ additionalProperties: { type: 'string' },
330
+ description: 'Additional headers (session token is injected automatically)',
331
+ },
332
+ body: {
333
+ type: 'string',
334
+ description: 'Request body for POST/PUT/PATCH',
335
+ },
336
+ timeout_ms: {
337
+ type: 'number',
338
+ description: 'Timeout in milliseconds (default: 30000)',
339
+ default: 30000,
340
+ },
341
+ },
342
+ required: ['session_id', 'url'],
343
+ },
344
+ };
345
+ async function handleX402SessionFetch(input) {
346
+ try {
347
+ const timeoutMs = input.timeout_ms ?? 30000;
348
+ // Look up session
349
+ const lookup = (0, manager_js_1.lookupSession)(input.session_id);
350
+ if (!lookup.found) {
351
+ return {
352
+ content: [
353
+ (0, format_js_1.textContent)(`❌ Session not found: "${input.session_id}"\n\n` +
354
+ `Create a new session with x402_session_start first.`),
355
+ ],
356
+ isError: true,
357
+ };
358
+ }
359
+ if (lookup.expired) {
360
+ const expiredAt = new Date(lookup.session.expiresAt * 1000).toISOString();
361
+ return {
362
+ content: [
363
+ (0, format_js_1.textContent)(`⏰ **Session Expired**\n\n` +
364
+ ` Session ID: ${input.session_id}\n` +
365
+ ` Endpoint: ${lookup.session.endpoint}\n` +
366
+ ` Expired at: ${expiredAt}\n\n` +
367
+ `Call x402_session_start to establish a new session for this endpoint.`),
368
+ ],
369
+ isError: true,
370
+ };
371
+ }
372
+ const { session } = lookup;
373
+ // Validate URL is covered by the session scope
374
+ const urlCovered = isUrlCoveredBySession(input.url, session);
375
+ if (!urlCovered) {
376
+ return {
377
+ content: [
378
+ (0, format_js_1.textContent)(`❌ URL not covered by this session.\n\n` +
379
+ ` Session endpoint: ${session.endpoint}\n` +
380
+ ` Session scope: ${session.scope}\n` +
381
+ ` Requested URL: ${input.url}\n\n` +
382
+ `This URL is outside the session's ${session.scope === 'exact' ? 'exact match' : 'prefix match'} scope.\n` +
383
+ `Create a new session for this URL with x402_session_start, or use x402_pay for a one-time request.`),
384
+ ],
385
+ isError: true,
386
+ };
387
+ }
388
+ // Build request headers — inject session token automatically
389
+ const sessionHeaders = (0, manager_js_1.buildSessionHeaders)(session);
390
+ const method = input.method ?? 'GET';
391
+ const mergedHeaders = {
392
+ 'Accept': 'application/json, text/plain, */*',
393
+ ...sessionHeaders,
394
+ ...(input.headers ?? {}),
395
+ };
396
+ if (input.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
397
+ if (!mergedHeaders['Content-Type']) {
398
+ mergedHeaders['Content-Type'] = 'application/json';
399
+ }
400
+ }
401
+ const requestInit = {
402
+ method,
403
+ headers: mergedHeaders,
404
+ ...(input.body ? { body: input.body } : {}),
405
+ signal: AbortSignal.timeout(timeoutMs),
406
+ };
407
+ // Make request — plain fetch, NO x402 payment client
408
+ // The session token headers tell the server to bypass the payment flow
409
+ const response = await fetch(input.url, requestInit);
410
+ const responseText = await response.text();
411
+ // Record the call in the session
412
+ (0, manager_js_1.recordSessionCall)(input.session_id);
413
+ // Handle edge case: server still returned 402 (session not recognised)
414
+ if (response.status === 402) {
415
+ return {
416
+ content: [
417
+ (0, format_js_1.textContent)(`⚠️ **Server returned 402 — Session Not Recognised**\n\n` +
418
+ ` URL: ${input.url}\n` +
419
+ ` Session ID: ${input.session_id}\n\n` +
420
+ `The server returned HTTP 402 despite session headers being sent.\n` +
421
+ `This means the server does not support x402 V2 session tokens yet,\n` +
422
+ `or the session has been invalidated server-side.\n\n` +
423
+ `Options:\n` +
424
+ ` • Use x402_pay for a one-time payment to this URL\n` +
425
+ ` • Contact the API provider about x402 V2 session support\n\n` +
426
+ `📄 **Response Body**\n` +
427
+ '```\n' + responseText.slice(0, 2000) + '\n```'),
428
+ ],
429
+ isError: true,
430
+ };
431
+ }
432
+ // Truncate very large responses
433
+ const MAX_LEN = 8000;
434
+ const truncated = responseText.length > MAX_LEN;
435
+ const displayText = truncated
436
+ ? responseText.slice(0, MAX_LEN) + '\n\n... [response truncated]'
437
+ : responseText;
438
+ const ttlRemaining = session.expiresAt - Math.floor(Date.now() / 1000);
439
+ const callNumber = session.callCount; // after recordSessionCall incremented it
440
+ let out = `⚡ **x402 Session Fetch** (call #${callNumber})\n\n`;
441
+ out += ` Session ID: ${session.sessionId}\n`;
442
+ if (session.label)
443
+ out += ` Label: ${session.label}\n`;
444
+ out += ` URL: ${input.url}\n`;
445
+ out += ` Method: ${method}\n`;
446
+ out += ` Status: ${response.status} ${response.statusText}\n`;
447
+ out += ` Session TTL: ${Math.ceil(ttlRemaining / 60)}m remaining\n`;
448
+ out += ` 💰 No payment — session token used\n\n`;
449
+ out += `📄 **Response Body**\n`;
450
+ out += '```\n' + displayText + '\n```';
451
+ return { content: [(0, format_js_1.textContent)(out)] };
452
+ }
453
+ catch (error) {
454
+ if (error instanceof Error && error.name === 'AbortError') {
455
+ return {
456
+ content: [(0, format_js_1.textContent)(`❌ x402_session_fetch failed: Request timed out after ${input.timeout_ms ?? 30000}ms`)],
457
+ isError: true,
458
+ };
459
+ }
460
+ return {
461
+ content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'x402_session_fetch'))],
462
+ isError: true,
463
+ };
464
+ }
465
+ }
466
+ // ─── x402_session_status ──────────────────────────────────────────────────
467
+ exports.X402SessionStatusSchema = zod_1.z.object({
468
+ session_id: zod_1.z
469
+ .string()
470
+ .optional()
471
+ .describe('Specific session ID to inspect. ' +
472
+ 'Omit to list all active sessions.'),
473
+ });
474
+ exports.x402SessionStatusTool = {
475
+ name: 'x402_session_status',
476
+ description: 'Check the status of x402 V2 payment sessions. ' +
477
+ 'Without arguments, lists all active sessions with TTL remaining. ' +
478
+ 'With a session_id, shows full details for that session including ' +
479
+ 'call count, payment info, and the signed session token.',
480
+ inputSchema: {
481
+ type: 'object',
482
+ properties: {
483
+ session_id: {
484
+ type: 'string',
485
+ description: 'Specific session ID to inspect. Omit to list all active sessions.',
486
+ },
487
+ },
488
+ required: [],
489
+ },
490
+ };
491
+ async function handleX402SessionStatus(input) {
492
+ try {
493
+ const now = Math.floor(Date.now() / 1000);
494
+ if (input.session_id) {
495
+ // Detailed view for a specific session
496
+ const lookup = (0, manager_js_1.lookupSession)(input.session_id);
497
+ if (!lookup.found) {
498
+ return {
499
+ content: [
500
+ (0, format_js_1.textContent)(`❌ Session not found: "${input.session_id}"\n\n` +
501
+ `Use x402_session_status (no arguments) to list active sessions.`),
502
+ ],
503
+ isError: true,
504
+ };
505
+ }
506
+ const { session, expired } = lookup;
507
+ const ttlRemaining = Math.max(0, session.expiresAt - now);
508
+ const decoded = (0, manager_js_1.decodeSessionToken)(session.sessionToken);
509
+ let out = expired
510
+ ? `⏰ **Session Expired**\n\n`
511
+ : `🔐 **Session Details**\n\n`;
512
+ out += ` Session ID: ${session.sessionId}\n`;
513
+ if (session.label)
514
+ out += ` Label: ${session.label}\n`;
515
+ out += ` Status: ${expired ? '❌ Expired' : '✅ Active'}\n`;
516
+ out += ` Endpoint: ${session.endpoint}\n`;
517
+ out += ` Scope: ${session.scope}\n`;
518
+ out += ` Wallet: ${session.walletAddress}\n\n`;
519
+ out += `⏱️ **Timing**\n`;
520
+ out += ` Created: ${new Date(session.createdAt * 1000).toISOString()}\n`;
521
+ out += ` Expires: ${new Date(session.expiresAt * 1000).toISOString()}\n`;
522
+ if (!expired)
523
+ out += ` TTL Remaining: ${formatTtl(ttlRemaining)}\n`;
524
+ out += ` Last Used: ${session.lastUsedAt > 0 ? new Date(session.lastUsedAt * 1000).toISOString() : 'Never'}\n`;
525
+ out += ` Call Count: ${session.callCount}\n\n`;
526
+ out += `💳 **Session Payment**\n`;
527
+ out += ` TX Hash: ${session.paymentTxHash}\n`;
528
+ out += ` Amount: ${session.paymentAmount} (base units)\n`;
529
+ out += ` Recipient: ${session.paymentRecipient}\n`;
530
+ out += ` Token: ${session.paymentToken === '0x0000000000000000000000000000000000000000' ? 'ETH (native)' : session.paymentToken}\n\n`;
531
+ if (decoded) {
532
+ out += `🔏 **Token Info**\n`;
533
+ out += ` Protocol: ${decoded.payload.version}\n`;
534
+ out += ` Signature: ${decoded.signature.slice(0, 20)}...${decoded.signature.slice(-8)}\n`;
535
+ }
536
+ return { content: [(0, format_js_1.textContent)(out)] };
537
+ }
538
+ // List all active sessions
539
+ const active = (0, manager_js_1.listActiveSessions)();
540
+ if (active.length === 0) {
541
+ return {
542
+ content: [
543
+ (0, format_js_1.textContent)(`📋 **Active Sessions**\n\n` +
544
+ `No active sessions. Use x402_session_start to establish a session.\n\n` +
545
+ `ℹ️ Sessions are stored in-process and survive for their configured TTL.\n` +
546
+ ` Default TTL: 3600 seconds (1 hour). Set SESSION_TTL_SECONDS to override.`),
547
+ ],
548
+ };
549
+ }
550
+ let out = `📋 **Active x402 Sessions** (${active.length})\n\n`;
551
+ for (const session of active) {
552
+ const ttlRemaining = Math.max(0, session.expiresAt - now);
553
+ const ttlBar = ttlProgressBar(ttlRemaining, session.expiresAt - session.createdAt);
554
+ out += `─────────────────────────────────────\n`;
555
+ out += ` ID: ${session.sessionId}\n`;
556
+ if (session.label)
557
+ out += ` Label: ${session.label}\n`;
558
+ out += ` Endpoint: ${session.endpoint}\n`;
559
+ out += ` Scope: ${session.scope}\n`;
560
+ out += ` TTL: ${formatTtl(ttlRemaining)} ${ttlBar}\n`;
561
+ out += ` Calls: ${session.callCount}\n`;
562
+ out += ` Payment: ${session.paymentAmount} base units → TX ${session.paymentTxHash.slice(0, 18)}...\n`;
563
+ out += '\n';
564
+ }
565
+ out += `\nUse x402_session_fetch with a session_id to make free calls within a session.\n`;
566
+ out += `Use x402_session_status with session_id="..." for full session details.`;
567
+ return { content: [(0, format_js_1.textContent)(out)] };
568
+ }
569
+ catch (error) {
570
+ return {
571
+ content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'x402_session_status'))],
572
+ isError: true,
573
+ };
574
+ }
575
+ }
576
+ // ─── x402_session_end ─────────────────────────────────────────────────────
577
+ exports.X402SessionEndSchema = zod_1.z.object({
578
+ session_id: zod_1.z
579
+ .string()
580
+ .uuid()
581
+ .describe('Session ID to close.'),
582
+ });
583
+ exports.x402SessionEndTool = {
584
+ name: 'x402_session_end',
585
+ description: 'Explicitly close an x402 V2 session before it expires naturally. ' +
586
+ 'After calling this, x402_session_fetch will return an error for the closed session. ' +
587
+ 'Useful for security hygiene or when you know a session is no longer needed.',
588
+ inputSchema: {
589
+ type: 'object',
590
+ properties: {
591
+ session_id: {
592
+ type: 'string',
593
+ description: 'Session ID to close (from x402_session_start)',
594
+ },
595
+ },
596
+ required: ['session_id'],
597
+ },
598
+ };
599
+ async function handleX402SessionEnd(input) {
600
+ try {
601
+ const lookup = (0, manager_js_1.lookupSession)(input.session_id);
602
+ if (!lookup.found) {
603
+ return {
604
+ content: [
605
+ (0, format_js_1.textContent)(`❌ Session not found: "${input.session_id}"`),
606
+ ],
607
+ isError: true,
608
+ };
609
+ }
610
+ if (lookup.expired) {
611
+ return {
612
+ content: [
613
+ (0, format_js_1.textContent)(`ℹ️ Session "${input.session_id}" was already expired.\n` +
614
+ `Endpoint: ${lookup.session.endpoint}`),
615
+ ],
616
+ };
617
+ }
618
+ const { session } = lookup;
619
+ (0, manager_js_1.endSession)(input.session_id);
620
+ return {
621
+ content: [
622
+ (0, format_js_1.textContent)(`✅ **Session Closed**\n\n` +
623
+ ` Session ID: ${session.sessionId}\n` +
624
+ ` Endpoint: ${session.endpoint}\n` +
625
+ ` Calls made: ${session.callCount}\n\n` +
626
+ `The session has been closed and can no longer be used.\n` +
627
+ `Use x402_session_start to establish a new session when needed.`),
628
+ ],
629
+ };
630
+ }
631
+ catch (error) {
632
+ return {
633
+ content: [(0, format_js_1.textContent)((0, format_js_1.formatError)(error, 'x402_session_end'))],
634
+ isError: true,
635
+ };
636
+ }
637
+ }
638
+ // ─── Internal helpers ──────────────────────────────────────────────────────
639
+ /**
640
+ * Check if a URL is covered by a session's endpoint + scope.
641
+ */
642
+ function isUrlCoveredBySession(url, session) {
643
+ if (session.scope === 'exact') {
644
+ return url === session.endpoint;
645
+ }
646
+ // Prefix: URL must start with endpoint
647
+ // Normalise: ensure endpoint doesn't end in slash for prefix matching
648
+ const base = session.endpoint.endsWith('/') ? session.endpoint : session.endpoint + '/';
649
+ return url === session.endpoint || url.startsWith(base) || url.startsWith(session.endpoint + '?');
650
+ }
651
+ /**
652
+ * Format TTL seconds as a human-readable string.
653
+ */
654
+ function formatTtl(seconds) {
655
+ if (seconds <= 0)
656
+ return 'Expired';
657
+ if (seconds < 60)
658
+ return `${seconds}s`;
659
+ if (seconds < 3600)
660
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
661
+ const h = Math.floor(seconds / 3600);
662
+ const m = Math.floor((seconds % 3600) / 60);
663
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
664
+ }
665
+ /**
666
+ * Generate a simple ASCII progress bar for TTL remaining.
667
+ */
668
+ function ttlProgressBar(remaining, total) {
669
+ if (total <= 0)
670
+ return '';
671
+ const pct = Math.max(0, Math.min(1, remaining / total));
672
+ const filled = Math.round(pct * 10);
673
+ const empty = 10 - filled;
674
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
675
+ const emoji = pct > 0.5 ? '🟢' : pct > 0.2 ? '🟡' : '🔴';
676
+ return `${emoji} [${bar}]`;
677
+ }
678
+ //# sourceMappingURL=session.js.map