mcp-use 1.10.5 → 1.11.0-canary.10
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/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/{chunk-IY632ZQS.js → chunk-3GB7G5X7.js} +2 -2
- package/dist/{chunk-E42PSMPK.js → chunk-5QAXQTDL.js} +1 -1
- package/dist/{chunk-C7WHRUWQ.js → chunk-BEGEDH6P.js} +196 -496
- package/dist/{chunk-MO5FM5B2.js → chunk-BLGIG2QD.js} +443 -969
- package/dist/{chunk-XG7SR6G4.js → chunk-CBJTHTR4.js} +3 -8
- package/dist/{chunk-34R6SIER.js → chunk-FRUZDWXH.js} +1 -1
- package/dist/chunk-GUB5GQDD.js +101 -0
- package/dist/chunk-GXNAXUDI.js +0 -0
- package/dist/{chunk-CPG2WZUL.js → chunk-JRGQRPTN.js} +1 -1
- package/dist/chunk-MFSO5PUW.js +1049 -0
- package/dist/{chunk-UD5FSFEZ.js → chunk-NXFHUS7A.js} +171 -11
- package/dist/chunk-ULFNVP5Z.js +12 -0
- package/dist/chunk-UWWLWLS2.js +62 -0
- package/dist/chunk-VTEYN43V.js +1055 -0
- package/dist/{chunk-YOHGE3NK.js → chunk-XPTKLSBC.js} +16 -2
- package/dist/index.cjs +5065 -4608
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -1068
- package/dist/{langfuse-N5Y5BSXK.js → langfuse-74RGPTAH.js} +2 -2
- package/dist/notifications-FLGIFS56.js +9 -0
- package/dist/src/adapters/base.d.ts +44 -0
- package/dist/src/adapters/base.d.ts.map +1 -1
- package/dist/src/adapters/index.cjs +1346 -0
- package/dist/src/adapters/index.js +11 -0
- package/dist/src/adapters/langchain_adapter.d.ts +12 -1
- package/dist/src/adapters/langchain_adapter.d.ts.map +1 -1
- package/dist/src/agents/index.cjs +3141 -159
- package/dist/src/agents/index.d.ts +2 -0
- package/dist/src/agents/index.d.ts.map +1 -1
- package/dist/src/agents/index.js +10 -6
- package/dist/src/agents/mcp_agent.d.ts +59 -37
- package/dist/src/agents/mcp_agent.d.ts.map +1 -1
- package/dist/src/agents/remote.d.ts +25 -0
- package/dist/src/agents/remote.d.ts.map +1 -1
- package/dist/src/agents/types.d.ts +76 -0
- package/dist/src/agents/types.d.ts.map +1 -1
- package/dist/src/agents/utils/index.d.ts +1 -0
- package/dist/src/agents/utils/index.d.ts.map +1 -1
- package/dist/src/agents/utils/llm_provider.d.ts +53 -0
- package/dist/src/agents/utils/llm_provider.d.ts.map +1 -0
- package/dist/src/browser.cjs +1856 -423
- package/dist/src/browser.d.ts +1 -2
- package/dist/src/browser.d.ts.map +1 -1
- package/dist/src/browser.js +26 -15
- package/dist/src/client/base.d.ts +1 -0
- package/dist/src/client/base.d.ts.map +1 -1
- package/dist/src/client/browser.d.ts +2 -2
- package/dist/src/client/browser.d.ts.map +1 -1
- package/dist/src/client/prompts.cjs +1 -1
- package/dist/src/client/prompts.js +5 -4
- package/dist/src/client.cjs +3787 -0
- package/dist/src/client.js +20 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/connectors/base.d.ts +8 -0
- package/dist/src/connectors/base.d.ts.map +1 -1
- package/dist/src/connectors/index.d.ts +0 -1
- package/dist/src/connectors/index.d.ts.map +1 -1
- package/dist/src/managers/server_manager.d.ts.map +1 -1
- package/dist/src/managers/tools/connect_mcp_server.d.ts.map +1 -1
- package/dist/src/react/index.cjs +259 -298
- package/dist/src/react/index.js +6 -5
- package/dist/src/react/types.d.ts +42 -4
- 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 +1379 -208
- 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 +1353 -249
- 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 +36 -19
- 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 +120 -17
- 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/session.d.ts +16 -2
- package/dist/src/session.d.ts.map +1 -1
- package/dist/src/task_managers/index.d.ts +10 -1
- 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/src/version.d.ts.map +1 -1
- package/dist/{tool-execution-helpers-XTDKRH6N.js → tool-execution-helpers-N7R7YGAW.js} +3 -3
- package/dist/tsup.config.d.ts.map +1 -1
- package/package.json +47 -14
- package/dist/src/connectors/websocket.d.ts +0 -38
- package/dist/src/connectors/websocket.d.ts.map +0 -1
- package/dist/src/task_managers/websocket.d.ts +0 -18
- package/dist/src/task_managers/websocket.d.ts.map +0 -1
- /package/dist/{chunk-EW4MJSHA.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) {
|
|
@@ -643,7 +643,7 @@ var init_logging = __esm({
|
|
|
643
643
|
timestamp({ format: "HH:mm:ss" }),
|
|
644
644
|
this.getFormatter()
|
|
645
645
|
),
|
|
646
|
-
transports: []
|
|
646
|
+
transports: [new winston.transports.Console()]
|
|
647
647
|
});
|
|
648
648
|
}
|
|
649
649
|
return this.instances[name];
|
|
@@ -761,7 +761,7 @@ var VERSION;
|
|
|
761
761
|
var init_version = __esm({
|
|
762
762
|
"src/version.ts"() {
|
|
763
763
|
"use strict";
|
|
764
|
-
VERSION = "1.10
|
|
764
|
+
VERSION = "1.11.0-canary.10";
|
|
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;
|
|
@@ -3335,10 +3455,13 @@ async function mountWidgetsDev(app, serverConfig, registerWidget, options) {
|
|
|
3335
3455
|
await fs.mkdir(widgetTempDir, { recursive: true });
|
|
3336
3456
|
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
3337
3457
|
const relativeResourcesPath = pathHelpers.relative(widgetTempDir, resourcesPath).replace(/\\/g, "/");
|
|
3458
|
+
const mcpUsePath = pathHelpers.join(getCwd(), "node_modules", "mcp-use");
|
|
3459
|
+
const relativeMcpUsePath = pathHelpers.relative(widgetTempDir, mcpUsePath).replace(/\\/g, "/");
|
|
3338
3460
|
const cssContent = `@import "tailwindcss";
|
|
3339
3461
|
|
|
3340
|
-
/* Configure Tailwind to scan the resources directory */
|
|
3462
|
+
/* Configure Tailwind to scan the resources directory and mcp-use package */
|
|
3341
3463
|
@source "${relativeResourcesPath}";
|
|
3464
|
+
@source "${relativeMcpUsePath}/**/*.{ts,tsx,js,jsx}";
|
|
3342
3465
|
`;
|
|
3343
3466
|
await fs.writeFile(
|
|
3344
3467
|
pathHelpers.join(widgetTempDir, "styles.css"),
|
|
@@ -3361,7 +3484,8 @@ if (container && Component) {
|
|
|
3361
3484
|
<head>
|
|
3362
3485
|
<meta charset="UTF-8" />
|
|
3363
3486
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
3364
|
-
<title>${widget2.name} Widget</title
|
|
3487
|
+
<title>${widget2.name} Widget</title>${serverConfig.favicon ? `
|
|
3488
|
+
<link rel="icon" href="/mcp-use/public/${serverConfig.favicon}" />` : ""}
|
|
3365
3489
|
</head>
|
|
3366
3490
|
<body>
|
|
3367
3491
|
<div id="widget-root"></div>
|
|
@@ -3405,6 +3529,50 @@ if (container && Component) {
|
|
|
3405
3529
|
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
3406
3530
|
server.watcher.add(resourcesPath);
|
|
3407
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
|
+
});
|
|
3408
3576
|
}
|
|
3409
3577
|
};
|
|
3410
3578
|
const nodeStubsPlugin = {
|
|
@@ -3517,6 +3685,7 @@ export default PostHog;
|
|
|
3517
3685
|
);
|
|
3518
3686
|
app.use(`${baseRoute}/*`, viteMiddleware);
|
|
3519
3687
|
setupPublicRoutes(app, false);
|
|
3688
|
+
setupFaviconRoute(app, serverConfig.favicon, false);
|
|
3520
3689
|
app.use(`${baseRoute}/*`, async (c) => {
|
|
3521
3690
|
const url = new URL(c.req.url);
|
|
3522
3691
|
const isAsset = url.pathname.match(
|
|
@@ -3726,6 +3895,7 @@ function setupWidgetRoutes(app, serverConfig) {
|
|
|
3726
3895
|
}
|
|
3727
3896
|
});
|
|
3728
3897
|
setupPublicRoutes(app, true);
|
|
3898
|
+
setupFaviconRoute(app, serverConfig.favicon, true);
|
|
3729
3899
|
}
|
|
3730
3900
|
__name(setupWidgetRoutes, "setupWidgetRoutes");
|
|
3731
3901
|
|
|
@@ -3860,18 +4030,30 @@ function uiResourceRegistration(server, definition) {
|
|
|
3860
4030
|
);
|
|
3861
4031
|
const uniqueToolMetadata = {
|
|
3862
4032
|
...toolMetadata,
|
|
3863
|
-
"openai/outputTemplate": uniqueUri
|
|
4033
|
+
"openai/outputTemplate": uniqueUri,
|
|
4034
|
+
"mcp-use/props": params
|
|
4035
|
+
// Pass params as widget props
|
|
3864
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
|
+
];
|
|
3865
4053
|
return {
|
|
3866
4054
|
_meta: uniqueToolMetadata,
|
|
3867
|
-
content
|
|
3868
|
-
|
|
3869
|
-
type: "text",
|
|
3870
|
-
text: `Displaying ${displayName}`
|
|
3871
|
-
}
|
|
3872
|
-
],
|
|
3873
|
-
// structuredContent will be injected as window.openai.toolOutput by Apps SDK
|
|
3874
|
-
structuredContent: params
|
|
4055
|
+
content,
|
|
4056
|
+
structuredContent: toolOutputResult.structuredContent
|
|
3875
4057
|
};
|
|
3876
4058
|
}
|
|
3877
4059
|
return {
|
|
@@ -3897,7 +4079,8 @@ async function mountWidgets(server, options) {
|
|
|
3897
4079
|
serverBaseUrl: server.serverBaseUrl || `http://${server.serverHost}:${server.serverPort || 3e3}`,
|
|
3898
4080
|
serverPort: server.serverPort || 3e3,
|
|
3899
4081
|
cspUrls: getCSPUrls(),
|
|
3900
|
-
buildId: server.buildId
|
|
4082
|
+
buildId: server.buildId,
|
|
4083
|
+
favicon: server.favicon
|
|
3901
4084
|
};
|
|
3902
4085
|
const registerWidget = /* @__PURE__ */ __name((widgetDef) => {
|
|
3903
4086
|
server.uiResource(widgetDef);
|
|
@@ -4392,27 +4575,7 @@ __name(registerPrompt, "registerPrompt");
|
|
|
4392
4575
|
|
|
4393
4576
|
// src/server/roots/roots-registration.ts
|
|
4394
4577
|
init_runtime();
|
|
4395
|
-
|
|
4396
|
-
// src/server/utils/jsonrpc-helpers.ts
|
|
4397
|
-
function createNotification(method, params) {
|
|
4398
|
-
return {
|
|
4399
|
-
jsonrpc: "2.0",
|
|
4400
|
-
method,
|
|
4401
|
-
...params && { params }
|
|
4402
|
-
};
|
|
4403
|
-
}
|
|
4404
|
-
__name(createNotification, "createNotification");
|
|
4405
|
-
function createRequest(id, method, params) {
|
|
4406
|
-
return {
|
|
4407
|
-
jsonrpc: "2.0",
|
|
4408
|
-
id,
|
|
4409
|
-
method,
|
|
4410
|
-
...params && { params }
|
|
4411
|
-
};
|
|
4412
|
-
}
|
|
4413
|
-
__name(createRequest, "createRequest");
|
|
4414
|
-
|
|
4415
|
-
// src/server/roots/roots-registration.ts
|
|
4578
|
+
init_jsonrpc_helpers();
|
|
4416
4579
|
function onRootsChanged(callback) {
|
|
4417
4580
|
this.onRootsChangedCallback = callback;
|
|
4418
4581
|
return this;
|
|
@@ -4574,41 +4737,8 @@ async function requestLogger(c, next) {
|
|
|
4574
4737
|
}
|
|
4575
4738
|
__name(requestLogger, "requestLogger");
|
|
4576
4739
|
|
|
4577
|
-
// src/server/sessions/notifications.ts
|
|
4578
|
-
async function sendNotificationToAll(sessions, method, params) {
|
|
4579
|
-
const notification = createNotification(method, params);
|
|
4580
|
-
for (const [sessionId, session] of sessions.entries()) {
|
|
4581
|
-
try {
|
|
4582
|
-
await session.transport.send(notification);
|
|
4583
|
-
} catch (error2) {
|
|
4584
|
-
console.warn(
|
|
4585
|
-
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
4586
|
-
error2
|
|
4587
|
-
);
|
|
4588
|
-
}
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
__name(sendNotificationToAll, "sendNotificationToAll");
|
|
4592
|
-
async function sendNotificationToSession(sessions, sessionId, method, params) {
|
|
4593
|
-
const session = sessions.get(sessionId);
|
|
4594
|
-
if (!session) {
|
|
4595
|
-
return false;
|
|
4596
|
-
}
|
|
4597
|
-
const notification = createNotification(method, params);
|
|
4598
|
-
try {
|
|
4599
|
-
await session.transport.send(notification);
|
|
4600
|
-
return true;
|
|
4601
|
-
} catch (error2) {
|
|
4602
|
-
console.warn(
|
|
4603
|
-
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
4604
|
-
error2
|
|
4605
|
-
);
|
|
4606
|
-
return false;
|
|
4607
|
-
}
|
|
4608
|
-
}
|
|
4609
|
-
__name(sendNotificationToSession, "sendNotificationToSession");
|
|
4610
|
-
|
|
4611
4740
|
// src/server/notifications/notification-registration.ts
|
|
4741
|
+
init_notifications();
|
|
4612
4742
|
function getActiveSessions() {
|
|
4613
4743
|
return Array.from(this.sessions.keys());
|
|
4614
4744
|
}
|
|
@@ -4626,13 +4756,34 @@ async function sendNotificationToSession2(sessionId, method, params) {
|
|
|
4626
4756
|
);
|
|
4627
4757
|
}
|
|
4628
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");
|
|
4629
4780
|
|
|
4630
4781
|
// src/server/mcp-server.ts
|
|
4631
4782
|
init_tool_execution_helpers();
|
|
4632
4783
|
init_context_storage();
|
|
4633
4784
|
|
|
4634
4785
|
// src/server/sessions/session-manager.ts
|
|
4635
|
-
function startIdleCleanup(sessions, idleTimeoutMs, mcpServerInstance) {
|
|
4786
|
+
function startIdleCleanup(sessions, idleTimeoutMs, transports, mcpServerInstance) {
|
|
4636
4787
|
if (idleTimeoutMs <= 0) {
|
|
4637
4788
|
return void 0;
|
|
4638
4789
|
}
|
|
@@ -4649,136 +4800,1141 @@ function startIdleCleanup(sessions, idleTimeoutMs, mcpServerInstance) {
|
|
|
4649
4800
|
`[MCP] Cleaning up ${expiredSessions.length} idle session(s)`
|
|
4650
4801
|
);
|
|
4651
4802
|
for (const sessionId of expiredSessions) {
|
|
4803
|
+
const transport = transports?.get(sessionId);
|
|
4804
|
+
if (transport?.close) {
|
|
4805
|
+
Promise.resolve(transport.close()).catch((e) => {
|
|
4806
|
+
console.warn(
|
|
4807
|
+
`[MCP] Error closing transport for session ${sessionId}:`,
|
|
4808
|
+
e
|
|
4809
|
+
);
|
|
4810
|
+
});
|
|
4811
|
+
}
|
|
4812
|
+
transports?.delete(sessionId);
|
|
4652
4813
|
sessions.delete(sessionId);
|
|
4653
4814
|
mcpServerInstance?.cleanupSessionSubscriptions?.(sessionId);
|
|
4815
|
+
console.log(
|
|
4816
|
+
`[MCP] Cleaned up resource subscriptions for session ${sessionId}`
|
|
4817
|
+
);
|
|
4654
4818
|
}
|
|
4655
4819
|
}
|
|
4656
4820
|
}, 6e4);
|
|
4657
4821
|
}
|
|
4658
4822
|
__name(startIdleCleanup, "startIdleCleanup");
|
|
4659
4823
|
|
|
4660
|
-
// src/server/
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
let idleCleanupInterval;
|
|
4668
|
-
if (idleTimeoutMs > 0) {
|
|
4669
|
-
idleCleanupInterval = startIdleCleanup(
|
|
4670
|
-
sessions,
|
|
4671
|
-
idleTimeoutMs,
|
|
4672
|
-
mcpServerInstance
|
|
4673
|
-
);
|
|
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");
|
|
4674
4831
|
}
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
}
|
|
4687
|
-
const server = mcpServerInstance.getServerForSession();
|
|
4688
|
-
const transport = new FetchStreamableHTTPServerTransport({
|
|
4689
|
-
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
4690
|
-
onsessioninitialized: /* @__PURE__ */ __name((sid) => {
|
|
4691
|
-
console.log(`[MCP] Session initialized: ${sid}`);
|
|
4692
|
-
transports.set(sid, transport);
|
|
4693
|
-
sessions.set(sid, {
|
|
4694
|
-
transport,
|
|
4695
|
-
server,
|
|
4696
|
-
lastAccessedAt: Date.now(),
|
|
4697
|
-
context: c,
|
|
4698
|
-
honoContext: c
|
|
4699
|
-
});
|
|
4700
|
-
server.server.oninitialized = () => {
|
|
4701
|
-
const clientCapabilities = server.server.getClientCapabilities();
|
|
4702
|
-
const clientInfo = server.server.getClientInfo?.() || {};
|
|
4703
|
-
const protocolVersion = server.server.getProtocolVersion?.() || "unknown";
|
|
4704
|
-
if (clientCapabilities && sessions.has(sid)) {
|
|
4705
|
-
const session = sessions.get(sid);
|
|
4706
|
-
session.clientCapabilities = clientCapabilities;
|
|
4707
|
-
console.log(
|
|
4708
|
-
`[MCP] Captured client capabilities for session ${sid}:`,
|
|
4709
|
-
Object.keys(clientCapabilities)
|
|
4710
|
-
);
|
|
4711
|
-
}
|
|
4712
|
-
Telemetry.getInstance().trackServerInitialize({
|
|
4713
|
-
protocolVersion: String(protocolVersion),
|
|
4714
|
-
clientInfo: clientInfo || {},
|
|
4715
|
-
clientCapabilities: clientCapabilities || {},
|
|
4716
|
-
sessionId: sid
|
|
4717
|
-
}).catch(
|
|
4718
|
-
(e) => console.debug(`Failed to track server initialize: ${e}`)
|
|
4719
|
-
);
|
|
4720
|
-
};
|
|
4721
|
-
}, "onsessioninitialized"),
|
|
4722
|
-
onsessionclosed: /* @__PURE__ */ __name((sid) => {
|
|
4723
|
-
console.log(`[MCP] Session closed: ${sid}`);
|
|
4724
|
-
transports.delete(sid);
|
|
4725
|
-
sessions.delete(sid);
|
|
4726
|
-
mcpServerInstance.cleanupSessionSubscriptions?.(sid);
|
|
4727
|
-
}, "onsessionclosed")
|
|
4728
|
-
});
|
|
4729
|
-
await server.connect(transport);
|
|
4730
|
-
return transport.handleRequest(c.req.raw);
|
|
4731
|
-
}, "handleRequest");
|
|
4732
|
-
for (const endpoint of ["/mcp", "/sse"]) {
|
|
4733
|
-
app.on(["GET", "POST", "DELETE"], endpoint, handleRequest);
|
|
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;
|
|
4734
4843
|
}
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
__name(mountMcp, "mountMcp");
|
|
4741
|
-
|
|
4742
|
-
// src/server/oauth/routes.ts
|
|
4743
|
-
var import_cors2 = require("hono/cors");
|
|
4744
|
-
function setupOAuthRoutes(app, provider, baseUrl) {
|
|
4745
|
-
const mode = provider.getMode?.() || "proxy";
|
|
4746
|
-
app.use(
|
|
4747
|
-
"/.well-known/*",
|
|
4748
|
-
(0, import_cors2.cors)({
|
|
4749
|
-
origin: "*",
|
|
4750
|
-
// Allow all origins for metadata discovery
|
|
4751
|
-
allowMethods: ["GET", "OPTIONS"],
|
|
4752
|
-
allowHeaders: ["Content-Type", "Authorization"],
|
|
4753
|
-
exposeHeaders: ["Content-Type"],
|
|
4754
|
-
maxAge: 86400
|
|
4755
|
-
// Cache preflight for 24 hours
|
|
4756
|
-
})
|
|
4757
|
-
);
|
|
4758
|
-
if (mode === "proxy") {
|
|
4759
|
-
app.use(
|
|
4760
|
-
"/authorize",
|
|
4761
|
-
(0, import_cors2.cors)({
|
|
4762
|
-
origin: "*",
|
|
4763
|
-
allowMethods: ["GET", "POST", "OPTIONS"],
|
|
4764
|
-
allowHeaders: ["Content-Type", "Authorization"],
|
|
4765
|
-
maxAge: 86400
|
|
4766
|
-
})
|
|
4767
|
-
);
|
|
4768
|
-
app.use(
|
|
4769
|
-
"/token",
|
|
4770
|
-
(0, import_cors2.cors)({
|
|
4771
|
-
origin: "*",
|
|
4772
|
-
allowMethods: ["POST", "OPTIONS"],
|
|
4773
|
-
allowHeaders: ["Content-Type", "Authorization"],
|
|
4774
|
-
maxAge: 86400
|
|
4775
|
-
})
|
|
4776
|
-
);
|
|
4844
|
+
/**
|
|
4845
|
+
* Store or update session metadata
|
|
4846
|
+
*/
|
|
4847
|
+
async set(sessionId, data) {
|
|
4848
|
+
this.sessions.set(sessionId, data);
|
|
4777
4849
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
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;
|
|
4925
|
+
}
|
|
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({
|
|
5831
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
5832
|
+
onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
|
|
5833
|
+
console.log(`[MCP] Session initialized: ${sid}`);
|
|
5834
|
+
transports.set(sid, transport);
|
|
5835
|
+
const sessionData = {
|
|
5836
|
+
transport,
|
|
5837
|
+
server,
|
|
5838
|
+
lastAccessedAt: Date.now(),
|
|
5839
|
+
context: c,
|
|
5840
|
+
honoContext: c
|
|
5841
|
+
};
|
|
5842
|
+
sessions.set(sid, sessionData);
|
|
5843
|
+
await sessionStore.set(sid, {
|
|
5844
|
+
lastAccessedAt: Date.now()
|
|
5845
|
+
});
|
|
5846
|
+
server.server.oninitialized = async () => {
|
|
5847
|
+
const clientCapabilities = server.server.getClientCapabilities();
|
|
5848
|
+
const clientInfo = server.server.getClientInfo?.() || {};
|
|
5849
|
+
const protocolVersion = server.server.getProtocolVersion?.() || "unknown";
|
|
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);
|
|
5856
|
+
console.log(
|
|
5857
|
+
`[MCP] Captured client capabilities for session ${sid}:`,
|
|
5858
|
+
clientCapabilities ? Object.keys(clientCapabilities) : "none"
|
|
5859
|
+
);
|
|
5860
|
+
}
|
|
5861
|
+
const sessionData2 = sessions.get(sid);
|
|
5862
|
+
if (sessionData2) {
|
|
5863
|
+
sessionData2.clientCapabilities = clientCapabilities;
|
|
5864
|
+
}
|
|
5865
|
+
Telemetry.getInstance().trackServerInitialize({
|
|
5866
|
+
protocolVersion: String(protocolVersion),
|
|
5867
|
+
clientInfo: clientInfo || {},
|
|
5868
|
+
clientCapabilities: clientCapabilities || {},
|
|
5869
|
+
sessionId: sid
|
|
5870
|
+
}).catch(
|
|
5871
|
+
(e) => console.debug(`Failed to track server initialize: ${e}`)
|
|
5872
|
+
);
|
|
5873
|
+
};
|
|
5874
|
+
}, "onsessioninitialized"),
|
|
5875
|
+
onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
|
|
5876
|
+
console.log(`[MCP] Session closed: ${sid}`);
|
|
5877
|
+
transports.delete(sid);
|
|
5878
|
+
await streamManager.delete(sid);
|
|
5879
|
+
await sessionStore.delete(sid);
|
|
5880
|
+
sessions.delete(sid);
|
|
5881
|
+
mcpServerInstance.cleanupSessionSubscriptions?.(sid);
|
|
5882
|
+
}, "onsessionclosed")
|
|
5883
|
+
});
|
|
5884
|
+
await server.connect(transport);
|
|
5885
|
+
return transport.handleRequest(c.req.raw);
|
|
5886
|
+
}
|
|
5887
|
+
}, "handleRequest");
|
|
5888
|
+
for (const endpoint of ["/mcp", "/sse"]) {
|
|
5889
|
+
app.on(["GET", "POST", "DELETE", "HEAD"], endpoint, handleRequest);
|
|
5890
|
+
}
|
|
5891
|
+
console.log(
|
|
5892
|
+
`[MCP] Server mounted at /mcp and /sse (${config.stateless ? "stateless" : "stateful"} mode)`
|
|
5893
|
+
);
|
|
5894
|
+
return { mcpMounted: true, idleCleanupInterval };
|
|
5895
|
+
}
|
|
5896
|
+
__name(mountMcp, "mountMcp");
|
|
5897
|
+
|
|
5898
|
+
// src/server/oauth/routes.ts
|
|
5899
|
+
var import_cors2 = require("hono/cors");
|
|
5900
|
+
function setupOAuthRoutes(app, provider, baseUrl) {
|
|
5901
|
+
const mode = provider.getMode?.() || "proxy";
|
|
5902
|
+
app.use(
|
|
5903
|
+
"/.well-known/*",
|
|
5904
|
+
(0, import_cors2.cors)({
|
|
5905
|
+
origin: "*",
|
|
5906
|
+
// Allow all origins for metadata discovery
|
|
5907
|
+
allowMethods: ["GET", "OPTIONS"],
|
|
5908
|
+
allowHeaders: ["Content-Type", "Authorization"],
|
|
5909
|
+
exposeHeaders: ["Content-Type"],
|
|
5910
|
+
maxAge: 86400
|
|
5911
|
+
// Cache preflight for 24 hours
|
|
5912
|
+
})
|
|
5913
|
+
);
|
|
5914
|
+
if (mode === "proxy") {
|
|
5915
|
+
app.use(
|
|
5916
|
+
"/authorize",
|
|
5917
|
+
(0, import_cors2.cors)({
|
|
5918
|
+
origin: "*",
|
|
5919
|
+
allowMethods: ["GET", "POST", "OPTIONS"],
|
|
5920
|
+
allowHeaders: ["Content-Type", "Authorization"],
|
|
5921
|
+
maxAge: 86400
|
|
5922
|
+
})
|
|
5923
|
+
);
|
|
5924
|
+
app.use(
|
|
5925
|
+
"/token",
|
|
5926
|
+
(0, import_cors2.cors)({
|
|
5927
|
+
origin: "*",
|
|
5928
|
+
allowMethods: ["POST", "OPTIONS"],
|
|
5929
|
+
allowHeaders: ["Content-Type", "Authorization"],
|
|
5930
|
+
maxAge: 86400
|
|
5931
|
+
})
|
|
5932
|
+
);
|
|
5933
|
+
}
|
|
5934
|
+
if (mode === "proxy") {
|
|
5935
|
+
const handleAuthorize = /* @__PURE__ */ __name(async (c) => {
|
|
5936
|
+
const params = c.req.method === "POST" ? await c.req.parseBody() : c.req.query();
|
|
5937
|
+
const clientId = params.client_id;
|
|
4782
5938
|
const redirectUri = params.redirect_uri;
|
|
4783
5939
|
const responseType = params.response_type;
|
|
4784
5940
|
const codeChallenge = params.code_challenge;
|
|
@@ -5042,6 +6198,7 @@ var MCPServerClass = class {
|
|
|
5042
6198
|
serverPort;
|
|
5043
6199
|
serverHost;
|
|
5044
6200
|
serverBaseUrl;
|
|
6201
|
+
favicon;
|
|
5045
6202
|
registeredTools = [];
|
|
5046
6203
|
registeredPrompts = [];
|
|
5047
6204
|
registeredResources = [];
|
|
@@ -5099,8 +6256,15 @@ var MCPServerClass = class {
|
|
|
5099
6256
|
*/
|
|
5100
6257
|
constructor(config) {
|
|
5101
6258
|
this.config = config;
|
|
6259
|
+
if (this.config.stateless === void 0) {
|
|
6260
|
+
this.config.stateless = isDeno;
|
|
6261
|
+
if (this.config.stateless) {
|
|
6262
|
+
console.log("[MCP] Deno detected - using stateless mode (no sessions)");
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
5102
6265
|
this.serverHost = config.host || "localhost";
|
|
5103
6266
|
this.serverBaseUrl = config.baseUrl;
|
|
6267
|
+
this.favicon = config.favicon;
|
|
5104
6268
|
this.nativeServer = new import_mcp2.McpServer(
|
|
5105
6269
|
{
|
|
5106
6270
|
name: config.name,
|
|
@@ -5165,7 +6329,10 @@ var MCPServerClass = class {
|
|
|
5165
6329
|
"openai/widgetAccessible": widgetConfig.widgetAccessible ?? true,
|
|
5166
6330
|
"openai/resultCanProduceWidget": widgetConfig.resultCanProduceWidget ?? true
|
|
5167
6331
|
};
|
|
5168
|
-
result._meta =
|
|
6332
|
+
result._meta = {
|
|
6333
|
+
...result._meta || {},
|
|
6334
|
+
...responseMeta
|
|
6335
|
+
};
|
|
5169
6336
|
if (result.content?.[0]?.type === "text" && !result.content[0].text) {
|
|
5170
6337
|
result.content[0].text = `Displaying ${widgetName}`;
|
|
5171
6338
|
}
|
|
@@ -5568,6 +6735,9 @@ var MCPServerClass = class {
|
|
|
5568
6735
|
getActiveSessions = getActiveSessions;
|
|
5569
6736
|
sendNotification = sendNotification;
|
|
5570
6737
|
sendNotificationToSession = sendNotificationToSession2;
|
|
6738
|
+
sendToolsListChanged = sendToolsListChanged;
|
|
6739
|
+
sendResourcesListChanged = sendResourcesListChanged;
|
|
6740
|
+
sendPromptsListChanged = sendPromptsListChanged;
|
|
5571
6741
|
/**
|
|
5572
6742
|
* Notify subscribed clients that a resource has been updated
|
|
5573
6743
|
*
|
|
@@ -5820,7 +6990,8 @@ function createMCPServer(name, config = {}) {
|
|
|
5820
6990
|
allowedOrigins: config.allowedOrigins,
|
|
5821
6991
|
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs,
|
|
5822
6992
|
autoCreateSessionOnInvalidId: config.autoCreateSessionOnInvalidId,
|
|
5823
|
-
oauth: config.oauth
|
|
6993
|
+
oauth: config.oauth,
|
|
6994
|
+
favicon: config.favicon
|
|
5824
6995
|
});
|
|
5825
6996
|
return instance;
|
|
5826
6997
|
}
|