mcp-use 1.11.0-canary.7 → 1.11.0-canary.9
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/.tsbuildinfo +1 -1
- package/dist/{chunk-REYY7LSD.js → chunk-5QFJZ7H3.js} +2 -2
- package/dist/chunk-D3CNYAYE.js +1055 -0
- package/dist/{chunk-OD6B7KGQ.js → chunk-ESMOFYJ6.js} +27 -2100
- package/dist/{chunk-WTGUJLTR.js → chunk-F3BZFJCD.js} +167 -7
- package/dist/chunk-GXNAXUDI.js +0 -0
- package/dist/{chunk-QP7MQ2UJ.js → chunk-HU2DGJ5J.js} +175 -133
- package/dist/{chunk-REX2YTWF.js → chunk-M7WATKYM.js} +1 -1
- package/dist/chunk-MFSO5PUW.js +1049 -0
- package/dist/{chunk-5LBXMCKC.js → chunk-N3DO4P2L.js} +27 -2100
- package/dist/{chunk-M7CHBY4S.js → chunk-OWPXM4QQ.js} +1 -1
- package/dist/{chunk-3QVRNWW7.js → chunk-Q5LZL6BH.js} +1 -1
- package/dist/{chunk-ZN3MKSKM.js → chunk-UCPSHMNO.js} +1 -1
- package/dist/chunk-UWWLWLS2.js +62 -0
- package/dist/chunk-WW3A2EKQ.js +1055 -0
- package/dist/{chunk-CHHWJQVC.js → chunk-XEFWIBQF.js} +1 -1
- package/dist/index.cjs +211 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -28
- package/dist/notifications-FLGIFS56.js +9 -0
- package/dist/src/adapters/index.cjs +1346 -0
- package/dist/src/adapters/index.js +11 -0
- package/dist/src/agents/index.cjs +46 -4
- package/dist/src/agents/index.js +8 -6
- package/dist/src/browser.cjs +46 -5
- package/dist/src/browser.d.ts +1 -1
- package/dist/src/browser.d.ts.map +1 -1
- package/dist/src/browser.js +15 -13
- package/dist/src/client/prompts.js +4 -4
- package/dist/src/client.cjs +3787 -0
- package/dist/src/client.js +20 -0
- package/dist/src/react/index.cjs +211 -9
- package/dist/src/react/index.js +5 -5
- package/dist/src/react/types.d.ts +41 -1
- package/dist/src/react/types.d.ts.map +1 -1
- package/dist/src/react/useMcp.d.ts.map +1 -1
- package/dist/src/react/useWidget.d.ts +11 -7
- package/dist/src/react/useWidget.d.ts.map +1 -1
- package/dist/src/react/widget-types.d.ts +6 -2
- package/dist/src/react/widget-types.d.ts.map +1 -1
- package/dist/src/server/endpoints/mount-mcp.d.ts.map +1 -1
- package/dist/src/server/index.cjs +1269 -144
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +1158 -100
- package/dist/src/server/mcp-server.d.ts +5 -1
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/notifications/index.d.ts +1 -1
- package/dist/src/server/notifications/index.d.ts.map +1 -1
- package/dist/src/server/notifications/notification-registration.d.ts +51 -0
- package/dist/src/server/notifications/notification-registration.d.ts.map +1 -1
- package/dist/src/server/sessions/index.d.ts +3 -1
- package/dist/src/server/sessions/index.d.ts.map +1 -1
- package/dist/src/server/sessions/session-manager.d.ts +30 -16
- package/dist/src/server/sessions/session-manager.d.ts.map +1 -1
- package/dist/src/server/sessions/stores/filesystem.d.ts +121 -0
- package/dist/src/server/sessions/stores/filesystem.d.ts.map +1 -0
- package/dist/src/server/sessions/stores/index.d.ts +94 -0
- package/dist/src/server/sessions/stores/index.d.ts.map +1 -0
- package/dist/src/server/sessions/stores/memory.d.ts +82 -0
- package/dist/src/server/sessions/stores/memory.d.ts.map +1 -0
- package/dist/src/server/sessions/stores/redis.d.ts +164 -0
- package/dist/src/server/sessions/stores/redis.d.ts.map +1 -0
- package/dist/src/server/sessions/streams/index.d.ts +77 -0
- package/dist/src/server/sessions/streams/index.d.ts.map +1 -0
- package/dist/src/server/sessions/streams/memory.d.ts +76 -0
- package/dist/src/server/sessions/streams/memory.d.ts.map +1 -0
- package/dist/src/server/sessions/streams/redis.d.ts +146 -0
- package/dist/src/server/sessions/streams/redis.d.ts.map +1 -0
- package/dist/src/server/types/common.d.ts +105 -28
- package/dist/src/server/types/common.d.ts.map +1 -1
- package/dist/src/server/types/resource.d.ts +16 -0
- package/dist/src/server/types/resource.d.ts.map +1 -1
- package/dist/src/server/types/widget.d.ts +21 -2
- package/dist/src/server/types/widget.d.ts.map +1 -1
- package/dist/src/server/utils/response-helpers.d.ts +12 -6
- package/dist/src/server/utils/response-helpers.d.ts.map +1 -1
- package/dist/src/server/widgets/index.d.ts +1 -1
- package/dist/src/server/widgets/index.d.ts.map +1 -1
- package/dist/src/server/widgets/mount-widgets-dev.d.ts.map +1 -1
- package/dist/src/server/widgets/setup-widget-routes.d.ts.map +1 -1
- package/dist/src/server/widgets/ui-resource-registration.d.ts.map +1 -1
- package/dist/src/server/widgets/widget-helpers.d.ts +22 -0
- package/dist/src/server/widgets/widget-helpers.d.ts.map +1 -1
- package/dist/src/server/widgets/widget-types.d.ts +2 -0
- package/dist/src/server/widgets/widget-types.d.ts.map +1 -1
- package/dist/src/task_managers/index.d.ts +10 -0
- package/dist/src/task_managers/index.d.ts.map +1 -1
- package/dist/src/task_managers/sse.d.ts +34 -1
- package/dist/src/task_managers/sse.d.ts.map +1 -1
- package/dist/src/task_managers/streamable_http.d.ts +8 -2
- package/dist/src/task_managers/streamable_http.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/{tool-execution-helpers-PAFGGAGL.js → tool-execution-helpers-MXVN6YNU.js} +2 -2
- package/dist/tsup.config.d.ts.map +1 -1
- package/package.json +29 -5
- /package/dist/{chunk-H4BZVTGK.js → chunk-LGDFGYRL.js} +0 -0
|
@@ -413,8 +413,8 @@ var init_runtime = __esm({
|
|
|
413
413
|
if (isDeno) {
|
|
414
414
|
return await globalThis.Deno.readTextFile(path);
|
|
415
415
|
}
|
|
416
|
-
const { readFileSync } = await import("fs");
|
|
417
|
-
const result =
|
|
416
|
+
const { readFileSync: readFileSync2 } = await import("fs");
|
|
417
|
+
const result = readFileSync2(path, encoding);
|
|
418
418
|
return typeof result === "string" ? result : result.toString(encoding);
|
|
419
419
|
},
|
|
420
420
|
async readFile(path) {
|
|
@@ -422,8 +422,8 @@ var init_runtime = __esm({
|
|
|
422
422
|
const data = await globalThis.Deno.readFile(path);
|
|
423
423
|
return data.buffer;
|
|
424
424
|
}
|
|
425
|
-
const { readFileSync } = await import("fs");
|
|
426
|
-
const buffer =
|
|
425
|
+
const { readFileSync: readFileSync2 } = await import("fs");
|
|
426
|
+
const buffer = readFileSync2(path);
|
|
427
427
|
return buffer.buffer.slice(
|
|
428
428
|
buffer.byteOffset,
|
|
429
429
|
buffer.byteOffset + buffer.byteLength
|
|
@@ -438,8 +438,8 @@ var init_runtime = __esm({
|
|
|
438
438
|
return false;
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
|
-
const { existsSync } = await import("fs");
|
|
442
|
-
return
|
|
441
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
442
|
+
return existsSync2(path);
|
|
443
443
|
},
|
|
444
444
|
async readdirSync(path) {
|
|
445
445
|
if (isDeno) {
|
|
@@ -761,7 +761,7 @@ var VERSION;
|
|
|
761
761
|
var init_version = __esm({
|
|
762
762
|
"src/version.ts"() {
|
|
763
763
|
"use strict";
|
|
764
|
-
VERSION = "1.11.0-canary.
|
|
764
|
+
VERSION = "1.11.0-canary.9";
|
|
765
765
|
__name(getPackageVersion, "getPackageVersion");
|
|
766
766
|
}
|
|
767
767
|
});
|
|
@@ -2098,10 +2098,84 @@ var init_conversion2 = __esm({
|
|
|
2098
2098
|
}
|
|
2099
2099
|
});
|
|
2100
2100
|
|
|
2101
|
+
// src/server/utils/jsonrpc-helpers.ts
|
|
2102
|
+
function createNotification(method, params) {
|
|
2103
|
+
return {
|
|
2104
|
+
jsonrpc: "2.0",
|
|
2105
|
+
method,
|
|
2106
|
+
...params && { params }
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
function createRequest(id, method, params) {
|
|
2110
|
+
return {
|
|
2111
|
+
jsonrpc: "2.0",
|
|
2112
|
+
id,
|
|
2113
|
+
method,
|
|
2114
|
+
...params && { params }
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
var init_jsonrpc_helpers = __esm({
|
|
2118
|
+
"src/server/utils/jsonrpc-helpers.ts"() {
|
|
2119
|
+
"use strict";
|
|
2120
|
+
__name(createNotification, "createNotification");
|
|
2121
|
+
__name(createRequest, "createRequest");
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
// src/server/sessions/notifications.ts
|
|
2126
|
+
var notifications_exports = {};
|
|
2127
|
+
__export(notifications_exports, {
|
|
2128
|
+
sendNotificationToAll: () => sendNotificationToAll,
|
|
2129
|
+
sendNotificationToSession: () => sendNotificationToSession
|
|
2130
|
+
});
|
|
2131
|
+
async function sendNotificationToAll(sessions, method, params) {
|
|
2132
|
+
const notification = createNotification(method, params);
|
|
2133
|
+
for (const [sessionId, session] of sessions.entries()) {
|
|
2134
|
+
try {
|
|
2135
|
+
await session.transport.send(notification);
|
|
2136
|
+
} catch (error2) {
|
|
2137
|
+
console.warn(
|
|
2138
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2139
|
+
error2
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
async function sendNotificationToSession(sessions, sessionId, method, params) {
|
|
2145
|
+
const session = sessions.get(sessionId);
|
|
2146
|
+
if (!session) {
|
|
2147
|
+
return false;
|
|
2148
|
+
}
|
|
2149
|
+
const notification = createNotification(method, params);
|
|
2150
|
+
try {
|
|
2151
|
+
await session.transport.send(notification);
|
|
2152
|
+
return true;
|
|
2153
|
+
} catch (error2) {
|
|
2154
|
+
console.warn(
|
|
2155
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2156
|
+
error2
|
|
2157
|
+
);
|
|
2158
|
+
return false;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
var init_notifications = __esm({
|
|
2162
|
+
"src/server/sessions/notifications.ts"() {
|
|
2163
|
+
"use strict";
|
|
2164
|
+
init_jsonrpc_helpers();
|
|
2165
|
+
__name(sendNotificationToAll, "sendNotificationToAll");
|
|
2166
|
+
__name(sendNotificationToSession, "sendNotificationToSession");
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2101
2170
|
// src/server/index.ts
|
|
2102
2171
|
var server_exports = {};
|
|
2103
2172
|
__export(server_exports, {
|
|
2173
|
+
FileSystemSessionStore: () => FileSystemSessionStore,
|
|
2174
|
+
InMemorySessionStore: () => InMemorySessionStore,
|
|
2175
|
+
InMemoryStreamManager: () => InMemoryStreamManager,
|
|
2104
2176
|
MCPServer: () => MCPServer,
|
|
2177
|
+
RedisSessionStore: () => RedisSessionStore,
|
|
2178
|
+
RedisStreamManager: () => RedisStreamManager,
|
|
2105
2179
|
VERSION: () => VERSION,
|
|
2106
2180
|
adaptConnectMiddleware: () => adaptConnectMiddleware,
|
|
2107
2181
|
adaptMiddleware: () => adaptMiddleware,
|
|
@@ -2427,17 +2501,23 @@ function binary(base64Data, mimeType) {
|
|
|
2427
2501
|
}
|
|
2428
2502
|
__name(binary, "binary");
|
|
2429
2503
|
function widget(config) {
|
|
2430
|
-
const
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
}
|
|
2437
|
-
],
|
|
2438
|
-
// structuredContent will be injected as window.openai.toolOutput by Apps SDK
|
|
2439
|
-
structuredContent: data
|
|
2504
|
+
const props = config.props || config.data || {};
|
|
2505
|
+
const { output, message } = config;
|
|
2506
|
+
const finalContent = message ? [{ type: "text", text: message }] : Array.isArray(output?.content) && output.content.length > 0 ? output.content : [{ type: "text", text: "" }];
|
|
2507
|
+
const meta = {
|
|
2508
|
+
...output?._meta || {},
|
|
2509
|
+
"mcp-use/props": props
|
|
2440
2510
|
};
|
|
2511
|
+
const result = {
|
|
2512
|
+
content: finalContent,
|
|
2513
|
+
_meta: meta
|
|
2514
|
+
};
|
|
2515
|
+
if (output?.structuredContent) {
|
|
2516
|
+
result.structuredContent = output.structuredContent;
|
|
2517
|
+
} else if (Object.keys(props).length > 0) {
|
|
2518
|
+
result.structuredContent = props;
|
|
2519
|
+
}
|
|
2520
|
+
return result;
|
|
2441
2521
|
}
|
|
2442
2522
|
__name(widget, "widget");
|
|
2443
2523
|
function mix(...results) {
|
|
@@ -3107,7 +3187,7 @@ function processWidgetHtml(html2, widgetName, baseUrl) {
|
|
|
3107
3187
|
}
|
|
3108
3188
|
__name(processWidgetHtml, "processWidgetHtml");
|
|
3109
3189
|
function createWidgetRegistration(widgetName, metadata, html2, serverConfig, isDev = false) {
|
|
3110
|
-
const props = metadata.inputs || {};
|
|
3190
|
+
const props = metadata.props || metadata.inputs || metadata.schema || {};
|
|
3111
3191
|
const description = metadata.description || `Widget: ${widgetName}`;
|
|
3112
3192
|
const title = metadata.title || widgetName;
|
|
3113
3193
|
const exposeAsTool = metadata.exposeAsTool !== void 0 ? metadata.exposeAsTool : true;
|
|
@@ -3249,6 +3329,33 @@ function setupPublicRoutes(app, useDistDirectory = false) {
|
|
|
3249
3329
|
});
|
|
3250
3330
|
}
|
|
3251
3331
|
__name(setupPublicRoutes, "setupPublicRoutes");
|
|
3332
|
+
function setupFaviconRoute(app, faviconPath, useDistDirectory = false) {
|
|
3333
|
+
if (!faviconPath) {
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
app.get("/favicon.ico", async (c) => {
|
|
3337
|
+
const basePath = useDistDirectory ? "dist/public" : "public";
|
|
3338
|
+
const fullPath = pathHelpers.join(getCwd(), basePath, faviconPath);
|
|
3339
|
+
try {
|
|
3340
|
+
if (await fsHelpers.existsSync(fullPath)) {
|
|
3341
|
+
const content = await fsHelpers.readFile(fullPath);
|
|
3342
|
+
const contentType = getContentType(faviconPath);
|
|
3343
|
+
return new Response(content, {
|
|
3344
|
+
status: 200,
|
|
3345
|
+
headers: {
|
|
3346
|
+
"Content-Type": contentType,
|
|
3347
|
+
"Cache-Control": "public, max-age=31536000"
|
|
3348
|
+
// Cache for 1 year
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
return c.notFound();
|
|
3353
|
+
} catch {
|
|
3354
|
+
return c.notFound();
|
|
3355
|
+
}
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
__name(setupFaviconRoute, "setupFaviconRoute");
|
|
3252
3359
|
|
|
3253
3360
|
// src/server/widgets/mount-widgets-dev.ts
|
|
3254
3361
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
@@ -3298,6 +3405,19 @@ async function mountWidgetsDev(app, serverConfig, registerWidget, options) {
|
|
|
3298
3405
|
return;
|
|
3299
3406
|
}
|
|
3300
3407
|
const tempDir = pathHelpers.join(getCwd(), TMP_MCP_USE_DIR);
|
|
3408
|
+
try {
|
|
3409
|
+
await fs.access(tempDir);
|
|
3410
|
+
const currentWidgetNames = new Set(entries.map((e) => e.name));
|
|
3411
|
+
const existingDirs = await fs.readdir(tempDir, { withFileTypes: true });
|
|
3412
|
+
for (const dirent of existingDirs) {
|
|
3413
|
+
if (dirent.isDirectory() && !currentWidgetNames.has(dirent.name)) {
|
|
3414
|
+
const staleDir = pathHelpers.join(tempDir, dirent.name);
|
|
3415
|
+
await fs.rm(staleDir, { recursive: true, force: true });
|
|
3416
|
+
console.log(`[WIDGETS] Cleaned up stale widget: ${dirent.name}`);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
} catch {
|
|
3420
|
+
}
|
|
3301
3421
|
await fs.mkdir(tempDir, { recursive: true }).catch(() => {
|
|
3302
3422
|
});
|
|
3303
3423
|
let createServer;
|
|
@@ -3364,7 +3484,8 @@ if (container && Component) {
|
|
|
3364
3484
|
<head>
|
|
3365
3485
|
<meta charset="UTF-8" />
|
|
3366
3486
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
3367
|
-
<title>${widget2.name} Widget</title
|
|
3487
|
+
<title>${widget2.name} Widget</title>${serverConfig.favicon ? `
|
|
3488
|
+
<link rel="icon" href="/mcp-use/public/${serverConfig.favicon}" />` : ""}
|
|
3368
3489
|
</head>
|
|
3369
3490
|
<body>
|
|
3370
3491
|
<div id="widget-root"></div>
|
|
@@ -3408,6 +3529,50 @@ if (container && Component) {
|
|
|
3408
3529
|
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
3409
3530
|
server.watcher.add(resourcesPath);
|
|
3410
3531
|
console.log(`[WIDGETS] Watching resources directory: ${resourcesPath}`);
|
|
3532
|
+
server.watcher.on("unlink", async (filePath) => {
|
|
3533
|
+
const relativePath = pathHelpers.relative(resourcesPath, filePath);
|
|
3534
|
+
if ((relativePath.endsWith(".tsx") || relativePath.endsWith(".ts")) && !relativePath.includes("/")) {
|
|
3535
|
+
const widgetName = relativePath.replace(/\.tsx?$/, "");
|
|
3536
|
+
const widgetDir = pathHelpers.join(tempDir, widgetName);
|
|
3537
|
+
try {
|
|
3538
|
+
await fs.access(widgetDir);
|
|
3539
|
+
await fs.rm(widgetDir, { recursive: true, force: true });
|
|
3540
|
+
console.log(
|
|
3541
|
+
`[WIDGETS] Cleaned up stale widget (file removed): ${widgetName}`
|
|
3542
|
+
);
|
|
3543
|
+
} catch {
|
|
3544
|
+
}
|
|
3545
|
+
} else if (relativePath.endsWith("widget.tsx")) {
|
|
3546
|
+
const parts = relativePath.split("/");
|
|
3547
|
+
if (parts.length === 2) {
|
|
3548
|
+
const widgetName = parts[0];
|
|
3549
|
+
const widgetDir = pathHelpers.join(tempDir, widgetName);
|
|
3550
|
+
try {
|
|
3551
|
+
await fs.access(widgetDir);
|
|
3552
|
+
await fs.rm(widgetDir, { recursive: true, force: true });
|
|
3553
|
+
console.log(
|
|
3554
|
+
`[WIDGETS] Cleaned up stale widget (file removed): ${widgetName}`
|
|
3555
|
+
);
|
|
3556
|
+
} catch {
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
});
|
|
3561
|
+
server.watcher.on("unlinkDir", async (dirPath) => {
|
|
3562
|
+
const relativePath = pathHelpers.relative(resourcesPath, dirPath);
|
|
3563
|
+
if (relativePath && !relativePath.includes("/")) {
|
|
3564
|
+
const widgetName = relativePath;
|
|
3565
|
+
const widgetDir = pathHelpers.join(tempDir, widgetName);
|
|
3566
|
+
try {
|
|
3567
|
+
await fs.access(widgetDir);
|
|
3568
|
+
await fs.rm(widgetDir, { recursive: true, force: true });
|
|
3569
|
+
console.log(
|
|
3570
|
+
`[WIDGETS] Cleaned up stale widget (directory removed): ${widgetName}`
|
|
3571
|
+
);
|
|
3572
|
+
} catch {
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
});
|
|
3411
3576
|
}
|
|
3412
3577
|
};
|
|
3413
3578
|
const nodeStubsPlugin = {
|
|
@@ -3520,6 +3685,7 @@ export default PostHog;
|
|
|
3520
3685
|
);
|
|
3521
3686
|
app.use(`${baseRoute}/*`, viteMiddleware);
|
|
3522
3687
|
setupPublicRoutes(app, false);
|
|
3688
|
+
setupFaviconRoute(app, serverConfig.favicon, false);
|
|
3523
3689
|
app.use(`${baseRoute}/*`, async (c) => {
|
|
3524
3690
|
const url = new URL(c.req.url);
|
|
3525
3691
|
const isAsset = url.pathname.match(
|
|
@@ -3729,6 +3895,7 @@ function setupWidgetRoutes(app, serverConfig) {
|
|
|
3729
3895
|
}
|
|
3730
3896
|
});
|
|
3731
3897
|
setupPublicRoutes(app, true);
|
|
3898
|
+
setupFaviconRoute(app, serverConfig.favicon, true);
|
|
3732
3899
|
}
|
|
3733
3900
|
__name(setupWidgetRoutes, "setupWidgetRoutes");
|
|
3734
3901
|
|
|
@@ -3863,18 +4030,30 @@ function uiResourceRegistration(server, definition) {
|
|
|
3863
4030
|
);
|
|
3864
4031
|
const uniqueToolMetadata = {
|
|
3865
4032
|
...toolMetadata,
|
|
3866
|
-
"openai/outputTemplate": uniqueUri
|
|
4033
|
+
"openai/outputTemplate": uniqueUri,
|
|
4034
|
+
"mcp-use/props": params
|
|
4035
|
+
// Pass params as widget props
|
|
3867
4036
|
};
|
|
4037
|
+
let toolOutputResult;
|
|
4038
|
+
if (definition.toolOutput) {
|
|
4039
|
+
toolOutputResult = typeof definition.toolOutput === "function" ? definition.toolOutput(params) : definition.toolOutput;
|
|
4040
|
+
} else {
|
|
4041
|
+
toolOutputResult = {
|
|
4042
|
+
content: [
|
|
4043
|
+
{
|
|
4044
|
+
type: "text",
|
|
4045
|
+
text: `Displaying ${displayName}`
|
|
4046
|
+
}
|
|
4047
|
+
]
|
|
4048
|
+
};
|
|
4049
|
+
}
|
|
4050
|
+
const content = toolOutputResult.content || [
|
|
4051
|
+
{ type: "text", text: `Displaying ${displayName}` }
|
|
4052
|
+
];
|
|
3868
4053
|
return {
|
|
3869
4054
|
_meta: uniqueToolMetadata,
|
|
3870
|
-
content
|
|
3871
|
-
|
|
3872
|
-
type: "text",
|
|
3873
|
-
text: `Displaying ${displayName}`
|
|
3874
|
-
}
|
|
3875
|
-
],
|
|
3876
|
-
// structuredContent will be injected as window.openai.toolOutput by Apps SDK
|
|
3877
|
-
structuredContent: params
|
|
4055
|
+
content,
|
|
4056
|
+
structuredContent: toolOutputResult.structuredContent
|
|
3878
4057
|
};
|
|
3879
4058
|
}
|
|
3880
4059
|
return {
|
|
@@ -3900,7 +4079,8 @@ async function mountWidgets(server, options) {
|
|
|
3900
4079
|
serverBaseUrl: server.serverBaseUrl || `http://${server.serverHost}:${server.serverPort || 3e3}`,
|
|
3901
4080
|
serverPort: server.serverPort || 3e3,
|
|
3902
4081
|
cspUrls: getCSPUrls(),
|
|
3903
|
-
buildId: server.buildId
|
|
4082
|
+
buildId: server.buildId,
|
|
4083
|
+
favicon: server.favicon
|
|
3904
4084
|
};
|
|
3905
4085
|
const registerWidget = /* @__PURE__ */ __name((widgetDef) => {
|
|
3906
4086
|
server.uiResource(widgetDef);
|
|
@@ -4395,27 +4575,7 @@ __name(registerPrompt, "registerPrompt");
|
|
|
4395
4575
|
|
|
4396
4576
|
// src/server/roots/roots-registration.ts
|
|
4397
4577
|
init_runtime();
|
|
4398
|
-
|
|
4399
|
-
// src/server/utils/jsonrpc-helpers.ts
|
|
4400
|
-
function createNotification(method, params) {
|
|
4401
|
-
return {
|
|
4402
|
-
jsonrpc: "2.0",
|
|
4403
|
-
method,
|
|
4404
|
-
...params && { params }
|
|
4405
|
-
};
|
|
4406
|
-
}
|
|
4407
|
-
__name(createNotification, "createNotification");
|
|
4408
|
-
function createRequest(id, method, params) {
|
|
4409
|
-
return {
|
|
4410
|
-
jsonrpc: "2.0",
|
|
4411
|
-
id,
|
|
4412
|
-
method,
|
|
4413
|
-
...params && { params }
|
|
4414
|
-
};
|
|
4415
|
-
}
|
|
4416
|
-
__name(createRequest, "createRequest");
|
|
4417
|
-
|
|
4418
|
-
// src/server/roots/roots-registration.ts
|
|
4578
|
+
init_jsonrpc_helpers();
|
|
4419
4579
|
function onRootsChanged(callback) {
|
|
4420
4580
|
this.onRootsChangedCallback = callback;
|
|
4421
4581
|
return this;
|
|
@@ -4577,41 +4737,8 @@ async function requestLogger(c, next) {
|
|
|
4577
4737
|
}
|
|
4578
4738
|
__name(requestLogger, "requestLogger");
|
|
4579
4739
|
|
|
4580
|
-
// src/server/sessions/notifications.ts
|
|
4581
|
-
async function sendNotificationToAll(sessions, method, params) {
|
|
4582
|
-
const notification = createNotification(method, params);
|
|
4583
|
-
for (const [sessionId, session] of sessions.entries()) {
|
|
4584
|
-
try {
|
|
4585
|
-
await session.transport.send(notification);
|
|
4586
|
-
} catch (error2) {
|
|
4587
|
-
console.warn(
|
|
4588
|
-
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
4589
|
-
error2
|
|
4590
|
-
);
|
|
4591
|
-
}
|
|
4592
|
-
}
|
|
4593
|
-
}
|
|
4594
|
-
__name(sendNotificationToAll, "sendNotificationToAll");
|
|
4595
|
-
async function sendNotificationToSession(sessions, sessionId, method, params) {
|
|
4596
|
-
const session = sessions.get(sessionId);
|
|
4597
|
-
if (!session) {
|
|
4598
|
-
return false;
|
|
4599
|
-
}
|
|
4600
|
-
const notification = createNotification(method, params);
|
|
4601
|
-
try {
|
|
4602
|
-
await session.transport.send(notification);
|
|
4603
|
-
return true;
|
|
4604
|
-
} catch (error2) {
|
|
4605
|
-
console.warn(
|
|
4606
|
-
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
4607
|
-
error2
|
|
4608
|
-
);
|
|
4609
|
-
return false;
|
|
4610
|
-
}
|
|
4611
|
-
}
|
|
4612
|
-
__name(sendNotificationToSession, "sendNotificationToSession");
|
|
4613
|
-
|
|
4614
4740
|
// src/server/notifications/notification-registration.ts
|
|
4741
|
+
init_notifications();
|
|
4615
4742
|
function getActiveSessions() {
|
|
4616
4743
|
return Array.from(this.sessions.keys());
|
|
4617
4744
|
}
|
|
@@ -4629,6 +4756,27 @@ async function sendNotificationToSession2(sessionId, method, params) {
|
|
|
4629
4756
|
);
|
|
4630
4757
|
}
|
|
4631
4758
|
__name(sendNotificationToSession2, "sendNotificationToSession");
|
|
4759
|
+
async function sendToolsListChanged() {
|
|
4760
|
+
await sendNotificationToAll(
|
|
4761
|
+
this.sessions,
|
|
4762
|
+
"notifications/tools/list_changed"
|
|
4763
|
+
);
|
|
4764
|
+
}
|
|
4765
|
+
__name(sendToolsListChanged, "sendToolsListChanged");
|
|
4766
|
+
async function sendResourcesListChanged() {
|
|
4767
|
+
await sendNotificationToAll(
|
|
4768
|
+
this.sessions,
|
|
4769
|
+
"notifications/resources/list_changed"
|
|
4770
|
+
);
|
|
4771
|
+
}
|
|
4772
|
+
__name(sendResourcesListChanged, "sendResourcesListChanged");
|
|
4773
|
+
async function sendPromptsListChanged() {
|
|
4774
|
+
await sendNotificationToAll(
|
|
4775
|
+
this.sessions,
|
|
4776
|
+
"notifications/prompts/list_changed"
|
|
4777
|
+
);
|
|
4778
|
+
}
|
|
4779
|
+
__name(sendPromptsListChanged, "sendPromptsListChanged");
|
|
4632
4780
|
|
|
4633
4781
|
// src/server/mcp-server.ts
|
|
4634
4782
|
init_tool_execution_helpers();
|
|
@@ -4673,81 +4821,1047 @@ function startIdleCleanup(sessions, idleTimeoutMs, transports, mcpServerInstance
|
|
|
4673
4821
|
}
|
|
4674
4822
|
__name(startIdleCleanup, "startIdleCleanup");
|
|
4675
4823
|
|
|
4676
|
-
// src/server/
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
let idleCleanupInterval;
|
|
4684
|
-
if (!config.stateless && idleTimeoutMs > 0) {
|
|
4685
|
-
idleCleanupInterval = startIdleCleanup(
|
|
4686
|
-
sessions,
|
|
4687
|
-
idleTimeoutMs,
|
|
4688
|
-
transports,
|
|
4689
|
-
mcpServerInstance
|
|
4690
|
-
);
|
|
4824
|
+
// src/server/sessions/index.ts
|
|
4825
|
+
init_notifications();
|
|
4826
|
+
|
|
4827
|
+
// src/server/sessions/stores/memory.ts
|
|
4828
|
+
var InMemorySessionStore = class {
|
|
4829
|
+
static {
|
|
4830
|
+
__name(this, "InMemorySessionStore");
|
|
4691
4831
|
}
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4832
|
+
/**
|
|
4833
|
+
* Internal map storing session metadata
|
|
4834
|
+
* Key: sessionId, Value: SessionMetadata
|
|
4835
|
+
*/
|
|
4836
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4837
|
+
/**
|
|
4838
|
+
* Retrieve session metadata by ID
|
|
4839
|
+
*/
|
|
4840
|
+
async get(sessionId) {
|
|
4841
|
+
const data = this.sessions.get(sessionId);
|
|
4842
|
+
return data ?? null;
|
|
4843
|
+
}
|
|
4844
|
+
/**
|
|
4845
|
+
* Store or update session metadata
|
|
4846
|
+
*/
|
|
4847
|
+
async set(sessionId, data) {
|
|
4848
|
+
this.sessions.set(sessionId, data);
|
|
4849
|
+
}
|
|
4850
|
+
/**
|
|
4851
|
+
* Delete session metadata
|
|
4852
|
+
*/
|
|
4853
|
+
async delete(sessionId) {
|
|
4854
|
+
this.sessions.delete(sessionId);
|
|
4855
|
+
}
|
|
4856
|
+
/**
|
|
4857
|
+
* Check if session exists
|
|
4858
|
+
*/
|
|
4859
|
+
async has(sessionId) {
|
|
4860
|
+
return this.sessions.has(sessionId);
|
|
4861
|
+
}
|
|
4862
|
+
/**
|
|
4863
|
+
* List all session IDs
|
|
4864
|
+
*/
|
|
4865
|
+
async keys() {
|
|
4866
|
+
return Array.from(this.sessions.keys());
|
|
4867
|
+
}
|
|
4868
|
+
/**
|
|
4869
|
+
* Store session metadata with TTL (time-to-live)
|
|
4870
|
+
*
|
|
4871
|
+
* Note: In-memory implementation uses setTimeout for TTL.
|
|
4872
|
+
* For production TTL support, use Redis or another store with native TTL.
|
|
4873
|
+
*/
|
|
4874
|
+
async setWithTTL(sessionId, data, ttlMs) {
|
|
4875
|
+
this.sessions.set(sessionId, data);
|
|
4876
|
+
setTimeout(() => {
|
|
4877
|
+
this.sessions.delete(sessionId);
|
|
4878
|
+
console.log(`[MCP] Session ${sessionId} expired after ${ttlMs}ms`);
|
|
4879
|
+
}, ttlMs);
|
|
4880
|
+
}
|
|
4881
|
+
/**
|
|
4882
|
+
* Get the number of active sessions
|
|
4883
|
+
* Useful for monitoring and debugging
|
|
4884
|
+
*/
|
|
4885
|
+
get size() {
|
|
4886
|
+
return this.sessions.size;
|
|
4887
|
+
}
|
|
4888
|
+
/**
|
|
4889
|
+
* Clear all sessions
|
|
4890
|
+
* Useful for testing and manual cleanup
|
|
4891
|
+
*/
|
|
4892
|
+
async clear() {
|
|
4893
|
+
this.sessions.clear();
|
|
4894
|
+
}
|
|
4895
|
+
};
|
|
4896
|
+
|
|
4897
|
+
// src/server/sessions/stores/redis.ts
|
|
4898
|
+
var RedisSessionStore = class {
|
|
4899
|
+
static {
|
|
4900
|
+
__name(this, "RedisSessionStore");
|
|
4901
|
+
}
|
|
4902
|
+
client;
|
|
4903
|
+
prefix;
|
|
4904
|
+
defaultTTL;
|
|
4905
|
+
constructor(config) {
|
|
4906
|
+
this.client = config.client;
|
|
4907
|
+
this.prefix = config.prefix ?? "mcp:session:";
|
|
4908
|
+
this.defaultTTL = config.defaultTTL ?? 3600;
|
|
4909
|
+
}
|
|
4910
|
+
/**
|
|
4911
|
+
* Get full Redis key for a session ID
|
|
4912
|
+
*/
|
|
4913
|
+
getKey(sessionId) {
|
|
4914
|
+
return `${this.prefix}${sessionId}`;
|
|
4915
|
+
}
|
|
4916
|
+
/**
|
|
4917
|
+
* Retrieve session metadata by ID
|
|
4918
|
+
*/
|
|
4919
|
+
async get(sessionId) {
|
|
4920
|
+
try {
|
|
4921
|
+
const key = this.getKey(sessionId);
|
|
4922
|
+
const data = await this.client.get(key);
|
|
4923
|
+
if (!data) {
|
|
4924
|
+
return null;
|
|
4725
4925
|
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4926
|
+
return JSON.parse(data);
|
|
4927
|
+
} catch (error2) {
|
|
4928
|
+
console.error(
|
|
4929
|
+
`[RedisSessionStore] Error getting session ${sessionId}:`,
|
|
4930
|
+
error2
|
|
4931
|
+
);
|
|
4932
|
+
return null;
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
/**
|
|
4936
|
+
* Store or update session metadata
|
|
4937
|
+
*/
|
|
4938
|
+
async set(sessionId, data) {
|
|
4939
|
+
try {
|
|
4940
|
+
const key = this.getKey(sessionId);
|
|
4941
|
+
const value = JSON.stringify(data);
|
|
4942
|
+
if (this.client.setEx) {
|
|
4943
|
+
await this.client.setEx(key, this.defaultTTL, value);
|
|
4944
|
+
} else if (this.client.setex) {
|
|
4945
|
+
await this.client.setex(key, this.defaultTTL, value);
|
|
4946
|
+
} else {
|
|
4947
|
+
await this.client.set(key, value, { EX: this.defaultTTL });
|
|
4948
|
+
}
|
|
4949
|
+
} catch (error2) {
|
|
4950
|
+
console.error(
|
|
4951
|
+
`[RedisSessionStore] Error setting session ${sessionId}:`,
|
|
4952
|
+
error2
|
|
4953
|
+
);
|
|
4954
|
+
throw error2;
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
/**
|
|
4958
|
+
* Delete a session
|
|
4959
|
+
*/
|
|
4960
|
+
async delete(sessionId) {
|
|
4961
|
+
try {
|
|
4962
|
+
const key = this.getKey(sessionId);
|
|
4963
|
+
await this.client.del(key);
|
|
4964
|
+
} catch (error2) {
|
|
4965
|
+
console.error(
|
|
4966
|
+
`[RedisSessionStore] Error deleting session ${sessionId}:`,
|
|
4967
|
+
error2
|
|
4968
|
+
);
|
|
4969
|
+
throw error2;
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
/**
|
|
4973
|
+
* Check if a session exists
|
|
4974
|
+
*/
|
|
4975
|
+
async has(sessionId) {
|
|
4976
|
+
try {
|
|
4977
|
+
const key = this.getKey(sessionId);
|
|
4978
|
+
const exists = await this.client.exists(key);
|
|
4979
|
+
return exists === 1;
|
|
4980
|
+
} catch (error2) {
|
|
4981
|
+
console.error(
|
|
4982
|
+
`[RedisSessionStore] Error checking session ${sessionId}:`,
|
|
4983
|
+
error2
|
|
4984
|
+
);
|
|
4985
|
+
return false;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
/**
|
|
4989
|
+
* List all session IDs
|
|
4990
|
+
*
|
|
4991
|
+
* WARNING: Uses KEYS command which blocks Redis. For production systems with
|
|
4992
|
+
* many sessions, consider using SCAN instead or maintaining a separate SET of
|
|
4993
|
+
* active session IDs.
|
|
4994
|
+
*/
|
|
4995
|
+
async keys() {
|
|
4996
|
+
try {
|
|
4997
|
+
const pattern = `${this.prefix}*`;
|
|
4998
|
+
const keys = await this.client.keys(pattern);
|
|
4999
|
+
return keys.map((key) => key.substring(this.prefix.length));
|
|
5000
|
+
} catch (error2) {
|
|
5001
|
+
console.error("[RedisSessionStore] Error listing session keys:", error2);
|
|
5002
|
+
return [];
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
/**
|
|
5006
|
+
* Store session metadata with custom TTL (time-to-live)
|
|
5007
|
+
*/
|
|
5008
|
+
async setWithTTL(sessionId, data, ttlMs) {
|
|
5009
|
+
try {
|
|
5010
|
+
const key = this.getKey(sessionId);
|
|
5011
|
+
const value = JSON.stringify(data);
|
|
5012
|
+
const ttlSeconds = Math.ceil(ttlMs / 1e3);
|
|
5013
|
+
if (this.client.setEx) {
|
|
5014
|
+
await this.client.setEx(key, ttlSeconds, value);
|
|
5015
|
+
} else if (this.client.setex) {
|
|
5016
|
+
await this.client.setex(key, ttlSeconds, value);
|
|
5017
|
+
} else {
|
|
5018
|
+
await this.client.set(key, value, { EX: ttlSeconds });
|
|
5019
|
+
}
|
|
5020
|
+
} catch (error2) {
|
|
5021
|
+
console.error(
|
|
5022
|
+
`[RedisSessionStore] Error setting session ${sessionId} with TTL:`,
|
|
5023
|
+
error2
|
|
5024
|
+
);
|
|
5025
|
+
throw error2;
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
/**
|
|
5029
|
+
* Close Redis connection
|
|
5030
|
+
* Should be called when shutting down the server
|
|
5031
|
+
*/
|
|
5032
|
+
async close() {
|
|
5033
|
+
try {
|
|
5034
|
+
await this.client.quit();
|
|
5035
|
+
} catch (error2) {
|
|
5036
|
+
console.error(
|
|
5037
|
+
"[RedisSessionStore] Error closing Redis connection:",
|
|
5038
|
+
error2
|
|
5039
|
+
);
|
|
5040
|
+
throw error2;
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
/**
|
|
5044
|
+
* Clear all sessions (useful for testing)
|
|
5045
|
+
* WARNING: This will delete all sessions with the configured prefix
|
|
5046
|
+
*
|
|
5047
|
+
* NOTE: Uses KEYS command which blocks Redis. This is acceptable for testing
|
|
5048
|
+
* but should be avoided in production with large datasets.
|
|
5049
|
+
*/
|
|
5050
|
+
async clear() {
|
|
5051
|
+
try {
|
|
5052
|
+
const pattern = `${this.prefix}*`;
|
|
5053
|
+
const keys = await this.client.keys(pattern);
|
|
5054
|
+
if (keys.length > 0) {
|
|
5055
|
+
await this.client.del(keys);
|
|
5056
|
+
}
|
|
5057
|
+
} catch (error2) {
|
|
5058
|
+
console.error("[RedisSessionStore] Error clearing sessions:", error2);
|
|
5059
|
+
throw error2;
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
};
|
|
5063
|
+
|
|
5064
|
+
// src/server/sessions/stores/filesystem.ts
|
|
5065
|
+
var import_promises = require("fs/promises");
|
|
5066
|
+
var import_node_path = require("path");
|
|
5067
|
+
var import_node_fs = require("fs");
|
|
5068
|
+
var FileSystemSessionStore = class {
|
|
5069
|
+
static {
|
|
5070
|
+
__name(this, "FileSystemSessionStore");
|
|
5071
|
+
}
|
|
5072
|
+
sessions = /* @__PURE__ */ new Map();
|
|
5073
|
+
filePath;
|
|
5074
|
+
debounceMs;
|
|
5075
|
+
maxAgeMs;
|
|
5076
|
+
saveTimer = null;
|
|
5077
|
+
saving = false;
|
|
5078
|
+
pendingSave = false;
|
|
5079
|
+
constructor(config = {}) {
|
|
5080
|
+
this.filePath = config.path ?? (0, import_node_path.join)(process.cwd(), ".mcp-use", "sessions.json");
|
|
5081
|
+
this.debounceMs = config.debounceMs ?? 100;
|
|
5082
|
+
this.maxAgeMs = config.maxAgeMs ?? 24 * 60 * 60 * 1e3;
|
|
5083
|
+
this.loadSessionsSync();
|
|
5084
|
+
}
|
|
5085
|
+
/**
|
|
5086
|
+
* Load sessions from file synchronously during construction
|
|
5087
|
+
* This ensures sessions are available immediately when the server starts
|
|
5088
|
+
*/
|
|
5089
|
+
loadSessionsSync() {
|
|
5090
|
+
try {
|
|
5091
|
+
if (!(0, import_node_fs.existsSync)(this.filePath)) {
|
|
5092
|
+
console.log(
|
|
5093
|
+
`[FileSystemSessionStore] No session file found at ${this.filePath}, starting fresh`
|
|
5094
|
+
);
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
const data = (0, import_node_fs.readFileSync)(this.filePath, "utf-8");
|
|
5098
|
+
const parsed = JSON.parse(data);
|
|
5099
|
+
const now = Date.now();
|
|
5100
|
+
let loadedCount = 0;
|
|
5101
|
+
let expiredCount = 0;
|
|
5102
|
+
for (const [sessionId, metadata] of Object.entries(parsed)) {
|
|
5103
|
+
const sessionMetadata = metadata;
|
|
5104
|
+
const age = now - sessionMetadata.lastAccessedAt;
|
|
5105
|
+
if (age > this.maxAgeMs) {
|
|
5106
|
+
expiredCount++;
|
|
5107
|
+
continue;
|
|
5108
|
+
}
|
|
5109
|
+
this.sessions.set(sessionId, sessionMetadata);
|
|
5110
|
+
loadedCount++;
|
|
5111
|
+
}
|
|
5112
|
+
console.log(
|
|
5113
|
+
`[FileSystemSessionStore] Loaded ${loadedCount} session(s) from ${this.filePath}` + (expiredCount > 0 ? ` (cleaned up ${expiredCount} expired)` : "")
|
|
5114
|
+
);
|
|
5115
|
+
} catch (error2) {
|
|
5116
|
+
if (error2.code === "ENOENT") {
|
|
5117
|
+
console.log(`[FileSystemSessionStore] No existing sessions file`);
|
|
5118
|
+
} else if (error2 instanceof SyntaxError) {
|
|
5119
|
+
console.warn(
|
|
5120
|
+
`[FileSystemSessionStore] Corrupted session file, starting fresh:`,
|
|
5121
|
+
error2.message
|
|
5122
|
+
);
|
|
5123
|
+
} else {
|
|
5124
|
+
console.warn(
|
|
5125
|
+
`[FileSystemSessionStore] Error loading sessions, starting fresh:`,
|
|
5126
|
+
error2.message
|
|
5127
|
+
);
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
/**
|
|
5132
|
+
* Retrieve session metadata by ID
|
|
5133
|
+
*/
|
|
5134
|
+
async get(sessionId) {
|
|
5135
|
+
const data = this.sessions.get(sessionId);
|
|
5136
|
+
return data ?? null;
|
|
5137
|
+
}
|
|
5138
|
+
/**
|
|
5139
|
+
* Store or update session metadata
|
|
5140
|
+
* Uses debouncing to batch rapid consecutive writes
|
|
5141
|
+
*/
|
|
5142
|
+
async set(sessionId, data) {
|
|
5143
|
+
this.sessions.set(sessionId, data);
|
|
5144
|
+
await this.scheduleSave();
|
|
5145
|
+
}
|
|
5146
|
+
/**
|
|
5147
|
+
* Delete session metadata
|
|
5148
|
+
*/
|
|
5149
|
+
async delete(sessionId) {
|
|
5150
|
+
this.sessions.delete(sessionId);
|
|
5151
|
+
await this.scheduleSave();
|
|
5152
|
+
}
|
|
5153
|
+
/**
|
|
5154
|
+
* Check if session exists
|
|
5155
|
+
*/
|
|
5156
|
+
async has(sessionId) {
|
|
5157
|
+
return this.sessions.has(sessionId);
|
|
5158
|
+
}
|
|
5159
|
+
/**
|
|
5160
|
+
* List all session IDs
|
|
5161
|
+
*/
|
|
5162
|
+
async keys() {
|
|
5163
|
+
return Array.from(this.sessions.keys());
|
|
5164
|
+
}
|
|
5165
|
+
/**
|
|
5166
|
+
* Store session metadata with TTL
|
|
5167
|
+
* Note: TTL is enforced on load, not with timers (simple implementation)
|
|
5168
|
+
*/
|
|
5169
|
+
async setWithTTL(sessionId, data, ttlMs) {
|
|
5170
|
+
const metadataWithExpiry = {
|
|
5171
|
+
...data,
|
|
5172
|
+
lastAccessedAt: Date.now()
|
|
5173
|
+
};
|
|
5174
|
+
this.sessions.set(sessionId, metadataWithExpiry);
|
|
5175
|
+
await this.scheduleSave();
|
|
5176
|
+
setTimeout(() => {
|
|
5177
|
+
this.sessions.delete(sessionId);
|
|
5178
|
+
this.scheduleSave();
|
|
5179
|
+
}, ttlMs);
|
|
5180
|
+
}
|
|
5181
|
+
/**
|
|
5182
|
+
* Get the number of active sessions
|
|
5183
|
+
*/
|
|
5184
|
+
get size() {
|
|
5185
|
+
return this.sessions.size;
|
|
5186
|
+
}
|
|
5187
|
+
/**
|
|
5188
|
+
* Clear all sessions
|
|
5189
|
+
*/
|
|
5190
|
+
async clear() {
|
|
5191
|
+
this.sessions.clear();
|
|
5192
|
+
await this.scheduleSave();
|
|
5193
|
+
}
|
|
5194
|
+
/**
|
|
5195
|
+
* Schedule a save operation with debouncing
|
|
5196
|
+
* Prevents excessive disk I/O from rapid consecutive writes
|
|
5197
|
+
*/
|
|
5198
|
+
async scheduleSave() {
|
|
5199
|
+
if (this.saving) {
|
|
5200
|
+
this.pendingSave = true;
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
5203
|
+
if (this.saveTimer) {
|
|
5204
|
+
clearTimeout(this.saveTimer);
|
|
5205
|
+
}
|
|
5206
|
+
this.saveTimer = setTimeout(() => {
|
|
5207
|
+
this.performSave();
|
|
5208
|
+
}, this.debounceMs);
|
|
5209
|
+
}
|
|
5210
|
+
/**
|
|
5211
|
+
* Perform the actual save operation with atomic writes
|
|
5212
|
+
* Uses write-to-temp-then-rename pattern to prevent corruption
|
|
5213
|
+
*/
|
|
5214
|
+
async performSave() {
|
|
5215
|
+
this.saveTimer = null;
|
|
5216
|
+
this.saving = true;
|
|
5217
|
+
this.pendingSave = false;
|
|
5218
|
+
try {
|
|
5219
|
+
const dir = (0, import_node_path.dirname)(this.filePath);
|
|
5220
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
5221
|
+
const data = {};
|
|
5222
|
+
for (const [sessionId, metadata] of Array.from(this.sessions.entries())) {
|
|
5223
|
+
data[sessionId] = metadata;
|
|
5224
|
+
}
|
|
5225
|
+
const tempPath = `${this.filePath}.tmp`;
|
|
5226
|
+
await (0, import_promises.writeFile)(tempPath, JSON.stringify(data, null, 2), "utf-8");
|
|
5227
|
+
await (0, import_promises.rename)(tempPath, this.filePath);
|
|
5228
|
+
console.debug(
|
|
5229
|
+
`[FileSystemSessionStore] Saved ${this.sessions.size} session(s) to ${this.filePath}`
|
|
5230
|
+
);
|
|
5231
|
+
} catch (error2) {
|
|
5232
|
+
console.error(
|
|
5233
|
+
`[FileSystemSessionStore] Error saving sessions:`,
|
|
5234
|
+
error2.message
|
|
5235
|
+
);
|
|
5236
|
+
try {
|
|
5237
|
+
const tempPath = `${this.filePath}.tmp`;
|
|
5238
|
+
if ((0, import_node_fs.existsSync)(tempPath)) {
|
|
5239
|
+
await (0, import_promises.unlink)(tempPath);
|
|
5240
|
+
}
|
|
5241
|
+
} catch {
|
|
5242
|
+
}
|
|
5243
|
+
} finally {
|
|
5244
|
+
this.saving = false;
|
|
5245
|
+
if (this.pendingSave) {
|
|
5246
|
+
await this.scheduleSave();
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
/**
|
|
5251
|
+
* Force an immediate save (bypasses debouncing)
|
|
5252
|
+
* Useful for ensuring persistence before process exit
|
|
5253
|
+
*/
|
|
5254
|
+
async flush() {
|
|
5255
|
+
if (this.saveTimer) {
|
|
5256
|
+
clearTimeout(this.saveTimer);
|
|
5257
|
+
this.saveTimer = null;
|
|
5258
|
+
}
|
|
5259
|
+
await this.performSave();
|
|
5260
|
+
}
|
|
5261
|
+
};
|
|
5262
|
+
|
|
5263
|
+
// src/server/sessions/streams/memory.ts
|
|
5264
|
+
var InMemoryStreamManager = class {
|
|
5265
|
+
static {
|
|
5266
|
+
__name(this, "InMemoryStreamManager");
|
|
5267
|
+
}
|
|
5268
|
+
/**
|
|
5269
|
+
* Map of active SSE stream controllers
|
|
5270
|
+
* Key: sessionId, Value: ReadableStreamDefaultController
|
|
5271
|
+
*/
|
|
5272
|
+
streams = /* @__PURE__ */ new Map();
|
|
5273
|
+
/**
|
|
5274
|
+
* Text encoder for converting strings to Uint8Array
|
|
5275
|
+
*/
|
|
5276
|
+
textEncoder = new TextEncoder();
|
|
5277
|
+
/**
|
|
5278
|
+
* Register an active SSE stream controller
|
|
5279
|
+
*/
|
|
5280
|
+
async create(sessionId, controller) {
|
|
5281
|
+
this.streams.set(sessionId, controller);
|
|
5282
|
+
}
|
|
5283
|
+
/**
|
|
5284
|
+
* Send data to active SSE streams
|
|
5285
|
+
*
|
|
5286
|
+
* Directly enqueues data to in-memory controllers.
|
|
5287
|
+
* For distributed deployments, use RedisStreamManager instead.
|
|
5288
|
+
*/
|
|
5289
|
+
async send(sessionIds, data) {
|
|
5290
|
+
const encoded = this.textEncoder.encode(data);
|
|
5291
|
+
if (!sessionIds) {
|
|
5292
|
+
for (const [_id, controller] of this.streams.entries()) {
|
|
5293
|
+
try {
|
|
5294
|
+
controller.enqueue(encoded);
|
|
5295
|
+
} catch (error2) {
|
|
5296
|
+
console.warn(
|
|
5297
|
+
`[InMemoryStreamManager] Failed to send to session ${_id}:`,
|
|
5298
|
+
error2
|
|
5299
|
+
);
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
} else {
|
|
5303
|
+
for (const sessionId of sessionIds) {
|
|
5304
|
+
const controller = this.streams.get(sessionId);
|
|
5305
|
+
if (controller) {
|
|
5306
|
+
try {
|
|
5307
|
+
controller.enqueue(encoded);
|
|
5308
|
+
} catch (error2) {
|
|
5309
|
+
console.warn(
|
|
5310
|
+
`[InMemoryStreamManager] Failed to send to session ${sessionId}:`,
|
|
5311
|
+
error2
|
|
5312
|
+
);
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
/**
|
|
5319
|
+
* Remove an active SSE stream
|
|
5320
|
+
*/
|
|
5321
|
+
async delete(sessionId) {
|
|
5322
|
+
const controller = this.streams.get(sessionId);
|
|
5323
|
+
if (controller) {
|
|
5324
|
+
try {
|
|
5325
|
+
controller.close();
|
|
5326
|
+
} catch (error2) {
|
|
5327
|
+
console.debug(
|
|
5328
|
+
`[InMemoryStreamManager] Controller already closed for session ${sessionId}`
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
5331
|
+
this.streams.delete(sessionId);
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
/**
|
|
5335
|
+
* Check if an active stream exists
|
|
5336
|
+
*/
|
|
5337
|
+
async has(sessionId) {
|
|
5338
|
+
return this.streams.has(sessionId);
|
|
5339
|
+
}
|
|
5340
|
+
/**
|
|
5341
|
+
* Close all active streams
|
|
5342
|
+
*/
|
|
5343
|
+
async close() {
|
|
5344
|
+
for (const [sessionId, controller] of this.streams.entries()) {
|
|
5345
|
+
try {
|
|
5346
|
+
controller.close();
|
|
5347
|
+
} catch (error2) {
|
|
5348
|
+
console.debug(
|
|
5349
|
+
`[InMemoryStreamManager] Error closing stream for ${sessionId}:`,
|
|
5350
|
+
error2
|
|
5351
|
+
);
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
this.streams.clear();
|
|
5355
|
+
}
|
|
5356
|
+
/**
|
|
5357
|
+
* Get the number of active streams
|
|
5358
|
+
* Useful for monitoring
|
|
5359
|
+
*/
|
|
5360
|
+
get size() {
|
|
5361
|
+
return this.streams.size;
|
|
5362
|
+
}
|
|
5363
|
+
};
|
|
5364
|
+
|
|
5365
|
+
// src/server/sessions/streams/redis.ts
|
|
5366
|
+
var RedisStreamManager = class {
|
|
5367
|
+
static {
|
|
5368
|
+
__name(this, "RedisStreamManager");
|
|
5369
|
+
}
|
|
5370
|
+
pubSubClient;
|
|
5371
|
+
client;
|
|
5372
|
+
prefix;
|
|
5373
|
+
heartbeatInterval;
|
|
5374
|
+
textEncoder = new TextEncoder();
|
|
5375
|
+
/**
|
|
5376
|
+
* Map of local controllers (only on this server instance)
|
|
5377
|
+
* Key: sessionId, Value: controller
|
|
5378
|
+
*/
|
|
5379
|
+
localControllers = /* @__PURE__ */ new Map();
|
|
5380
|
+
/**
|
|
5381
|
+
* Map of heartbeat intervals for keeping sessions alive
|
|
5382
|
+
* Key: sessionId, Value: interval timer
|
|
5383
|
+
*/
|
|
5384
|
+
heartbeats = /* @__PURE__ */ new Map();
|
|
5385
|
+
constructor(config) {
|
|
5386
|
+
this.pubSubClient = config.pubSubClient;
|
|
5387
|
+
this.client = config.client;
|
|
5388
|
+
this.prefix = config.prefix ?? "mcp:stream:";
|
|
5389
|
+
this.heartbeatInterval = config.heartbeatInterval ?? 10;
|
|
5390
|
+
}
|
|
5391
|
+
/**
|
|
5392
|
+
* Get the Redis channel name for a session
|
|
5393
|
+
*/
|
|
5394
|
+
getChannel(sessionId) {
|
|
5395
|
+
return `${this.prefix}${sessionId}`;
|
|
5396
|
+
}
|
|
5397
|
+
/**
|
|
5398
|
+
* Get the Redis key for tracking active sessions
|
|
5399
|
+
*/
|
|
5400
|
+
getAvailableKey(sessionId) {
|
|
5401
|
+
return `available:${this.prefix}${sessionId}`;
|
|
5402
|
+
}
|
|
5403
|
+
/**
|
|
5404
|
+
* Get the Redis key for the active sessions SET
|
|
5405
|
+
*/
|
|
5406
|
+
getActiveSessionsKey() {
|
|
5407
|
+
return `${this.prefix}active`;
|
|
5408
|
+
}
|
|
5409
|
+
/**
|
|
5410
|
+
* Register an active SSE stream and subscribe to Redis channel
|
|
5411
|
+
*/
|
|
5412
|
+
async create(sessionId, controller) {
|
|
5413
|
+
try {
|
|
5414
|
+
this.localControllers.set(sessionId, controller);
|
|
5415
|
+
const availableKey = this.getAvailableKey(sessionId);
|
|
5416
|
+
await this.client.set(availableKey, "active");
|
|
5417
|
+
if (this.client.expire) {
|
|
5418
|
+
await this.client.expire(availableKey, this.heartbeatInterval * 2);
|
|
5419
|
+
}
|
|
5420
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
5421
|
+
if (this.client.sAdd) {
|
|
5422
|
+
await this.client.sAdd(activeSessionsKey, sessionId);
|
|
5423
|
+
if (this.client.expire) {
|
|
5424
|
+
await this.client.expire(
|
|
5425
|
+
activeSessionsKey,
|
|
5426
|
+
this.heartbeatInterval * 2
|
|
5427
|
+
);
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
const heartbeat = setInterval(async () => {
|
|
5431
|
+
try {
|
|
5432
|
+
if (this.client.expire) {
|
|
5433
|
+
await this.client.expire(availableKey, this.heartbeatInterval * 2);
|
|
5434
|
+
const activeSessionsKey2 = this.getActiveSessionsKey();
|
|
5435
|
+
await this.client.expire(
|
|
5436
|
+
activeSessionsKey2,
|
|
5437
|
+
this.heartbeatInterval * 2
|
|
5438
|
+
);
|
|
5439
|
+
}
|
|
5440
|
+
} catch (error2) {
|
|
5441
|
+
console.warn(
|
|
5442
|
+
`[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
|
|
5443
|
+
error2
|
|
5444
|
+
);
|
|
5445
|
+
}
|
|
5446
|
+
}, this.heartbeatInterval * 1e3);
|
|
5447
|
+
this.heartbeats.set(sessionId, heartbeat);
|
|
5448
|
+
const channel = this.getChannel(sessionId);
|
|
5449
|
+
if (!this.pubSubClient.subscribe) {
|
|
5450
|
+
throw new Error(
|
|
5451
|
+
"[RedisStreamManager] Redis client does not support subscribe method"
|
|
5452
|
+
);
|
|
5453
|
+
}
|
|
5454
|
+
await this.pubSubClient.subscribe(channel, (message) => {
|
|
5455
|
+
const localController = this.localControllers.get(sessionId);
|
|
5456
|
+
if (localController) {
|
|
5457
|
+
try {
|
|
5458
|
+
localController.enqueue(this.textEncoder.encode(message));
|
|
5459
|
+
} catch (error2) {
|
|
5460
|
+
console.warn(
|
|
5461
|
+
`[RedisStreamManager] Failed to enqueue message for ${sessionId}:`,
|
|
5462
|
+
error2
|
|
5463
|
+
);
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
});
|
|
5467
|
+
const deleteChannel = `delete:${this.getChannel(sessionId)}`;
|
|
5468
|
+
await this.pubSubClient.subscribe(deleteChannel, async () => {
|
|
5469
|
+
await this.delete(sessionId);
|
|
5470
|
+
});
|
|
5471
|
+
console.log(
|
|
5472
|
+
`[RedisStreamManager] Created stream for session ${sessionId}`
|
|
5473
|
+
);
|
|
5474
|
+
} catch (error2) {
|
|
5475
|
+
console.error(
|
|
5476
|
+
`[RedisStreamManager] Error creating stream for ${sessionId}:`,
|
|
5477
|
+
error2
|
|
5478
|
+
);
|
|
5479
|
+
throw error2;
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
/**
|
|
5483
|
+
* Send data to sessions via Redis Pub/Sub
|
|
5484
|
+
*
|
|
5485
|
+
* This works across distributed servers - any server with an active
|
|
5486
|
+
* SSE connection for the target session will receive and forward the message.
|
|
5487
|
+
*
|
|
5488
|
+
* Note: Uses the regular client (not pubSubClient) for publishing.
|
|
5489
|
+
* In node-redis v5+, clients in subscriber mode cannot publish.
|
|
5490
|
+
*/
|
|
5491
|
+
async send(sessionIds, data) {
|
|
5492
|
+
try {
|
|
5493
|
+
if (!sessionIds) {
|
|
5494
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
5495
|
+
if (this.client.sMembers) {
|
|
5496
|
+
const sessionIds2 = await this.client.sMembers(activeSessionsKey);
|
|
5497
|
+
for (const sessionId of sessionIds2) {
|
|
5498
|
+
const channel = this.getChannel(sessionId);
|
|
5499
|
+
if (!this.client.publish) {
|
|
5500
|
+
throw new Error(
|
|
5501
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
5502
|
+
);
|
|
5503
|
+
}
|
|
5504
|
+
await this.client.publish(channel, data);
|
|
5505
|
+
}
|
|
5506
|
+
} else {
|
|
5507
|
+
const pattern = `available:${this.prefix}*`;
|
|
5508
|
+
const keys = await this.client.keys(pattern);
|
|
5509
|
+
for (const key of keys) {
|
|
5510
|
+
const sessionId = key.replace(`available:${this.prefix}`, "");
|
|
5511
|
+
const channel = this.getChannel(sessionId);
|
|
5512
|
+
if (!this.client.publish) {
|
|
5513
|
+
throw new Error(
|
|
5514
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
5515
|
+
);
|
|
5516
|
+
}
|
|
5517
|
+
await this.client.publish(channel, data);
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
} else {
|
|
5521
|
+
for (const sessionId of sessionIds) {
|
|
5522
|
+
const channel = this.getChannel(sessionId);
|
|
5523
|
+
if (!this.client.publish) {
|
|
5524
|
+
throw new Error(
|
|
5525
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
await this.client.publish(channel, data);
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
} catch (error2) {
|
|
5532
|
+
console.error(`[RedisStreamManager] Error sending to sessions:`, error2);
|
|
5533
|
+
throw error2;
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
/**
|
|
5537
|
+
* Remove an active SSE stream
|
|
5538
|
+
*/
|
|
5539
|
+
async delete(sessionId) {
|
|
5540
|
+
try {
|
|
5541
|
+
const heartbeat = this.heartbeats.get(sessionId);
|
|
5542
|
+
if (heartbeat) {
|
|
5543
|
+
clearInterval(heartbeat);
|
|
5544
|
+
this.heartbeats.delete(sessionId);
|
|
5545
|
+
}
|
|
5546
|
+
const channel = this.getChannel(sessionId);
|
|
5547
|
+
const deleteChannel = `delete:${channel}`;
|
|
5548
|
+
if (!this.pubSubClient.unsubscribe) {
|
|
5549
|
+
throw new Error(
|
|
5550
|
+
"[RedisStreamManager] Redis client does not support unsubscribe method"
|
|
5551
|
+
);
|
|
5552
|
+
}
|
|
5553
|
+
await this.pubSubClient.unsubscribe(channel);
|
|
5554
|
+
await this.pubSubClient.unsubscribe(deleteChannel);
|
|
5555
|
+
if (!this.client.publish) {
|
|
5556
|
+
throw new Error(
|
|
5557
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
5558
|
+
);
|
|
5559
|
+
}
|
|
5560
|
+
await this.client.publish(deleteChannel, "");
|
|
5561
|
+
await this.client.del(this.getAvailableKey(sessionId));
|
|
5562
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
5563
|
+
if (this.client.sRem) {
|
|
5564
|
+
await this.client.sRem(activeSessionsKey, sessionId);
|
|
5565
|
+
}
|
|
5566
|
+
const controller = this.localControllers.get(sessionId);
|
|
5567
|
+
if (controller) {
|
|
5568
|
+
try {
|
|
5569
|
+
controller.close();
|
|
5570
|
+
} catch (error2) {
|
|
5571
|
+
console.debug(
|
|
5572
|
+
`[RedisStreamManager] Controller already closed for ${sessionId}`
|
|
5573
|
+
);
|
|
5574
|
+
}
|
|
5575
|
+
this.localControllers.delete(sessionId);
|
|
5576
|
+
}
|
|
5577
|
+
console.log(
|
|
5578
|
+
`[RedisStreamManager] Deleted stream for session ${sessionId}`
|
|
5579
|
+
);
|
|
5580
|
+
} catch (error2) {
|
|
5581
|
+
console.error(
|
|
5582
|
+
`[RedisStreamManager] Error deleting stream for ${sessionId}:`,
|
|
5583
|
+
error2
|
|
5584
|
+
);
|
|
5585
|
+
throw error2;
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
/**
|
|
5589
|
+
* Check if a session has an active stream (on ANY server)
|
|
5590
|
+
*/
|
|
5591
|
+
async has(sessionId) {
|
|
5592
|
+
try {
|
|
5593
|
+
const availableKey = this.getAvailableKey(sessionId);
|
|
5594
|
+
const exists = await this.client.exists(availableKey);
|
|
5595
|
+
return exists === 1;
|
|
5596
|
+
} catch (error2) {
|
|
5597
|
+
console.error(
|
|
5598
|
+
`[RedisStreamManager] Error checking session ${sessionId}:`,
|
|
5599
|
+
error2
|
|
5600
|
+
);
|
|
5601
|
+
return false;
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
/**
|
|
5605
|
+
* Close all connections and cleanup
|
|
5606
|
+
*/
|
|
5607
|
+
async close() {
|
|
5608
|
+
try {
|
|
5609
|
+
for (const heartbeat of this.heartbeats.values()) {
|
|
5610
|
+
clearInterval(heartbeat);
|
|
5611
|
+
}
|
|
5612
|
+
this.heartbeats.clear();
|
|
5613
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
5614
|
+
const sessionIdsToCleanup = Array.from(this.localControllers.keys());
|
|
5615
|
+
for (const sessionId of sessionIdsToCleanup) {
|
|
5616
|
+
await this.client.del(this.getAvailableKey(sessionId));
|
|
5617
|
+
if (this.client.sRem) {
|
|
5618
|
+
await this.client.sRem(activeSessionsKey, sessionId);
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
for (const controller of this.localControllers.values()) {
|
|
5622
|
+
try {
|
|
5623
|
+
controller.close();
|
|
5624
|
+
} catch (error2) {
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
this.localControllers.clear();
|
|
5628
|
+
console.log(`[RedisStreamManager] Closed all streams`);
|
|
5629
|
+
} catch (error2) {
|
|
5630
|
+
console.error(`[RedisStreamManager] Error during close:`, error2);
|
|
5631
|
+
throw error2;
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
/**
|
|
5635
|
+
* Get count of active local streams on this server instance
|
|
5636
|
+
*/
|
|
5637
|
+
get localSize() {
|
|
5638
|
+
return this.localControllers.size;
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
|
|
5642
|
+
// src/server/endpoints/mount-mcp.ts
|
|
5643
|
+
init_runtime();
|
|
5644
|
+
init_telemetry2();
|
|
5645
|
+
var import_node_path2 = require("path");
|
|
5646
|
+
async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMode2) {
|
|
5647
|
+
const { FetchStreamableHTTPServerTransport } = await import("@mcp-use/modelcontextprotocol-sdk/experimental/fetch-streamable-http/index.js");
|
|
5648
|
+
const idleTimeoutMs = config.sessionIdleTimeoutMs ?? 3e5;
|
|
5649
|
+
const sessionStore = config.sessionStore ?? (isProductionMode2 ? new InMemorySessionStore() : new FileSystemSessionStore({
|
|
5650
|
+
path: (0, import_node_path2.join)(process.cwd(), ".mcp-use", "sessions.json")
|
|
5651
|
+
}));
|
|
5652
|
+
const streamManager = config.streamManager ?? new InMemoryStreamManager();
|
|
5653
|
+
const transports = /* @__PURE__ */ new Map();
|
|
5654
|
+
if (config.autoCreateSessionOnInvalidId !== void 0) {
|
|
5655
|
+
console.warn(
|
|
5656
|
+
"[MCP] WARNING: 'autoCreateSessionOnInvalidId' is deprecated and will be removed in a future version.\nThe MCP specification requires clients to send a new InitializeRequest when receiving a 404 for stale sessions.\nModern MCP clients handle this correctly. For session persistence across restarts, use the 'sessionStore' option.\nSee: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management"
|
|
5657
|
+
);
|
|
5658
|
+
}
|
|
5659
|
+
let idleCleanupInterval;
|
|
5660
|
+
if (!config.stateless && idleTimeoutMs > 0) {
|
|
5661
|
+
idleCleanupInterval = startIdleCleanup(
|
|
5662
|
+
sessions,
|
|
5663
|
+
idleTimeoutMs,
|
|
5664
|
+
transports,
|
|
5665
|
+
mcpServerInstance
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
const handleRequest = /* @__PURE__ */ __name(async (c) => {
|
|
5669
|
+
const acceptHeader = c.req.header("Accept") || c.req.header("accept") || "";
|
|
5670
|
+
const clientSupportsSSE = acceptHeader.includes("text/event-stream");
|
|
5671
|
+
const useStatelessMode = config.stateless || !clientSupportsSSE;
|
|
5672
|
+
if (useStatelessMode) {
|
|
5673
|
+
const server = mcpServerInstance.getServerForSession();
|
|
5674
|
+
const transport = new FetchStreamableHTTPServerTransport({
|
|
5675
|
+
sessionIdGenerator: void 0,
|
|
5676
|
+
// No session tracking
|
|
5677
|
+
// Enable plain JSON responses ONLY if client doesn't support SSE
|
|
5678
|
+
// This allows k6/curl to work while maintaining SSE format for compatible clients
|
|
5679
|
+
enableJsonResponse: !clientSupportsSSE
|
|
5680
|
+
});
|
|
5681
|
+
try {
|
|
5682
|
+
await server.connect(transport);
|
|
5683
|
+
const request = c.req.raw;
|
|
5684
|
+
if (!clientSupportsSSE) {
|
|
5685
|
+
const modifiedRequest = new Request(request.url, {
|
|
5686
|
+
method: request.method,
|
|
5687
|
+
headers: {
|
|
5688
|
+
...Object.fromEntries(request.headers.entries()),
|
|
5689
|
+
Accept: "application/json, text/event-stream"
|
|
5690
|
+
},
|
|
5691
|
+
body: request.body,
|
|
5692
|
+
...request.body && { duplex: "half" }
|
|
5693
|
+
});
|
|
5694
|
+
return await transport.handleRequest(modifiedRequest);
|
|
5695
|
+
}
|
|
5696
|
+
return await transport.handleRequest(request);
|
|
5697
|
+
} catch (error2) {
|
|
5698
|
+
console.error("[MCP] Stateless request error:", error2);
|
|
5699
|
+
transport.close();
|
|
5700
|
+
server.close();
|
|
5701
|
+
throw error2;
|
|
5702
|
+
}
|
|
5703
|
+
} else {
|
|
5704
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
5705
|
+
if (c.req.method === "HEAD") {
|
|
5706
|
+
if (sessionId && await sessionStore.has(sessionId)) {
|
|
5707
|
+
const session = await sessionStore.get(sessionId);
|
|
5708
|
+
if (session) {
|
|
5709
|
+
session.lastAccessedAt = Date.now();
|
|
5710
|
+
await sessionStore.set(sessionId, session);
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
return new Response(null, { status: 200 });
|
|
5714
|
+
}
|
|
5715
|
+
if (sessionId && await sessionStore.has(sessionId) && !transports.has(sessionId)) {
|
|
5716
|
+
console.log(
|
|
5717
|
+
`[MCP] Session metadata found but transport lost (likely hot reload): ${sessionId} - recreating transport`
|
|
5718
|
+
);
|
|
5719
|
+
const server2 = mcpServerInstance.getServerForSession();
|
|
5720
|
+
const transport2 = new FetchStreamableHTTPServerTransport({
|
|
5721
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => sessionId, "sessionIdGenerator"),
|
|
5722
|
+
// Reuse existing session ID
|
|
5723
|
+
onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
|
|
5724
|
+
console.log(`[MCP] Session reconnected: ${sid}`);
|
|
5725
|
+
transports.set(sid, transport2);
|
|
5726
|
+
const metadata = await sessionStore.get(sid);
|
|
5727
|
+
const sessionData = {
|
|
5728
|
+
transport: transport2,
|
|
5729
|
+
server: server2,
|
|
5730
|
+
lastAccessedAt: Date.now(),
|
|
5731
|
+
context: c,
|
|
5732
|
+
honoContext: c,
|
|
5733
|
+
...metadata || {}
|
|
5734
|
+
};
|
|
5735
|
+
sessions.set(sid, sessionData);
|
|
5736
|
+
server2.server.oninitialized = async () => {
|
|
5737
|
+
const clientCapabilities = server2.server.getClientCapabilities();
|
|
5738
|
+
const clientInfo = server2.server.getClientInfo?.() || {};
|
|
5739
|
+
const protocolVersion = server2.server.getProtocolVersion?.() || "unknown";
|
|
5740
|
+
const metadata2 = await sessionStore.get(sid);
|
|
5741
|
+
if (metadata2) {
|
|
5742
|
+
metadata2.clientCapabilities = clientCapabilities;
|
|
5743
|
+
metadata2.clientInfo = clientInfo;
|
|
5744
|
+
metadata2.protocolVersion = String(protocolVersion);
|
|
5745
|
+
await sessionStore.set(sid, metadata2);
|
|
5746
|
+
}
|
|
5747
|
+
const sessionData2 = sessions.get(sid);
|
|
5748
|
+
if (sessionData2) {
|
|
5749
|
+
sessionData2.clientCapabilities = clientCapabilities;
|
|
5750
|
+
}
|
|
5751
|
+
Telemetry.getInstance().trackServerInitialize({
|
|
5752
|
+
protocolVersion: String(protocolVersion),
|
|
5753
|
+
clientInfo: clientInfo || {},
|
|
5754
|
+
clientCapabilities: clientCapabilities || {},
|
|
5755
|
+
sessionId: sid
|
|
5756
|
+
}).catch(
|
|
5757
|
+
(e) => console.debug(`Failed to track server initialize: ${e}`)
|
|
5758
|
+
);
|
|
5759
|
+
if (!isProductionMode2) {
|
|
5760
|
+
console.log(
|
|
5761
|
+
`[MCP] Development mode: Sending list_changed notifications to reconnected session ${sid}`
|
|
5762
|
+
);
|
|
5763
|
+
try {
|
|
5764
|
+
const { sendNotificationToSession: sendNotificationToSession3 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
|
|
5765
|
+
await sendNotificationToSession3(
|
|
5766
|
+
sessions,
|
|
5767
|
+
sid,
|
|
5768
|
+
"notifications/tools/list_changed"
|
|
5769
|
+
);
|
|
5770
|
+
await sendNotificationToSession3(
|
|
5771
|
+
sessions,
|
|
5772
|
+
sid,
|
|
5773
|
+
"notifications/resources/list_changed"
|
|
5774
|
+
);
|
|
5775
|
+
await sendNotificationToSession3(
|
|
5776
|
+
sessions,
|
|
5777
|
+
sid,
|
|
5778
|
+
"notifications/prompts/list_changed"
|
|
5779
|
+
);
|
|
5780
|
+
} catch (err) {
|
|
5781
|
+
console.debug(
|
|
5782
|
+
`[MCP] Failed to send list_changed notification:`,
|
|
5783
|
+
err
|
|
5784
|
+
);
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
};
|
|
5788
|
+
}, "onsessioninitialized"),
|
|
5789
|
+
onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
|
|
5790
|
+
console.log(`[MCP] Session closed: ${sid}`);
|
|
5791
|
+
transports.delete(sid);
|
|
5792
|
+
await streamManager.delete(sid);
|
|
5793
|
+
await sessionStore.delete(sid);
|
|
5794
|
+
sessions.delete(sid);
|
|
5795
|
+
mcpServerInstance.cleanupSessionSubscriptions?.(sid);
|
|
5796
|
+
}, "onsessionclosed")
|
|
5797
|
+
});
|
|
5798
|
+
await server2.connect(transport2);
|
|
5799
|
+
return transport2.handleRequest(c.req.raw);
|
|
5800
|
+
}
|
|
5801
|
+
if (sessionId && !await sessionStore.has(sessionId)) {
|
|
5802
|
+
console.log(
|
|
5803
|
+
`[MCP] Session not found: ${sessionId} - returning 404 (client should re-initialize)`
|
|
5804
|
+
);
|
|
5805
|
+
return c.json(
|
|
5806
|
+
{
|
|
5807
|
+
jsonrpc: "2.0",
|
|
5808
|
+
error: { code: -32001, message: "Session not found" },
|
|
5809
|
+
id: null
|
|
5810
|
+
},
|
|
5811
|
+
404
|
|
5812
|
+
);
|
|
5813
|
+
}
|
|
5814
|
+
if (sessionId && transports.has(sessionId)) {
|
|
5815
|
+
const transport2 = transports.get(sessionId);
|
|
5816
|
+
const metadata = await sessionStore.get(sessionId);
|
|
5817
|
+
if (metadata) {
|
|
5818
|
+
metadata.lastAccessedAt = Date.now();
|
|
5819
|
+
await sessionStore.set(sessionId, metadata);
|
|
5820
|
+
}
|
|
5821
|
+
const sessionData = sessions.get(sessionId);
|
|
5822
|
+
if (sessionData) {
|
|
5823
|
+
sessionData.lastAccessedAt = Date.now();
|
|
5824
|
+
sessionData.context = c;
|
|
5825
|
+
sessionData.honoContext = c;
|
|
5826
|
+
}
|
|
5827
|
+
return transport2.handleRequest(c.req.raw);
|
|
5828
|
+
}
|
|
5829
|
+
const server = mcpServerInstance.getServerForSession();
|
|
5830
|
+
const transport = new FetchStreamableHTTPServerTransport({
|
|
4728
5831
|
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
4729
|
-
onsessioninitialized: /* @__PURE__ */ __name((sid) => {
|
|
5832
|
+
onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
|
|
4730
5833
|
console.log(`[MCP] Session initialized: ${sid}`);
|
|
4731
5834
|
transports.set(sid, transport);
|
|
4732
|
-
|
|
5835
|
+
const sessionData = {
|
|
4733
5836
|
transport,
|
|
4734
5837
|
server,
|
|
4735
5838
|
lastAccessedAt: Date.now(),
|
|
4736
5839
|
context: c,
|
|
4737
5840
|
honoContext: c
|
|
5841
|
+
};
|
|
5842
|
+
sessions.set(sid, sessionData);
|
|
5843
|
+
await sessionStore.set(sid, {
|
|
5844
|
+
lastAccessedAt: Date.now()
|
|
4738
5845
|
});
|
|
4739
|
-
server.server.oninitialized = () => {
|
|
5846
|
+
server.server.oninitialized = async () => {
|
|
4740
5847
|
const clientCapabilities = server.server.getClientCapabilities();
|
|
4741
5848
|
const clientInfo = server.server.getClientInfo?.() || {};
|
|
4742
5849
|
const protocolVersion = server.server.getProtocolVersion?.() || "unknown";
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
5850
|
+
const metadata = await sessionStore.get(sid);
|
|
5851
|
+
if (metadata) {
|
|
5852
|
+
metadata.clientCapabilities = clientCapabilities;
|
|
5853
|
+
metadata.clientInfo = clientInfo;
|
|
5854
|
+
metadata.protocolVersion = String(protocolVersion);
|
|
5855
|
+
await sessionStore.set(sid, metadata);
|
|
4746
5856
|
console.log(
|
|
4747
5857
|
`[MCP] Captured client capabilities for session ${sid}:`,
|
|
4748
|
-
Object.keys(clientCapabilities)
|
|
5858
|
+
clientCapabilities ? Object.keys(clientCapabilities) : "none"
|
|
4749
5859
|
);
|
|
4750
5860
|
}
|
|
5861
|
+
const sessionData2 = sessions.get(sid);
|
|
5862
|
+
if (sessionData2) {
|
|
5863
|
+
sessionData2.clientCapabilities = clientCapabilities;
|
|
5864
|
+
}
|
|
4751
5865
|
Telemetry.getInstance().trackServerInitialize({
|
|
4752
5866
|
protocolVersion: String(protocolVersion),
|
|
4753
5867
|
clientInfo: clientInfo || {},
|
|
@@ -4758,9 +5872,11 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
|
|
|
4758
5872
|
);
|
|
4759
5873
|
};
|
|
4760
5874
|
}, "onsessioninitialized"),
|
|
4761
|
-
onsessionclosed: /* @__PURE__ */ __name((sid) => {
|
|
5875
|
+
onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
|
|
4762
5876
|
console.log(`[MCP] Session closed: ${sid}`);
|
|
4763
5877
|
transports.delete(sid);
|
|
5878
|
+
await streamManager.delete(sid);
|
|
5879
|
+
await sessionStore.delete(sid);
|
|
4764
5880
|
sessions.delete(sid);
|
|
4765
5881
|
mcpServerInstance.cleanupSessionSubscriptions?.(sid);
|
|
4766
5882
|
}, "onsessionclosed")
|
|
@@ -5082,6 +6198,7 @@ var MCPServerClass = class {
|
|
|
5082
6198
|
serverPort;
|
|
5083
6199
|
serverHost;
|
|
5084
6200
|
serverBaseUrl;
|
|
6201
|
+
favicon;
|
|
5085
6202
|
registeredTools = [];
|
|
5086
6203
|
registeredPrompts = [];
|
|
5087
6204
|
registeredResources = [];
|
|
@@ -5147,6 +6264,7 @@ var MCPServerClass = class {
|
|
|
5147
6264
|
}
|
|
5148
6265
|
this.serverHost = config.host || "localhost";
|
|
5149
6266
|
this.serverBaseUrl = config.baseUrl;
|
|
6267
|
+
this.favicon = config.favicon;
|
|
5150
6268
|
this.nativeServer = new import_mcp2.McpServer(
|
|
5151
6269
|
{
|
|
5152
6270
|
name: config.name,
|
|
@@ -5211,7 +6329,10 @@ var MCPServerClass = class {
|
|
|
5211
6329
|
"openai/widgetAccessible": widgetConfig.widgetAccessible ?? true,
|
|
5212
6330
|
"openai/resultCanProduceWidget": widgetConfig.resultCanProduceWidget ?? true
|
|
5213
6331
|
};
|
|
5214
|
-
result._meta =
|
|
6332
|
+
result._meta = {
|
|
6333
|
+
...result._meta || {},
|
|
6334
|
+
...responseMeta
|
|
6335
|
+
};
|
|
5215
6336
|
if (result.content?.[0]?.type === "text" && !result.content[0].text) {
|
|
5216
6337
|
result.content[0].text = `Displaying ${widgetName}`;
|
|
5217
6338
|
}
|
|
@@ -5614,6 +6735,9 @@ var MCPServerClass = class {
|
|
|
5614
6735
|
getActiveSessions = getActiveSessions;
|
|
5615
6736
|
sendNotification = sendNotification;
|
|
5616
6737
|
sendNotificationToSession = sendNotificationToSession2;
|
|
6738
|
+
sendToolsListChanged = sendToolsListChanged;
|
|
6739
|
+
sendResourcesListChanged = sendResourcesListChanged;
|
|
6740
|
+
sendPromptsListChanged = sendPromptsListChanged;
|
|
5617
6741
|
/**
|
|
5618
6742
|
* Notify subscribed clients that a resource has been updated
|
|
5619
6743
|
*
|
|
@@ -5866,7 +6990,8 @@ function createMCPServer(name, config = {}) {
|
|
|
5866
6990
|
allowedOrigins: config.allowedOrigins,
|
|
5867
6991
|
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs,
|
|
5868
6992
|
autoCreateSessionOnInvalidId: config.autoCreateSessionOnInvalidId,
|
|
5869
|
-
oauth: config.oauth
|
|
6993
|
+
oauth: config.oauth,
|
|
6994
|
+
favicon: config.favicon
|
|
5870
6995
|
});
|
|
5871
6996
|
return instance;
|
|
5872
6997
|
}
|