pagebolt-mcp 1.8.1 → 1.8.2
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/package.json +1 -1
- package/server.json +3 -3
- package/src/index.mjs +240 -174
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pagebolt-mcp",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "MCP server for PageBolt — take screenshots, generate PDFs, create OG images, inspect pages, record demo videos with Audio Guide narration, from AI coding assistants like Claude, Cursor, and Windsurf.",
|
|
5
5
|
"main": "src/index.mjs",
|
|
6
6
|
"module": "src/index.mjs",
|
package/server.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.Custodia-Admin/pagebolt",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Screenshots, PDFs, OG images, page inspection, and narrated video recording for Claude and Cursor.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/Custodia-Admin/pagebolt-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.8.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "pagebolt-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.8.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/src/index.mjs
CHANGED
|
@@ -50,35 +50,62 @@ function requireApiKey() {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// ─── HTTP helper
|
|
53
|
+
// ─── HTTP helper (with timeout + retry) ─────────────────────────
|
|
54
|
+
const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
|
|
55
|
+
const MAX_RETRIES = 1;
|
|
56
|
+
const REQUEST_TIMEOUT_MS = 120_000;
|
|
57
|
+
|
|
54
58
|
async function callApi(endpoint, options = {}) {
|
|
55
59
|
requireApiKey();
|
|
56
60
|
const url = `${BASE_URL}${endpoint}`;
|
|
57
61
|
const method = options.method || 'GET';
|
|
58
62
|
const headers = {
|
|
59
63
|
'x-api-key': API_KEY,
|
|
60
|
-
'user-agent': 'pagebolt-mcp/1.
|
|
64
|
+
'user-agent': 'pagebolt-mcp/1.8.2',
|
|
61
65
|
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
62
66
|
};
|
|
67
|
+
const body = options.body ? JSON.stringify(options.body) : undefined;
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
69
|
+
let lastError;
|
|
70
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
69
73
|
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
let errorMsg;
|
|
72
74
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const res = await fetch(url, { method, headers, body, signal: controller.signal });
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
|
|
78
|
+
if (res.ok) return res;
|
|
79
|
+
|
|
80
|
+
if (RETRYABLE_STATUSES.has(res.status) && attempt < MAX_RETRIES) {
|
|
81
|
+
const retryAfter = parseInt(res.headers.get('retry-after'), 10);
|
|
82
|
+
const delayMs = retryAfter > 0 ? retryAfter * 1000 : 1000 * (attempt + 1);
|
|
83
|
+
await new Promise(r => setTimeout(r, Math.min(delayMs, 10_000)));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let errorMsg;
|
|
88
|
+
try {
|
|
89
|
+
const errJson = await res.json();
|
|
90
|
+
errorMsg = errJson.error || JSON.stringify(errJson);
|
|
91
|
+
} catch {
|
|
92
|
+
errorMsg = `HTTP ${res.status} ${res.statusText}`;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`PageBolt API error: ${errorMsg}`);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
if (err.name === 'AbortError') {
|
|
98
|
+
throw new Error(`PageBolt API error: request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);
|
|
99
|
+
}
|
|
100
|
+
lastError = err;
|
|
101
|
+
if (attempt < MAX_RETRIES && !err.message.startsWith('PageBolt API error:')) {
|
|
102
|
+
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
77
106
|
}
|
|
78
|
-
throw new Error(`PageBolt API error: ${errorMsg}`);
|
|
79
107
|
}
|
|
80
|
-
|
|
81
|
-
return res;
|
|
108
|
+
throw lastError;
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
// ─── MIME type helper ────────────────────────────────────────────
|
|
@@ -219,7 +246,7 @@ Use blockBanners on almost every request to get clean captures. Combine blockAds
|
|
|
219
246
|
function createConfiguredServer() {
|
|
220
247
|
const srv = new McpServer({
|
|
221
248
|
name: 'pagebolt',
|
|
222
|
-
version: '1.
|
|
249
|
+
version: '1.8.2',
|
|
223
250
|
}, {
|
|
224
251
|
instructions: SERVER_INSTRUCTIONS,
|
|
225
252
|
});
|
|
@@ -314,35 +341,38 @@ server.tool(
|
|
|
314
341
|
return { content: [{ type: 'text', text: 'Error: One of "url", "html", or "markdown" is required.' }], isError: true };
|
|
315
342
|
}
|
|
316
343
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const data = await res.json();
|
|
323
|
-
const format = params.format || 'png';
|
|
324
|
-
|
|
325
|
-
const content = [
|
|
326
|
-
{
|
|
327
|
-
type: 'image',
|
|
328
|
-
data: data.data,
|
|
329
|
-
mimeType: imageMimeType(format),
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
type: 'text',
|
|
333
|
-
text: `Screenshot captured successfully. Format: ${format}, Size: ${data.size_bytes} bytes, Duration: ${data.duration_ms}ms`,
|
|
334
|
-
},
|
|
335
|
-
];
|
|
336
|
-
|
|
337
|
-
// Include metadata if extracted
|
|
338
|
-
if (data.metadata) {
|
|
339
|
-
content.push({
|
|
340
|
-
type: 'text',
|
|
341
|
-
text: `Metadata:\n${JSON.stringify(data.metadata, null, 2)}`,
|
|
344
|
+
try {
|
|
345
|
+
const res = await callApi('/api/v1/screenshot', {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
body: { ...params, response_type: 'json' },
|
|
342
348
|
});
|
|
343
|
-
}
|
|
344
349
|
|
|
345
|
-
|
|
350
|
+
const data = await res.json();
|
|
351
|
+
const format = params.format || 'png';
|
|
352
|
+
|
|
353
|
+
const content = [
|
|
354
|
+
{
|
|
355
|
+
type: 'image',
|
|
356
|
+
data: data.data,
|
|
357
|
+
mimeType: imageMimeType(format),
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
type: 'text',
|
|
361
|
+
text: `Screenshot captured successfully. Format: ${format}, Size: ${data.size_bytes} bytes, Duration: ${data.duration_ms}ms`,
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
if (data.metadata) {
|
|
366
|
+
content.push({
|
|
367
|
+
type: 'text',
|
|
368
|
+
text: `Metadata:\n${JSON.stringify(data.metadata, null, 2)}`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return { content };
|
|
373
|
+
} catch (err) {
|
|
374
|
+
return { content: [{ type: 'text', text: `Screenshot error: ${err.message}` }], isError: true };
|
|
375
|
+
}
|
|
346
376
|
}
|
|
347
377
|
);
|
|
348
378
|
|
|
@@ -381,49 +411,51 @@ server.tool(
|
|
|
381
411
|
return { content: [{ type: 'text', text: 'Error: Either "url" or "html" is required.' }], isError: true };
|
|
382
412
|
}
|
|
383
413
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
414
|
+
try {
|
|
415
|
+
const { saveTo, ...apiParams } = params;
|
|
416
|
+
const res = await callApi('/api/v1/pdf', {
|
|
417
|
+
method: 'POST',
|
|
418
|
+
body: { ...apiParams, response_type: 'json' },
|
|
419
|
+
});
|
|
389
420
|
|
|
390
|
-
|
|
421
|
+
const data = await res.json();
|
|
391
422
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// still returned as an embedded resource below, so the client gets it.
|
|
402
|
-
}
|
|
423
|
+
let savedPath = null;
|
|
424
|
+
try {
|
|
425
|
+
const outputPath = safePath(saveTo, './output.pdf');
|
|
426
|
+
const buffer = Buffer.from(data.data, 'base64');
|
|
427
|
+
writeFileSync(outputPath, buffer);
|
|
428
|
+
savedPath = outputPath;
|
|
429
|
+
} catch (_diskErr) {
|
|
430
|
+
// Disk write failed — data still returned as embedded resource
|
|
431
|
+
}
|
|
403
432
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
433
|
+
const fileNote = savedPath
|
|
434
|
+
? ` File: ${savedPath}`
|
|
435
|
+
: ` File: (not saved to disk — use the embedded resource data below)`;
|
|
407
436
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
437
|
+
return {
|
|
438
|
+
content: [
|
|
439
|
+
{
|
|
440
|
+
type: 'resource',
|
|
441
|
+
resource: {
|
|
442
|
+
uri: 'pagebolt://pdf/output.pdf',
|
|
443
|
+
mimeType: 'application/pdf',
|
|
444
|
+
blob: data.data,
|
|
445
|
+
},
|
|
416
446
|
},
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
447
|
+
{
|
|
448
|
+
type: 'text',
|
|
449
|
+
text: `PDF generated successfully.\n` +
|
|
450
|
+
`${fileNote}\n` +
|
|
451
|
+
` Size: ${data.size_bytes} bytes\n` +
|
|
452
|
+
` Duration: ${data.duration_ms}ms`,
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
};
|
|
456
|
+
} catch (err) {
|
|
457
|
+
return { content: [{ type: 'text', text: `PDF error: ${err.message}` }], isError: true };
|
|
458
|
+
}
|
|
427
459
|
}
|
|
428
460
|
);
|
|
429
461
|
|
|
@@ -448,27 +480,31 @@ server.tool(
|
|
|
448
480
|
format: z.enum(['png', 'jpeg', 'webp']).optional().describe('Image format (default: png)'),
|
|
449
481
|
},
|
|
450
482
|
async (params) => {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
483
|
+
try {
|
|
484
|
+
const res = await callApi('/api/v1/og-image', {
|
|
485
|
+
method: 'POST',
|
|
486
|
+
body: { ...params, response_type: 'json' },
|
|
487
|
+
});
|
|
455
488
|
|
|
456
|
-
|
|
457
|
-
|
|
489
|
+
const data = await res.json();
|
|
490
|
+
const format = params.format || 'png';
|
|
458
491
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
492
|
+
return {
|
|
493
|
+
content: [
|
|
494
|
+
{
|
|
495
|
+
type: 'image',
|
|
496
|
+
data: data.data,
|
|
497
|
+
mimeType: imageMimeType(format),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
type: 'text',
|
|
501
|
+
text: `OG image created successfully. Format: ${format}, Size: ${data.size_bytes} bytes, Duration: ${data.duration_ms}ms`,
|
|
502
|
+
},
|
|
470
503
|
],
|
|
471
504
|
};
|
|
505
|
+
} catch (err) {
|
|
506
|
+
return { content: [{ type: 'text', text: `OG image error: ${err.message}` }], isError: true };
|
|
507
|
+
}
|
|
472
508
|
}
|
|
473
509
|
);
|
|
474
510
|
|
|
@@ -546,9 +582,19 @@ server.tool(
|
|
|
546
582
|
text: `[${output.name}] Screenshot — ${output.format}, ${output.size_bytes} bytes, step ${output.step_index}`,
|
|
547
583
|
});
|
|
548
584
|
} else if (output.type === 'pdf') {
|
|
585
|
+
if (output.data) {
|
|
586
|
+
content.push({
|
|
587
|
+
type: 'resource',
|
|
588
|
+
resource: {
|
|
589
|
+
uri: `pagebolt://sequence-pdf/${output.name || `step-${output.step_index}`}`,
|
|
590
|
+
mimeType: 'application/pdf',
|
|
591
|
+
blob: output.data,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
}
|
|
549
595
|
content.push({
|
|
550
596
|
type: 'text',
|
|
551
|
-
text: `[${output.name}] PDF generated — ${output.
|
|
597
|
+
text: `[${output.name}] PDF generated — ${output.size_bytes} bytes, step ${output.step_index}`,
|
|
552
598
|
});
|
|
553
599
|
}
|
|
554
600
|
}
|
|
@@ -890,26 +936,29 @@ server.tool(
|
|
|
890
936
|
'List all available device presets for viewport emulation (e.g. iphone_14_pro, macbook_pro_14). Use the returned device names with the viewportDevice parameter in take_screenshot.',
|
|
891
937
|
{},
|
|
892
938
|
async () => {
|
|
893
|
-
|
|
894
|
-
|
|
939
|
+
try {
|
|
940
|
+
const res = await callApi('/api/v1/devices');
|
|
941
|
+
const data = await res.json();
|
|
895
942
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
});
|
|
943
|
+
const lines = data.devices.map((d) => {
|
|
944
|
+
const mobile = d.mobile ? ', mobile' : '';
|
|
945
|
+
return ` ${d.id} — ${d.name} — ${d.width}x${d.height} @${d.deviceScaleFactor}x${mobile}`;
|
|
946
|
+
});
|
|
901
947
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
948
|
+
return {
|
|
949
|
+
content: [
|
|
950
|
+
{
|
|
951
|
+
type: 'text',
|
|
952
|
+
text:
|
|
953
|
+
`Available device presets (${data.devices.length}):\n` +
|
|
954
|
+
lines.join('\n') +
|
|
955
|
+
`\n\nUse the device name as the "viewportDevice" parameter in take_screenshot.`,
|
|
956
|
+
},
|
|
957
|
+
],
|
|
958
|
+
};
|
|
959
|
+
} catch (err) {
|
|
960
|
+
return { content: [{ type: 'text', text: `List devices error: ${err.message}` }], isError: true };
|
|
961
|
+
}
|
|
913
962
|
}
|
|
914
963
|
);
|
|
915
964
|
|
|
@@ -921,25 +970,29 @@ server.tool(
|
|
|
921
970
|
'Check your current PageBolt API usage and plan limits.',
|
|
922
971
|
{},
|
|
923
972
|
async () => {
|
|
924
|
-
|
|
925
|
-
|
|
973
|
+
try {
|
|
974
|
+
const res = await callApi('/api/v1/usage');
|
|
975
|
+
const data = await res.json();
|
|
926
976
|
|
|
927
|
-
|
|
928
|
-
|
|
977
|
+
const { plan, usage } = data;
|
|
978
|
+
const pct = usage.limit > 0 ? Math.round((usage.current / usage.limit) * 100) : 0;
|
|
929
979
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
980
|
+
return {
|
|
981
|
+
content: [
|
|
982
|
+
{
|
|
983
|
+
type: 'text',
|
|
984
|
+
text:
|
|
985
|
+
`PageBolt Usage\n` +
|
|
986
|
+
` Plan: ${plan}\n` +
|
|
987
|
+
` Used: ${usage.current.toLocaleString()} / ${usage.limit.toLocaleString()} requests\n` +
|
|
988
|
+
` Remaining: ${usage.remaining.toLocaleString()}\n` +
|
|
989
|
+
` Usage: ${pct}%`,
|
|
990
|
+
},
|
|
991
|
+
],
|
|
992
|
+
};
|
|
993
|
+
} catch (err) {
|
|
994
|
+
return { content: [{ type: 'text', text: `Usage check error: ${err.message}` }], isError: true };
|
|
995
|
+
}
|
|
943
996
|
}
|
|
944
997
|
);
|
|
945
998
|
|
|
@@ -958,24 +1011,28 @@ server.tool(
|
|
|
958
1011
|
stealth: z.boolean().optional().describe('Launch this session with stealth mode (bypasses bot detection). Note: stealth sessions use a dedicated browser and consume more memory.'),
|
|
959
1012
|
},
|
|
960
1013
|
async (params) => {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1014
|
+
try {
|
|
1015
|
+
const res = await callApi('/api/v1/sessions', {
|
|
1016
|
+
method: 'POST',
|
|
1017
|
+
body: params,
|
|
1018
|
+
});
|
|
1019
|
+
const data = await res.json();
|
|
1020
|
+
return {
|
|
1021
|
+
content: [
|
|
1022
|
+
{
|
|
1023
|
+
type: 'text',
|
|
1024
|
+
text:
|
|
1025
|
+
`Session created.\n` +
|
|
1026
|
+
` session_id: ${data.session_id}\n` +
|
|
1027
|
+
` expires_at: ${data.expires_at}\n\n` +
|
|
1028
|
+
`Pass session_id to take_screenshot or run_sequence to reuse this browser page.\n` +
|
|
1029
|
+
`Note: ${data.note || 'Sessions do not persist across server restarts.'}`,
|
|
1030
|
+
},
|
|
1031
|
+
],
|
|
1032
|
+
};
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
return { content: [{ type: 'text', text: `Create session error: ${err.message}` }], isError: true };
|
|
1035
|
+
}
|
|
979
1036
|
}
|
|
980
1037
|
);
|
|
981
1038
|
|
|
@@ -987,17 +1044,22 @@ server.tool(
|
|
|
987
1044
|
'List all active persistent browser sessions for your API key. Returns session IDs, creation times, and expiry times. Useful for checking which sessions are still alive before reusing them.',
|
|
988
1045
|
{},
|
|
989
1046
|
async () => {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1047
|
+
try {
|
|
1048
|
+
const res = await callApi('/api/v1/sessions', { method: 'GET' });
|
|
1049
|
+
const data = await res.json();
|
|
1050
|
+
const sessions = data.sessions || [];
|
|
1051
|
+
if (sessions.length === 0) {
|
|
1052
|
+
return { content: [{ type: 'text', text: 'No active sessions.' }] };
|
|
1053
|
+
}
|
|
1054
|
+
const lines = sessions.map(s =>
|
|
1055
|
+
`• ${s.session_id} expires: ${s.expires_at} created: ${s.created_at}`
|
|
1056
|
+
);
|
|
1057
|
+
return {
|
|
1058
|
+
content: [{ type: 'text', text: `Active sessions (${sessions.length}):\n${lines.join('\n')}` }],
|
|
1059
|
+
};
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
return { content: [{ type: 'text', text: `List sessions error: ${err.message}` }], isError: true };
|
|
994
1062
|
}
|
|
995
|
-
const lines = sessions.map(s =>
|
|
996
|
-
`• ${s.session_id} expires: ${s.expires_at} created: ${s.created_at}`
|
|
997
|
-
);
|
|
998
|
-
return {
|
|
999
|
-
content: [{ type: 'text', text: `Active sessions (${sessions.length}):\n${lines.join('\n')}` }],
|
|
1000
|
-
};
|
|
1001
1063
|
}
|
|
1002
1064
|
);
|
|
1003
1065
|
|
|
@@ -1010,17 +1072,21 @@ server.tool(
|
|
|
1010
1072
|
session_id: z.string().describe('The session ID to destroy (returned by create_session)'),
|
|
1011
1073
|
},
|
|
1012
1074
|
async (params) => {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1075
|
+
try {
|
|
1076
|
+
await callApi(`/api/v1/sessions/${encodeURIComponent(params.session_id)}`, {
|
|
1077
|
+
method: 'DELETE',
|
|
1078
|
+
});
|
|
1079
|
+
return {
|
|
1080
|
+
content: [
|
|
1081
|
+
{
|
|
1082
|
+
type: 'text',
|
|
1083
|
+
text: `Session ${params.session_id} destroyed successfully.`,
|
|
1084
|
+
},
|
|
1085
|
+
],
|
|
1086
|
+
};
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
return { content: [{ type: 'text', text: `Destroy session error: ${err.message}` }], isError: true };
|
|
1089
|
+
}
|
|
1024
1090
|
}
|
|
1025
1091
|
);
|
|
1026
1092
|
|