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
package/dist/src/server/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRequest,
|
|
3
|
+
sendNotificationToAll,
|
|
4
|
+
sendNotificationToSession
|
|
5
|
+
} from "../../chunk-UWWLWLS2.js";
|
|
1
6
|
import {
|
|
2
7
|
getRequestContext,
|
|
3
8
|
hasRequestContext,
|
|
@@ -7,7 +12,7 @@ import {
|
|
|
7
12
|
createEnhancedContext,
|
|
8
13
|
findSessionContext,
|
|
9
14
|
isValidLogLevel
|
|
10
|
-
} from "../../chunk-
|
|
15
|
+
} from "../../chunk-UCPSHMNO.js";
|
|
11
16
|
import {
|
|
12
17
|
convertToolResultToResourceResult
|
|
13
18
|
} from "../../chunk-362PI25Z.js";
|
|
@@ -25,7 +30,7 @@ import {
|
|
|
25
30
|
getPackageVersion,
|
|
26
31
|
isDeno,
|
|
27
32
|
pathHelpers
|
|
28
|
-
} from "../../chunk-
|
|
33
|
+
} from "../../chunk-Q5LZL6BH.js";
|
|
29
34
|
import "../../chunk-FRUZDWXH.js";
|
|
30
35
|
import {
|
|
31
36
|
__name
|
|
@@ -314,17 +319,23 @@ function binary(base64Data, mimeType) {
|
|
|
314
319
|
}
|
|
315
320
|
__name(binary, "binary");
|
|
316
321
|
function widget(config) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
322
|
+
const props = config.props || config.data || {};
|
|
323
|
+
const { output, message } = config;
|
|
324
|
+
const finalContent = message ? [{ type: "text", text: message }] : Array.isArray(output?.content) && output.content.length > 0 ? output.content : [{ type: "text", text: "" }];
|
|
325
|
+
const meta = {
|
|
326
|
+
...output?._meta || {},
|
|
327
|
+
"mcp-use/props": props
|
|
328
|
+
};
|
|
329
|
+
const result = {
|
|
330
|
+
content: finalContent,
|
|
331
|
+
_meta: meta
|
|
327
332
|
};
|
|
333
|
+
if (output?.structuredContent) {
|
|
334
|
+
result.structuredContent = output.structuredContent;
|
|
335
|
+
} else if (Object.keys(props).length > 0) {
|
|
336
|
+
result.structuredContent = props;
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
328
339
|
}
|
|
329
340
|
__name(widget, "widget");
|
|
330
341
|
function mix(...results) {
|
|
@@ -985,7 +996,7 @@ function processWidgetHtml(html2, widgetName, baseUrl) {
|
|
|
985
996
|
}
|
|
986
997
|
__name(processWidgetHtml, "processWidgetHtml");
|
|
987
998
|
function createWidgetRegistration(widgetName, metadata, html2, serverConfig, isDev = false) {
|
|
988
|
-
const props = metadata.inputs || {};
|
|
999
|
+
const props = metadata.props || metadata.inputs || metadata.schema || {};
|
|
989
1000
|
const description = metadata.description || `Widget: ${widgetName}`;
|
|
990
1001
|
const title = metadata.title || widgetName;
|
|
991
1002
|
const exposeAsTool = metadata.exposeAsTool !== void 0 ? metadata.exposeAsTool : true;
|
|
@@ -1127,6 +1138,33 @@ function setupPublicRoutes(app, useDistDirectory = false) {
|
|
|
1127
1138
|
});
|
|
1128
1139
|
}
|
|
1129
1140
|
__name(setupPublicRoutes, "setupPublicRoutes");
|
|
1141
|
+
function setupFaviconRoute(app, faviconPath, useDistDirectory = false) {
|
|
1142
|
+
if (!faviconPath) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
app.get("/favicon.ico", async (c) => {
|
|
1146
|
+
const basePath = useDistDirectory ? "dist/public" : "public";
|
|
1147
|
+
const fullPath = pathHelpers.join(getCwd(), basePath, faviconPath);
|
|
1148
|
+
try {
|
|
1149
|
+
if (await fsHelpers.existsSync(fullPath)) {
|
|
1150
|
+
const content = await fsHelpers.readFile(fullPath);
|
|
1151
|
+
const contentType = getContentType(faviconPath);
|
|
1152
|
+
return new Response(content, {
|
|
1153
|
+
status: 200,
|
|
1154
|
+
headers: {
|
|
1155
|
+
"Content-Type": contentType,
|
|
1156
|
+
"Cache-Control": "public, max-age=31536000"
|
|
1157
|
+
// Cache for 1 year
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
return c.notFound();
|
|
1162
|
+
} catch {
|
|
1163
|
+
return c.notFound();
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
__name(setupFaviconRoute, "setupFaviconRoute");
|
|
1130
1168
|
|
|
1131
1169
|
// src/server/widgets/mount-widgets-dev.ts
|
|
1132
1170
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
@@ -1176,6 +1214,19 @@ async function mountWidgetsDev(app, serverConfig, registerWidget, options) {
|
|
|
1176
1214
|
return;
|
|
1177
1215
|
}
|
|
1178
1216
|
const tempDir = pathHelpers.join(getCwd(), TMP_MCP_USE_DIR);
|
|
1217
|
+
try {
|
|
1218
|
+
await fs.access(tempDir);
|
|
1219
|
+
const currentWidgetNames = new Set(entries.map((e) => e.name));
|
|
1220
|
+
const existingDirs = await fs.readdir(tempDir, { withFileTypes: true });
|
|
1221
|
+
for (const dirent of existingDirs) {
|
|
1222
|
+
if (dirent.isDirectory() && !currentWidgetNames.has(dirent.name)) {
|
|
1223
|
+
const staleDir = pathHelpers.join(tempDir, dirent.name);
|
|
1224
|
+
await fs.rm(staleDir, { recursive: true, force: true });
|
|
1225
|
+
console.log(`[WIDGETS] Cleaned up stale widget: ${dirent.name}`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
} catch {
|
|
1229
|
+
}
|
|
1179
1230
|
await fs.mkdir(tempDir, { recursive: true }).catch(() => {
|
|
1180
1231
|
});
|
|
1181
1232
|
let createServer;
|
|
@@ -1242,7 +1293,8 @@ if (container && Component) {
|
|
|
1242
1293
|
<head>
|
|
1243
1294
|
<meta charset="UTF-8" />
|
|
1244
1295
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
1245
|
-
<title>${widget2.name} Widget</title
|
|
1296
|
+
<title>${widget2.name} Widget</title>${serverConfig.favicon ? `
|
|
1297
|
+
<link rel="icon" href="/mcp-use/public/${serverConfig.favicon}" />` : ""}
|
|
1246
1298
|
</head>
|
|
1247
1299
|
<body>
|
|
1248
1300
|
<div id="widget-root"></div>
|
|
@@ -1286,6 +1338,50 @@ if (container && Component) {
|
|
|
1286
1338
|
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
1287
1339
|
server.watcher.add(resourcesPath);
|
|
1288
1340
|
console.log(`[WIDGETS] Watching resources directory: ${resourcesPath}`);
|
|
1341
|
+
server.watcher.on("unlink", async (filePath) => {
|
|
1342
|
+
const relativePath = pathHelpers.relative(resourcesPath, filePath);
|
|
1343
|
+
if ((relativePath.endsWith(".tsx") || relativePath.endsWith(".ts")) && !relativePath.includes("/")) {
|
|
1344
|
+
const widgetName = relativePath.replace(/\.tsx?$/, "");
|
|
1345
|
+
const widgetDir = pathHelpers.join(tempDir, widgetName);
|
|
1346
|
+
try {
|
|
1347
|
+
await fs.access(widgetDir);
|
|
1348
|
+
await fs.rm(widgetDir, { recursive: true, force: true });
|
|
1349
|
+
console.log(
|
|
1350
|
+
`[WIDGETS] Cleaned up stale widget (file removed): ${widgetName}`
|
|
1351
|
+
);
|
|
1352
|
+
} catch {
|
|
1353
|
+
}
|
|
1354
|
+
} else if (relativePath.endsWith("widget.tsx")) {
|
|
1355
|
+
const parts = relativePath.split("/");
|
|
1356
|
+
if (parts.length === 2) {
|
|
1357
|
+
const widgetName = parts[0];
|
|
1358
|
+
const widgetDir = pathHelpers.join(tempDir, widgetName);
|
|
1359
|
+
try {
|
|
1360
|
+
await fs.access(widgetDir);
|
|
1361
|
+
await fs.rm(widgetDir, { recursive: true, force: true });
|
|
1362
|
+
console.log(
|
|
1363
|
+
`[WIDGETS] Cleaned up stale widget (file removed): ${widgetName}`
|
|
1364
|
+
);
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
server.watcher.on("unlinkDir", async (dirPath) => {
|
|
1371
|
+
const relativePath = pathHelpers.relative(resourcesPath, dirPath);
|
|
1372
|
+
if (relativePath && !relativePath.includes("/")) {
|
|
1373
|
+
const widgetName = relativePath;
|
|
1374
|
+
const widgetDir = pathHelpers.join(tempDir, widgetName);
|
|
1375
|
+
try {
|
|
1376
|
+
await fs.access(widgetDir);
|
|
1377
|
+
await fs.rm(widgetDir, { recursive: true, force: true });
|
|
1378
|
+
console.log(
|
|
1379
|
+
`[WIDGETS] Cleaned up stale widget (directory removed): ${widgetName}`
|
|
1380
|
+
);
|
|
1381
|
+
} catch {
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1289
1385
|
}
|
|
1290
1386
|
};
|
|
1291
1387
|
const nodeStubsPlugin = {
|
|
@@ -1398,6 +1494,7 @@ export default PostHog;
|
|
|
1398
1494
|
);
|
|
1399
1495
|
app.use(`${baseRoute}/*`, viteMiddleware);
|
|
1400
1496
|
setupPublicRoutes(app, false);
|
|
1497
|
+
setupFaviconRoute(app, serverConfig.favicon, false);
|
|
1401
1498
|
app.use(`${baseRoute}/*`, async (c) => {
|
|
1402
1499
|
const url = new URL(c.req.url);
|
|
1403
1500
|
const isAsset = url.pathname.match(
|
|
@@ -1605,6 +1702,7 @@ function setupWidgetRoutes(app, serverConfig) {
|
|
|
1605
1702
|
}
|
|
1606
1703
|
});
|
|
1607
1704
|
setupPublicRoutes(app, true);
|
|
1705
|
+
setupFaviconRoute(app, serverConfig.favicon, true);
|
|
1608
1706
|
}
|
|
1609
1707
|
__name(setupWidgetRoutes, "setupWidgetRoutes");
|
|
1610
1708
|
|
|
@@ -1739,18 +1837,30 @@ function uiResourceRegistration(server, definition) {
|
|
|
1739
1837
|
);
|
|
1740
1838
|
const uniqueToolMetadata = {
|
|
1741
1839
|
...toolMetadata,
|
|
1742
|
-
"openai/outputTemplate": uniqueUri
|
|
1840
|
+
"openai/outputTemplate": uniqueUri,
|
|
1841
|
+
"mcp-use/props": params
|
|
1842
|
+
// Pass params as widget props
|
|
1743
1843
|
};
|
|
1844
|
+
let toolOutputResult;
|
|
1845
|
+
if (definition.toolOutput) {
|
|
1846
|
+
toolOutputResult = typeof definition.toolOutput === "function" ? definition.toolOutput(params) : definition.toolOutput;
|
|
1847
|
+
} else {
|
|
1848
|
+
toolOutputResult = {
|
|
1849
|
+
content: [
|
|
1850
|
+
{
|
|
1851
|
+
type: "text",
|
|
1852
|
+
text: `Displaying ${displayName}`
|
|
1853
|
+
}
|
|
1854
|
+
]
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
const content = toolOutputResult.content || [
|
|
1858
|
+
{ type: "text", text: `Displaying ${displayName}` }
|
|
1859
|
+
];
|
|
1744
1860
|
return {
|
|
1745
1861
|
_meta: uniqueToolMetadata,
|
|
1746
|
-
content
|
|
1747
|
-
|
|
1748
|
-
type: "text",
|
|
1749
|
-
text: `Displaying ${displayName}`
|
|
1750
|
-
}
|
|
1751
|
-
],
|
|
1752
|
-
// structuredContent will be injected as window.openai.toolOutput by Apps SDK
|
|
1753
|
-
structuredContent: params
|
|
1862
|
+
content,
|
|
1863
|
+
structuredContent: toolOutputResult.structuredContent
|
|
1754
1864
|
};
|
|
1755
1865
|
}
|
|
1756
1866
|
return {
|
|
@@ -1776,7 +1886,8 @@ async function mountWidgets(server, options) {
|
|
|
1776
1886
|
serverBaseUrl: server.serverBaseUrl || `http://${server.serverHost}:${server.serverPort || 3e3}`,
|
|
1777
1887
|
serverPort: server.serverPort || 3e3,
|
|
1778
1888
|
cspUrls: getCSPUrls(),
|
|
1779
|
-
buildId: server.buildId
|
|
1889
|
+
buildId: server.buildId,
|
|
1890
|
+
favicon: server.favicon
|
|
1780
1891
|
};
|
|
1781
1892
|
const registerWidget = /* @__PURE__ */ __name((widgetDef) => {
|
|
1782
1893
|
server.uiResource(widgetDef);
|
|
@@ -2094,7 +2205,7 @@ function registerResource(resourceDefinition, callback) {
|
|
|
2094
2205
|
const explicitMimeType = resourceDefinition.mimeType;
|
|
2095
2206
|
const wrappedCallback = /* @__PURE__ */ __name(async () => {
|
|
2096
2207
|
const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-NA4MHWOZ.js");
|
|
2097
|
-
const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-
|
|
2208
|
+
const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-MXVN6YNU.js");
|
|
2098
2209
|
const initialRequestContext = getRequestContext2();
|
|
2099
2210
|
const sessions = this.sessions || /* @__PURE__ */ new Map();
|
|
2100
2211
|
const { requestContext } = findSessionContext2(
|
|
@@ -2172,7 +2283,7 @@ function registerResourceTemplate(resourceTemplateDefinition, callback) {
|
|
|
2172
2283
|
async (uri) => {
|
|
2173
2284
|
const params = this.parseTemplateUri(uriTemplate, uri.toString());
|
|
2174
2285
|
const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-NA4MHWOZ.js");
|
|
2175
|
-
const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-
|
|
2286
|
+
const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-MXVN6YNU.js");
|
|
2176
2287
|
const initialRequestContext = getRequestContext2();
|
|
2177
2288
|
const sessions = this.sessions || /* @__PURE__ */ new Map();
|
|
2178
2289
|
const { requestContext } = findSessionContext2(
|
|
@@ -2227,7 +2338,7 @@ function registerPrompt(promptDefinition, callback) {
|
|
|
2227
2338
|
}
|
|
2228
2339
|
const wrappedCallback = /* @__PURE__ */ __name(async (params, extra) => {
|
|
2229
2340
|
const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-NA4MHWOZ.js");
|
|
2230
|
-
const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-
|
|
2341
|
+
const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-MXVN6YNU.js");
|
|
2231
2342
|
const initialRequestContext = getRequestContext2();
|
|
2232
2343
|
const sessions = this.sessions || /* @__PURE__ */ new Map();
|
|
2233
2344
|
const { requestContext } = findSessionContext2(
|
|
@@ -2264,25 +2375,6 @@ function registerPrompt(promptDefinition, callback) {
|
|
|
2264
2375
|
}
|
|
2265
2376
|
__name(registerPrompt, "registerPrompt");
|
|
2266
2377
|
|
|
2267
|
-
// src/server/utils/jsonrpc-helpers.ts
|
|
2268
|
-
function createNotification(method, params) {
|
|
2269
|
-
return {
|
|
2270
|
-
jsonrpc: "2.0",
|
|
2271
|
-
method,
|
|
2272
|
-
...params && { params }
|
|
2273
|
-
};
|
|
2274
|
-
}
|
|
2275
|
-
__name(createNotification, "createNotification");
|
|
2276
|
-
function createRequest(id, method, params) {
|
|
2277
|
-
return {
|
|
2278
|
-
jsonrpc: "2.0",
|
|
2279
|
-
id,
|
|
2280
|
-
method,
|
|
2281
|
-
...params && { params }
|
|
2282
|
-
};
|
|
2283
|
-
}
|
|
2284
|
-
__name(createRequest, "createRequest");
|
|
2285
|
-
|
|
2286
2378
|
// src/server/roots/roots-registration.ts
|
|
2287
2379
|
function onRootsChanged(callback) {
|
|
2288
2380
|
this.onRootsChangedCallback = callback;
|
|
@@ -2444,40 +2536,6 @@ async function requestLogger(c, next) {
|
|
|
2444
2536
|
}
|
|
2445
2537
|
__name(requestLogger, "requestLogger");
|
|
2446
2538
|
|
|
2447
|
-
// src/server/sessions/notifications.ts
|
|
2448
|
-
async function sendNotificationToAll(sessions, method, params) {
|
|
2449
|
-
const notification = createNotification(method, params);
|
|
2450
|
-
for (const [sessionId, session] of sessions.entries()) {
|
|
2451
|
-
try {
|
|
2452
|
-
await session.transport.send(notification);
|
|
2453
|
-
} catch (error2) {
|
|
2454
|
-
console.warn(
|
|
2455
|
-
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2456
|
-
error2
|
|
2457
|
-
);
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
__name(sendNotificationToAll, "sendNotificationToAll");
|
|
2462
|
-
async function sendNotificationToSession(sessions, sessionId, method, params) {
|
|
2463
|
-
const session = sessions.get(sessionId);
|
|
2464
|
-
if (!session) {
|
|
2465
|
-
return false;
|
|
2466
|
-
}
|
|
2467
|
-
const notification = createNotification(method, params);
|
|
2468
|
-
try {
|
|
2469
|
-
await session.transport.send(notification);
|
|
2470
|
-
return true;
|
|
2471
|
-
} catch (error2) {
|
|
2472
|
-
console.warn(
|
|
2473
|
-
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2474
|
-
error2
|
|
2475
|
-
);
|
|
2476
|
-
return false;
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
__name(sendNotificationToSession, "sendNotificationToSession");
|
|
2480
|
-
|
|
2481
2539
|
// src/server/notifications/notification-registration.ts
|
|
2482
2540
|
function getActiveSessions() {
|
|
2483
2541
|
return Array.from(this.sessions.keys());
|
|
@@ -2496,6 +2554,27 @@ async function sendNotificationToSession2(sessionId, method, params) {
|
|
|
2496
2554
|
);
|
|
2497
2555
|
}
|
|
2498
2556
|
__name(sendNotificationToSession2, "sendNotificationToSession");
|
|
2557
|
+
async function sendToolsListChanged() {
|
|
2558
|
+
await sendNotificationToAll(
|
|
2559
|
+
this.sessions,
|
|
2560
|
+
"notifications/tools/list_changed"
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
__name(sendToolsListChanged, "sendToolsListChanged");
|
|
2564
|
+
async function sendResourcesListChanged() {
|
|
2565
|
+
await sendNotificationToAll(
|
|
2566
|
+
this.sessions,
|
|
2567
|
+
"notifications/resources/list_changed"
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2570
|
+
__name(sendResourcesListChanged, "sendResourcesListChanged");
|
|
2571
|
+
async function sendPromptsListChanged() {
|
|
2572
|
+
await sendNotificationToAll(
|
|
2573
|
+
this.sessions,
|
|
2574
|
+
"notifications/prompts/list_changed"
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
__name(sendPromptsListChanged, "sendPromptsListChanged");
|
|
2499
2578
|
|
|
2500
2579
|
// src/server/sessions/session-manager.ts
|
|
2501
2580
|
function startIdleCleanup(sessions, idleTimeoutMs, transports, mcpServerInstance) {
|
|
@@ -2536,11 +2615,836 @@ function startIdleCleanup(sessions, idleTimeoutMs, transports, mcpServerInstance
|
|
|
2536
2615
|
}
|
|
2537
2616
|
__name(startIdleCleanup, "startIdleCleanup");
|
|
2538
2617
|
|
|
2618
|
+
// src/server/sessions/stores/memory.ts
|
|
2619
|
+
var InMemorySessionStore = class {
|
|
2620
|
+
static {
|
|
2621
|
+
__name(this, "InMemorySessionStore");
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Internal map storing session metadata
|
|
2625
|
+
* Key: sessionId, Value: SessionMetadata
|
|
2626
|
+
*/
|
|
2627
|
+
sessions = /* @__PURE__ */ new Map();
|
|
2628
|
+
/**
|
|
2629
|
+
* Retrieve session metadata by ID
|
|
2630
|
+
*/
|
|
2631
|
+
async get(sessionId) {
|
|
2632
|
+
const data = this.sessions.get(sessionId);
|
|
2633
|
+
return data ?? null;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Store or update session metadata
|
|
2637
|
+
*/
|
|
2638
|
+
async set(sessionId, data) {
|
|
2639
|
+
this.sessions.set(sessionId, data);
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Delete session metadata
|
|
2643
|
+
*/
|
|
2644
|
+
async delete(sessionId) {
|
|
2645
|
+
this.sessions.delete(sessionId);
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Check if session exists
|
|
2649
|
+
*/
|
|
2650
|
+
async has(sessionId) {
|
|
2651
|
+
return this.sessions.has(sessionId);
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* List all session IDs
|
|
2655
|
+
*/
|
|
2656
|
+
async keys() {
|
|
2657
|
+
return Array.from(this.sessions.keys());
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Store session metadata with TTL (time-to-live)
|
|
2661
|
+
*
|
|
2662
|
+
* Note: In-memory implementation uses setTimeout for TTL.
|
|
2663
|
+
* For production TTL support, use Redis or another store with native TTL.
|
|
2664
|
+
*/
|
|
2665
|
+
async setWithTTL(sessionId, data, ttlMs) {
|
|
2666
|
+
this.sessions.set(sessionId, data);
|
|
2667
|
+
setTimeout(() => {
|
|
2668
|
+
this.sessions.delete(sessionId);
|
|
2669
|
+
console.log(`[MCP] Session ${sessionId} expired after ${ttlMs}ms`);
|
|
2670
|
+
}, ttlMs);
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Get the number of active sessions
|
|
2674
|
+
* Useful for monitoring and debugging
|
|
2675
|
+
*/
|
|
2676
|
+
get size() {
|
|
2677
|
+
return this.sessions.size;
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Clear all sessions
|
|
2681
|
+
* Useful for testing and manual cleanup
|
|
2682
|
+
*/
|
|
2683
|
+
async clear() {
|
|
2684
|
+
this.sessions.clear();
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
|
|
2688
|
+
// src/server/sessions/stores/redis.ts
|
|
2689
|
+
var RedisSessionStore = class {
|
|
2690
|
+
static {
|
|
2691
|
+
__name(this, "RedisSessionStore");
|
|
2692
|
+
}
|
|
2693
|
+
client;
|
|
2694
|
+
prefix;
|
|
2695
|
+
defaultTTL;
|
|
2696
|
+
constructor(config) {
|
|
2697
|
+
this.client = config.client;
|
|
2698
|
+
this.prefix = config.prefix ?? "mcp:session:";
|
|
2699
|
+
this.defaultTTL = config.defaultTTL ?? 3600;
|
|
2700
|
+
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Get full Redis key for a session ID
|
|
2703
|
+
*/
|
|
2704
|
+
getKey(sessionId) {
|
|
2705
|
+
return `${this.prefix}${sessionId}`;
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* Retrieve session metadata by ID
|
|
2709
|
+
*/
|
|
2710
|
+
async get(sessionId) {
|
|
2711
|
+
try {
|
|
2712
|
+
const key = this.getKey(sessionId);
|
|
2713
|
+
const data = await this.client.get(key);
|
|
2714
|
+
if (!data) {
|
|
2715
|
+
return null;
|
|
2716
|
+
}
|
|
2717
|
+
return JSON.parse(data);
|
|
2718
|
+
} catch (error2) {
|
|
2719
|
+
console.error(
|
|
2720
|
+
`[RedisSessionStore] Error getting session ${sessionId}:`,
|
|
2721
|
+
error2
|
|
2722
|
+
);
|
|
2723
|
+
return null;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Store or update session metadata
|
|
2728
|
+
*/
|
|
2729
|
+
async set(sessionId, data) {
|
|
2730
|
+
try {
|
|
2731
|
+
const key = this.getKey(sessionId);
|
|
2732
|
+
const value = JSON.stringify(data);
|
|
2733
|
+
if (this.client.setEx) {
|
|
2734
|
+
await this.client.setEx(key, this.defaultTTL, value);
|
|
2735
|
+
} else if (this.client.setex) {
|
|
2736
|
+
await this.client.setex(key, this.defaultTTL, value);
|
|
2737
|
+
} else {
|
|
2738
|
+
await this.client.set(key, value, { EX: this.defaultTTL });
|
|
2739
|
+
}
|
|
2740
|
+
} catch (error2) {
|
|
2741
|
+
console.error(
|
|
2742
|
+
`[RedisSessionStore] Error setting session ${sessionId}:`,
|
|
2743
|
+
error2
|
|
2744
|
+
);
|
|
2745
|
+
throw error2;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* Delete a session
|
|
2750
|
+
*/
|
|
2751
|
+
async delete(sessionId) {
|
|
2752
|
+
try {
|
|
2753
|
+
const key = this.getKey(sessionId);
|
|
2754
|
+
await this.client.del(key);
|
|
2755
|
+
} catch (error2) {
|
|
2756
|
+
console.error(
|
|
2757
|
+
`[RedisSessionStore] Error deleting session ${sessionId}:`,
|
|
2758
|
+
error2
|
|
2759
|
+
);
|
|
2760
|
+
throw error2;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Check if a session exists
|
|
2765
|
+
*/
|
|
2766
|
+
async has(sessionId) {
|
|
2767
|
+
try {
|
|
2768
|
+
const key = this.getKey(sessionId);
|
|
2769
|
+
const exists = await this.client.exists(key);
|
|
2770
|
+
return exists === 1;
|
|
2771
|
+
} catch (error2) {
|
|
2772
|
+
console.error(
|
|
2773
|
+
`[RedisSessionStore] Error checking session ${sessionId}:`,
|
|
2774
|
+
error2
|
|
2775
|
+
);
|
|
2776
|
+
return false;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* List all session IDs
|
|
2781
|
+
*
|
|
2782
|
+
* WARNING: Uses KEYS command which blocks Redis. For production systems with
|
|
2783
|
+
* many sessions, consider using SCAN instead or maintaining a separate SET of
|
|
2784
|
+
* active session IDs.
|
|
2785
|
+
*/
|
|
2786
|
+
async keys() {
|
|
2787
|
+
try {
|
|
2788
|
+
const pattern = `${this.prefix}*`;
|
|
2789
|
+
const keys = await this.client.keys(pattern);
|
|
2790
|
+
return keys.map((key) => key.substring(this.prefix.length));
|
|
2791
|
+
} catch (error2) {
|
|
2792
|
+
console.error("[RedisSessionStore] Error listing session keys:", error2);
|
|
2793
|
+
return [];
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Store session metadata with custom TTL (time-to-live)
|
|
2798
|
+
*/
|
|
2799
|
+
async setWithTTL(sessionId, data, ttlMs) {
|
|
2800
|
+
try {
|
|
2801
|
+
const key = this.getKey(sessionId);
|
|
2802
|
+
const value = JSON.stringify(data);
|
|
2803
|
+
const ttlSeconds = Math.ceil(ttlMs / 1e3);
|
|
2804
|
+
if (this.client.setEx) {
|
|
2805
|
+
await this.client.setEx(key, ttlSeconds, value);
|
|
2806
|
+
} else if (this.client.setex) {
|
|
2807
|
+
await this.client.setex(key, ttlSeconds, value);
|
|
2808
|
+
} else {
|
|
2809
|
+
await this.client.set(key, value, { EX: ttlSeconds });
|
|
2810
|
+
}
|
|
2811
|
+
} catch (error2) {
|
|
2812
|
+
console.error(
|
|
2813
|
+
`[RedisSessionStore] Error setting session ${sessionId} with TTL:`,
|
|
2814
|
+
error2
|
|
2815
|
+
);
|
|
2816
|
+
throw error2;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Close Redis connection
|
|
2821
|
+
* Should be called when shutting down the server
|
|
2822
|
+
*/
|
|
2823
|
+
async close() {
|
|
2824
|
+
try {
|
|
2825
|
+
await this.client.quit();
|
|
2826
|
+
} catch (error2) {
|
|
2827
|
+
console.error(
|
|
2828
|
+
"[RedisSessionStore] Error closing Redis connection:",
|
|
2829
|
+
error2
|
|
2830
|
+
);
|
|
2831
|
+
throw error2;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
/**
|
|
2835
|
+
* Clear all sessions (useful for testing)
|
|
2836
|
+
* WARNING: This will delete all sessions with the configured prefix
|
|
2837
|
+
*
|
|
2838
|
+
* NOTE: Uses KEYS command which blocks Redis. This is acceptable for testing
|
|
2839
|
+
* but should be avoided in production with large datasets.
|
|
2840
|
+
*/
|
|
2841
|
+
async clear() {
|
|
2842
|
+
try {
|
|
2843
|
+
const pattern = `${this.prefix}*`;
|
|
2844
|
+
const keys = await this.client.keys(pattern);
|
|
2845
|
+
if (keys.length > 0) {
|
|
2846
|
+
await this.client.del(keys);
|
|
2847
|
+
}
|
|
2848
|
+
} catch (error2) {
|
|
2849
|
+
console.error("[RedisSessionStore] Error clearing sessions:", error2);
|
|
2850
|
+
throw error2;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
|
|
2855
|
+
// src/server/sessions/stores/filesystem.ts
|
|
2856
|
+
import { mkdir, writeFile, rename, unlink } from "fs/promises";
|
|
2857
|
+
import { join, dirname } from "path";
|
|
2858
|
+
import { existsSync, readFileSync } from "fs";
|
|
2859
|
+
var FileSystemSessionStore = class {
|
|
2860
|
+
static {
|
|
2861
|
+
__name(this, "FileSystemSessionStore");
|
|
2862
|
+
}
|
|
2863
|
+
sessions = /* @__PURE__ */ new Map();
|
|
2864
|
+
filePath;
|
|
2865
|
+
debounceMs;
|
|
2866
|
+
maxAgeMs;
|
|
2867
|
+
saveTimer = null;
|
|
2868
|
+
saving = false;
|
|
2869
|
+
pendingSave = false;
|
|
2870
|
+
constructor(config = {}) {
|
|
2871
|
+
this.filePath = config.path ?? join(process.cwd(), ".mcp-use", "sessions.json");
|
|
2872
|
+
this.debounceMs = config.debounceMs ?? 100;
|
|
2873
|
+
this.maxAgeMs = config.maxAgeMs ?? 24 * 60 * 60 * 1e3;
|
|
2874
|
+
this.loadSessionsSync();
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Load sessions from file synchronously during construction
|
|
2878
|
+
* This ensures sessions are available immediately when the server starts
|
|
2879
|
+
*/
|
|
2880
|
+
loadSessionsSync() {
|
|
2881
|
+
try {
|
|
2882
|
+
if (!existsSync(this.filePath)) {
|
|
2883
|
+
console.log(
|
|
2884
|
+
`[FileSystemSessionStore] No session file found at ${this.filePath}, starting fresh`
|
|
2885
|
+
);
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
const data = readFileSync(this.filePath, "utf-8");
|
|
2889
|
+
const parsed = JSON.parse(data);
|
|
2890
|
+
const now = Date.now();
|
|
2891
|
+
let loadedCount = 0;
|
|
2892
|
+
let expiredCount = 0;
|
|
2893
|
+
for (const [sessionId, metadata] of Object.entries(parsed)) {
|
|
2894
|
+
const sessionMetadata = metadata;
|
|
2895
|
+
const age = now - sessionMetadata.lastAccessedAt;
|
|
2896
|
+
if (age > this.maxAgeMs) {
|
|
2897
|
+
expiredCount++;
|
|
2898
|
+
continue;
|
|
2899
|
+
}
|
|
2900
|
+
this.sessions.set(sessionId, sessionMetadata);
|
|
2901
|
+
loadedCount++;
|
|
2902
|
+
}
|
|
2903
|
+
console.log(
|
|
2904
|
+
`[FileSystemSessionStore] Loaded ${loadedCount} session(s) from ${this.filePath}` + (expiredCount > 0 ? ` (cleaned up ${expiredCount} expired)` : "")
|
|
2905
|
+
);
|
|
2906
|
+
} catch (error2) {
|
|
2907
|
+
if (error2.code === "ENOENT") {
|
|
2908
|
+
console.log(`[FileSystemSessionStore] No existing sessions file`);
|
|
2909
|
+
} else if (error2 instanceof SyntaxError) {
|
|
2910
|
+
console.warn(
|
|
2911
|
+
`[FileSystemSessionStore] Corrupted session file, starting fresh:`,
|
|
2912
|
+
error2.message
|
|
2913
|
+
);
|
|
2914
|
+
} else {
|
|
2915
|
+
console.warn(
|
|
2916
|
+
`[FileSystemSessionStore] Error loading sessions, starting fresh:`,
|
|
2917
|
+
error2.message
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Retrieve session metadata by ID
|
|
2924
|
+
*/
|
|
2925
|
+
async get(sessionId) {
|
|
2926
|
+
const data = this.sessions.get(sessionId);
|
|
2927
|
+
return data ?? null;
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* Store or update session metadata
|
|
2931
|
+
* Uses debouncing to batch rapid consecutive writes
|
|
2932
|
+
*/
|
|
2933
|
+
async set(sessionId, data) {
|
|
2934
|
+
this.sessions.set(sessionId, data);
|
|
2935
|
+
await this.scheduleSave();
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Delete session metadata
|
|
2939
|
+
*/
|
|
2940
|
+
async delete(sessionId) {
|
|
2941
|
+
this.sessions.delete(sessionId);
|
|
2942
|
+
await this.scheduleSave();
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Check if session exists
|
|
2946
|
+
*/
|
|
2947
|
+
async has(sessionId) {
|
|
2948
|
+
return this.sessions.has(sessionId);
|
|
2949
|
+
}
|
|
2950
|
+
/**
|
|
2951
|
+
* List all session IDs
|
|
2952
|
+
*/
|
|
2953
|
+
async keys() {
|
|
2954
|
+
return Array.from(this.sessions.keys());
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* Store session metadata with TTL
|
|
2958
|
+
* Note: TTL is enforced on load, not with timers (simple implementation)
|
|
2959
|
+
*/
|
|
2960
|
+
async setWithTTL(sessionId, data, ttlMs) {
|
|
2961
|
+
const metadataWithExpiry = {
|
|
2962
|
+
...data,
|
|
2963
|
+
lastAccessedAt: Date.now()
|
|
2964
|
+
};
|
|
2965
|
+
this.sessions.set(sessionId, metadataWithExpiry);
|
|
2966
|
+
await this.scheduleSave();
|
|
2967
|
+
setTimeout(() => {
|
|
2968
|
+
this.sessions.delete(sessionId);
|
|
2969
|
+
this.scheduleSave();
|
|
2970
|
+
}, ttlMs);
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Get the number of active sessions
|
|
2974
|
+
*/
|
|
2975
|
+
get size() {
|
|
2976
|
+
return this.sessions.size;
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Clear all sessions
|
|
2980
|
+
*/
|
|
2981
|
+
async clear() {
|
|
2982
|
+
this.sessions.clear();
|
|
2983
|
+
await this.scheduleSave();
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Schedule a save operation with debouncing
|
|
2987
|
+
* Prevents excessive disk I/O from rapid consecutive writes
|
|
2988
|
+
*/
|
|
2989
|
+
async scheduleSave() {
|
|
2990
|
+
if (this.saving) {
|
|
2991
|
+
this.pendingSave = true;
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
if (this.saveTimer) {
|
|
2995
|
+
clearTimeout(this.saveTimer);
|
|
2996
|
+
}
|
|
2997
|
+
this.saveTimer = setTimeout(() => {
|
|
2998
|
+
this.performSave();
|
|
2999
|
+
}, this.debounceMs);
|
|
3000
|
+
}
|
|
3001
|
+
/**
|
|
3002
|
+
* Perform the actual save operation with atomic writes
|
|
3003
|
+
* Uses write-to-temp-then-rename pattern to prevent corruption
|
|
3004
|
+
*/
|
|
3005
|
+
async performSave() {
|
|
3006
|
+
this.saveTimer = null;
|
|
3007
|
+
this.saving = true;
|
|
3008
|
+
this.pendingSave = false;
|
|
3009
|
+
try {
|
|
3010
|
+
const dir = dirname(this.filePath);
|
|
3011
|
+
await mkdir(dir, { recursive: true });
|
|
3012
|
+
const data = {};
|
|
3013
|
+
for (const [sessionId, metadata] of Array.from(this.sessions.entries())) {
|
|
3014
|
+
data[sessionId] = metadata;
|
|
3015
|
+
}
|
|
3016
|
+
const tempPath = `${this.filePath}.tmp`;
|
|
3017
|
+
await writeFile(tempPath, JSON.stringify(data, null, 2), "utf-8");
|
|
3018
|
+
await rename(tempPath, this.filePath);
|
|
3019
|
+
console.debug(
|
|
3020
|
+
`[FileSystemSessionStore] Saved ${this.sessions.size} session(s) to ${this.filePath}`
|
|
3021
|
+
);
|
|
3022
|
+
} catch (error2) {
|
|
3023
|
+
console.error(
|
|
3024
|
+
`[FileSystemSessionStore] Error saving sessions:`,
|
|
3025
|
+
error2.message
|
|
3026
|
+
);
|
|
3027
|
+
try {
|
|
3028
|
+
const tempPath = `${this.filePath}.tmp`;
|
|
3029
|
+
if (existsSync(tempPath)) {
|
|
3030
|
+
await unlink(tempPath);
|
|
3031
|
+
}
|
|
3032
|
+
} catch {
|
|
3033
|
+
}
|
|
3034
|
+
} finally {
|
|
3035
|
+
this.saving = false;
|
|
3036
|
+
if (this.pendingSave) {
|
|
3037
|
+
await this.scheduleSave();
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Force an immediate save (bypasses debouncing)
|
|
3043
|
+
* Useful for ensuring persistence before process exit
|
|
3044
|
+
*/
|
|
3045
|
+
async flush() {
|
|
3046
|
+
if (this.saveTimer) {
|
|
3047
|
+
clearTimeout(this.saveTimer);
|
|
3048
|
+
this.saveTimer = null;
|
|
3049
|
+
}
|
|
3050
|
+
await this.performSave();
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
|
|
3054
|
+
// src/server/sessions/streams/memory.ts
|
|
3055
|
+
var InMemoryStreamManager = class {
|
|
3056
|
+
static {
|
|
3057
|
+
__name(this, "InMemoryStreamManager");
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Map of active SSE stream controllers
|
|
3061
|
+
* Key: sessionId, Value: ReadableStreamDefaultController
|
|
3062
|
+
*/
|
|
3063
|
+
streams = /* @__PURE__ */ new Map();
|
|
3064
|
+
/**
|
|
3065
|
+
* Text encoder for converting strings to Uint8Array
|
|
3066
|
+
*/
|
|
3067
|
+
textEncoder = new TextEncoder();
|
|
3068
|
+
/**
|
|
3069
|
+
* Register an active SSE stream controller
|
|
3070
|
+
*/
|
|
3071
|
+
async create(sessionId, controller) {
|
|
3072
|
+
this.streams.set(sessionId, controller);
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Send data to active SSE streams
|
|
3076
|
+
*
|
|
3077
|
+
* Directly enqueues data to in-memory controllers.
|
|
3078
|
+
* For distributed deployments, use RedisStreamManager instead.
|
|
3079
|
+
*/
|
|
3080
|
+
async send(sessionIds, data) {
|
|
3081
|
+
const encoded = this.textEncoder.encode(data);
|
|
3082
|
+
if (!sessionIds) {
|
|
3083
|
+
for (const [_id, controller] of this.streams.entries()) {
|
|
3084
|
+
try {
|
|
3085
|
+
controller.enqueue(encoded);
|
|
3086
|
+
} catch (error2) {
|
|
3087
|
+
console.warn(
|
|
3088
|
+
`[InMemoryStreamManager] Failed to send to session ${_id}:`,
|
|
3089
|
+
error2
|
|
3090
|
+
);
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
} else {
|
|
3094
|
+
for (const sessionId of sessionIds) {
|
|
3095
|
+
const controller = this.streams.get(sessionId);
|
|
3096
|
+
if (controller) {
|
|
3097
|
+
try {
|
|
3098
|
+
controller.enqueue(encoded);
|
|
3099
|
+
} catch (error2) {
|
|
3100
|
+
console.warn(
|
|
3101
|
+
`[InMemoryStreamManager] Failed to send to session ${sessionId}:`,
|
|
3102
|
+
error2
|
|
3103
|
+
);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Remove an active SSE stream
|
|
3111
|
+
*/
|
|
3112
|
+
async delete(sessionId) {
|
|
3113
|
+
const controller = this.streams.get(sessionId);
|
|
3114
|
+
if (controller) {
|
|
3115
|
+
try {
|
|
3116
|
+
controller.close();
|
|
3117
|
+
} catch (error2) {
|
|
3118
|
+
console.debug(
|
|
3119
|
+
`[InMemoryStreamManager] Controller already closed for session ${sessionId}`
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
this.streams.delete(sessionId);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Check if an active stream exists
|
|
3127
|
+
*/
|
|
3128
|
+
async has(sessionId) {
|
|
3129
|
+
return this.streams.has(sessionId);
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* Close all active streams
|
|
3133
|
+
*/
|
|
3134
|
+
async close() {
|
|
3135
|
+
for (const [sessionId, controller] of this.streams.entries()) {
|
|
3136
|
+
try {
|
|
3137
|
+
controller.close();
|
|
3138
|
+
} catch (error2) {
|
|
3139
|
+
console.debug(
|
|
3140
|
+
`[InMemoryStreamManager] Error closing stream for ${sessionId}:`,
|
|
3141
|
+
error2
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
this.streams.clear();
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Get the number of active streams
|
|
3149
|
+
* Useful for monitoring
|
|
3150
|
+
*/
|
|
3151
|
+
get size() {
|
|
3152
|
+
return this.streams.size;
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
|
|
3156
|
+
// src/server/sessions/streams/redis.ts
|
|
3157
|
+
var RedisStreamManager = class {
|
|
3158
|
+
static {
|
|
3159
|
+
__name(this, "RedisStreamManager");
|
|
3160
|
+
}
|
|
3161
|
+
pubSubClient;
|
|
3162
|
+
client;
|
|
3163
|
+
prefix;
|
|
3164
|
+
heartbeatInterval;
|
|
3165
|
+
textEncoder = new TextEncoder();
|
|
3166
|
+
/**
|
|
3167
|
+
* Map of local controllers (only on this server instance)
|
|
3168
|
+
* Key: sessionId, Value: controller
|
|
3169
|
+
*/
|
|
3170
|
+
localControllers = /* @__PURE__ */ new Map();
|
|
3171
|
+
/**
|
|
3172
|
+
* Map of heartbeat intervals for keeping sessions alive
|
|
3173
|
+
* Key: sessionId, Value: interval timer
|
|
3174
|
+
*/
|
|
3175
|
+
heartbeats = /* @__PURE__ */ new Map();
|
|
3176
|
+
constructor(config) {
|
|
3177
|
+
this.pubSubClient = config.pubSubClient;
|
|
3178
|
+
this.client = config.client;
|
|
3179
|
+
this.prefix = config.prefix ?? "mcp:stream:";
|
|
3180
|
+
this.heartbeatInterval = config.heartbeatInterval ?? 10;
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Get the Redis channel name for a session
|
|
3184
|
+
*/
|
|
3185
|
+
getChannel(sessionId) {
|
|
3186
|
+
return `${this.prefix}${sessionId}`;
|
|
3187
|
+
}
|
|
3188
|
+
/**
|
|
3189
|
+
* Get the Redis key for tracking active sessions
|
|
3190
|
+
*/
|
|
3191
|
+
getAvailableKey(sessionId) {
|
|
3192
|
+
return `available:${this.prefix}${sessionId}`;
|
|
3193
|
+
}
|
|
3194
|
+
/**
|
|
3195
|
+
* Get the Redis key for the active sessions SET
|
|
3196
|
+
*/
|
|
3197
|
+
getActiveSessionsKey() {
|
|
3198
|
+
return `${this.prefix}active`;
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Register an active SSE stream and subscribe to Redis channel
|
|
3202
|
+
*/
|
|
3203
|
+
async create(sessionId, controller) {
|
|
3204
|
+
try {
|
|
3205
|
+
this.localControllers.set(sessionId, controller);
|
|
3206
|
+
const availableKey = this.getAvailableKey(sessionId);
|
|
3207
|
+
await this.client.set(availableKey, "active");
|
|
3208
|
+
if (this.client.expire) {
|
|
3209
|
+
await this.client.expire(availableKey, this.heartbeatInterval * 2);
|
|
3210
|
+
}
|
|
3211
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
3212
|
+
if (this.client.sAdd) {
|
|
3213
|
+
await this.client.sAdd(activeSessionsKey, sessionId);
|
|
3214
|
+
if (this.client.expire) {
|
|
3215
|
+
await this.client.expire(
|
|
3216
|
+
activeSessionsKey,
|
|
3217
|
+
this.heartbeatInterval * 2
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
const heartbeat = setInterval(async () => {
|
|
3222
|
+
try {
|
|
3223
|
+
if (this.client.expire) {
|
|
3224
|
+
await this.client.expire(availableKey, this.heartbeatInterval * 2);
|
|
3225
|
+
const activeSessionsKey2 = this.getActiveSessionsKey();
|
|
3226
|
+
await this.client.expire(
|
|
3227
|
+
activeSessionsKey2,
|
|
3228
|
+
this.heartbeatInterval * 2
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
} catch (error2) {
|
|
3232
|
+
console.warn(
|
|
3233
|
+
`[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
|
|
3234
|
+
error2
|
|
3235
|
+
);
|
|
3236
|
+
}
|
|
3237
|
+
}, this.heartbeatInterval * 1e3);
|
|
3238
|
+
this.heartbeats.set(sessionId, heartbeat);
|
|
3239
|
+
const channel = this.getChannel(sessionId);
|
|
3240
|
+
if (!this.pubSubClient.subscribe) {
|
|
3241
|
+
throw new Error(
|
|
3242
|
+
"[RedisStreamManager] Redis client does not support subscribe method"
|
|
3243
|
+
);
|
|
3244
|
+
}
|
|
3245
|
+
await this.pubSubClient.subscribe(channel, (message) => {
|
|
3246
|
+
const localController = this.localControllers.get(sessionId);
|
|
3247
|
+
if (localController) {
|
|
3248
|
+
try {
|
|
3249
|
+
localController.enqueue(this.textEncoder.encode(message));
|
|
3250
|
+
} catch (error2) {
|
|
3251
|
+
console.warn(
|
|
3252
|
+
`[RedisStreamManager] Failed to enqueue message for ${sessionId}:`,
|
|
3253
|
+
error2
|
|
3254
|
+
);
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
});
|
|
3258
|
+
const deleteChannel = `delete:${this.getChannel(sessionId)}`;
|
|
3259
|
+
await this.pubSubClient.subscribe(deleteChannel, async () => {
|
|
3260
|
+
await this.delete(sessionId);
|
|
3261
|
+
});
|
|
3262
|
+
console.log(
|
|
3263
|
+
`[RedisStreamManager] Created stream for session ${sessionId}`
|
|
3264
|
+
);
|
|
3265
|
+
} catch (error2) {
|
|
3266
|
+
console.error(
|
|
3267
|
+
`[RedisStreamManager] Error creating stream for ${sessionId}:`,
|
|
3268
|
+
error2
|
|
3269
|
+
);
|
|
3270
|
+
throw error2;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Send data to sessions via Redis Pub/Sub
|
|
3275
|
+
*
|
|
3276
|
+
* This works across distributed servers - any server with an active
|
|
3277
|
+
* SSE connection for the target session will receive and forward the message.
|
|
3278
|
+
*
|
|
3279
|
+
* Note: Uses the regular client (not pubSubClient) for publishing.
|
|
3280
|
+
* In node-redis v5+, clients in subscriber mode cannot publish.
|
|
3281
|
+
*/
|
|
3282
|
+
async send(sessionIds, data) {
|
|
3283
|
+
try {
|
|
3284
|
+
if (!sessionIds) {
|
|
3285
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
3286
|
+
if (this.client.sMembers) {
|
|
3287
|
+
const sessionIds2 = await this.client.sMembers(activeSessionsKey);
|
|
3288
|
+
for (const sessionId of sessionIds2) {
|
|
3289
|
+
const channel = this.getChannel(sessionId);
|
|
3290
|
+
if (!this.client.publish) {
|
|
3291
|
+
throw new Error(
|
|
3292
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
await this.client.publish(channel, data);
|
|
3296
|
+
}
|
|
3297
|
+
} else {
|
|
3298
|
+
const pattern = `available:${this.prefix}*`;
|
|
3299
|
+
const keys = await this.client.keys(pattern);
|
|
3300
|
+
for (const key of keys) {
|
|
3301
|
+
const sessionId = key.replace(`available:${this.prefix}`, "");
|
|
3302
|
+
const channel = this.getChannel(sessionId);
|
|
3303
|
+
if (!this.client.publish) {
|
|
3304
|
+
throw new Error(
|
|
3305
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
3306
|
+
);
|
|
3307
|
+
}
|
|
3308
|
+
await this.client.publish(channel, data);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
} else {
|
|
3312
|
+
for (const sessionId of sessionIds) {
|
|
3313
|
+
const channel = this.getChannel(sessionId);
|
|
3314
|
+
if (!this.client.publish) {
|
|
3315
|
+
throw new Error(
|
|
3316
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
await this.client.publish(channel, data);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
} catch (error2) {
|
|
3323
|
+
console.error(`[RedisStreamManager] Error sending to sessions:`, error2);
|
|
3324
|
+
throw error2;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Remove an active SSE stream
|
|
3329
|
+
*/
|
|
3330
|
+
async delete(sessionId) {
|
|
3331
|
+
try {
|
|
3332
|
+
const heartbeat = this.heartbeats.get(sessionId);
|
|
3333
|
+
if (heartbeat) {
|
|
3334
|
+
clearInterval(heartbeat);
|
|
3335
|
+
this.heartbeats.delete(sessionId);
|
|
3336
|
+
}
|
|
3337
|
+
const channel = this.getChannel(sessionId);
|
|
3338
|
+
const deleteChannel = `delete:${channel}`;
|
|
3339
|
+
if (!this.pubSubClient.unsubscribe) {
|
|
3340
|
+
throw new Error(
|
|
3341
|
+
"[RedisStreamManager] Redis client does not support unsubscribe method"
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
await this.pubSubClient.unsubscribe(channel);
|
|
3345
|
+
await this.pubSubClient.unsubscribe(deleteChannel);
|
|
3346
|
+
if (!this.client.publish) {
|
|
3347
|
+
throw new Error(
|
|
3348
|
+
"[RedisStreamManager] Redis client does not support publish method"
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
await this.client.publish(deleteChannel, "");
|
|
3352
|
+
await this.client.del(this.getAvailableKey(sessionId));
|
|
3353
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
3354
|
+
if (this.client.sRem) {
|
|
3355
|
+
await this.client.sRem(activeSessionsKey, sessionId);
|
|
3356
|
+
}
|
|
3357
|
+
const controller = this.localControllers.get(sessionId);
|
|
3358
|
+
if (controller) {
|
|
3359
|
+
try {
|
|
3360
|
+
controller.close();
|
|
3361
|
+
} catch (error2) {
|
|
3362
|
+
console.debug(
|
|
3363
|
+
`[RedisStreamManager] Controller already closed for ${sessionId}`
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
this.localControllers.delete(sessionId);
|
|
3367
|
+
}
|
|
3368
|
+
console.log(
|
|
3369
|
+
`[RedisStreamManager] Deleted stream for session ${sessionId}`
|
|
3370
|
+
);
|
|
3371
|
+
} catch (error2) {
|
|
3372
|
+
console.error(
|
|
3373
|
+
`[RedisStreamManager] Error deleting stream for ${sessionId}:`,
|
|
3374
|
+
error2
|
|
3375
|
+
);
|
|
3376
|
+
throw error2;
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Check if a session has an active stream (on ANY server)
|
|
3381
|
+
*/
|
|
3382
|
+
async has(sessionId) {
|
|
3383
|
+
try {
|
|
3384
|
+
const availableKey = this.getAvailableKey(sessionId);
|
|
3385
|
+
const exists = await this.client.exists(availableKey);
|
|
3386
|
+
return exists === 1;
|
|
3387
|
+
} catch (error2) {
|
|
3388
|
+
console.error(
|
|
3389
|
+
`[RedisStreamManager] Error checking session ${sessionId}:`,
|
|
3390
|
+
error2
|
|
3391
|
+
);
|
|
3392
|
+
return false;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Close all connections and cleanup
|
|
3397
|
+
*/
|
|
3398
|
+
async close() {
|
|
3399
|
+
try {
|
|
3400
|
+
for (const heartbeat of this.heartbeats.values()) {
|
|
3401
|
+
clearInterval(heartbeat);
|
|
3402
|
+
}
|
|
3403
|
+
this.heartbeats.clear();
|
|
3404
|
+
const activeSessionsKey = this.getActiveSessionsKey();
|
|
3405
|
+
const sessionIdsToCleanup = Array.from(this.localControllers.keys());
|
|
3406
|
+
for (const sessionId of sessionIdsToCleanup) {
|
|
3407
|
+
await this.client.del(this.getAvailableKey(sessionId));
|
|
3408
|
+
if (this.client.sRem) {
|
|
3409
|
+
await this.client.sRem(activeSessionsKey, sessionId);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
for (const controller of this.localControllers.values()) {
|
|
3413
|
+
try {
|
|
3414
|
+
controller.close();
|
|
3415
|
+
} catch (error2) {
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
this.localControllers.clear();
|
|
3419
|
+
console.log(`[RedisStreamManager] Closed all streams`);
|
|
3420
|
+
} catch (error2) {
|
|
3421
|
+
console.error(`[RedisStreamManager] Error during close:`, error2);
|
|
3422
|
+
throw error2;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
3426
|
+
* Get count of active local streams on this server instance
|
|
3427
|
+
*/
|
|
3428
|
+
get localSize() {
|
|
3429
|
+
return this.localControllers.size;
|
|
3430
|
+
}
|
|
3431
|
+
};
|
|
3432
|
+
|
|
2539
3433
|
// src/server/endpoints/mount-mcp.ts
|
|
3434
|
+
import { join as join2 } from "path";
|
|
2540
3435
|
async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMode2) {
|
|
2541
3436
|
const { FetchStreamableHTTPServerTransport } = await import("@mcp-use/modelcontextprotocol-sdk/experimental/fetch-streamable-http/index.js");
|
|
2542
3437
|
const idleTimeoutMs = config.sessionIdleTimeoutMs ?? 3e5;
|
|
3438
|
+
const sessionStore = config.sessionStore ?? (isProductionMode2 ? new InMemorySessionStore() : new FileSystemSessionStore({
|
|
3439
|
+
path: join2(process.cwd(), ".mcp-use", "sessions.json")
|
|
3440
|
+
}));
|
|
3441
|
+
const streamManager = config.streamManager ?? new InMemoryStreamManager();
|
|
2543
3442
|
const transports = /* @__PURE__ */ new Map();
|
|
3443
|
+
if (config.autoCreateSessionOnInvalidId !== void 0) {
|
|
3444
|
+
console.warn(
|
|
3445
|
+
"[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"
|
|
3446
|
+
);
|
|
3447
|
+
}
|
|
2544
3448
|
let idleCleanupInterval;
|
|
2545
3449
|
if (!config.stateless && idleTimeoutMs > 0) {
|
|
2546
3450
|
idleCleanupInterval = startIdleCleanup(
|
|
@@ -2551,15 +3455,34 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
|
|
|
2551
3455
|
);
|
|
2552
3456
|
}
|
|
2553
3457
|
const handleRequest = /* @__PURE__ */ __name(async (c) => {
|
|
2554
|
-
|
|
3458
|
+
const acceptHeader = c.req.header("Accept") || c.req.header("accept") || "";
|
|
3459
|
+
const clientSupportsSSE = acceptHeader.includes("text/event-stream");
|
|
3460
|
+
const useStatelessMode = config.stateless || !clientSupportsSSE;
|
|
3461
|
+
if (useStatelessMode) {
|
|
2555
3462
|
const server = mcpServerInstance.getServerForSession();
|
|
2556
3463
|
const transport = new FetchStreamableHTTPServerTransport({
|
|
2557
|
-
sessionIdGenerator: void 0
|
|
3464
|
+
sessionIdGenerator: void 0,
|
|
2558
3465
|
// No session tracking
|
|
3466
|
+
// Enable plain JSON responses ONLY if client doesn't support SSE
|
|
3467
|
+
// This allows k6/curl to work while maintaining SSE format for compatible clients
|
|
3468
|
+
enableJsonResponse: !clientSupportsSSE
|
|
2559
3469
|
});
|
|
2560
3470
|
try {
|
|
2561
3471
|
await server.connect(transport);
|
|
2562
|
-
|
|
3472
|
+
const request = c.req.raw;
|
|
3473
|
+
if (!clientSupportsSSE) {
|
|
3474
|
+
const modifiedRequest = new Request(request.url, {
|
|
3475
|
+
method: request.method,
|
|
3476
|
+
headers: {
|
|
3477
|
+
...Object.fromEntries(request.headers.entries()),
|
|
3478
|
+
Accept: "application/json, text/event-stream"
|
|
3479
|
+
},
|
|
3480
|
+
body: request.body,
|
|
3481
|
+
...request.body && { duplex: "half" }
|
|
3482
|
+
});
|
|
3483
|
+
return await transport.handleRequest(modifiedRequest);
|
|
3484
|
+
}
|
|
3485
|
+
return await transport.handleRequest(request);
|
|
2563
3486
|
} catch (error2) {
|
|
2564
3487
|
console.error("[MCP] Stateless request error:", error2);
|
|
2565
3488
|
transport.close();
|
|
@@ -2569,46 +3492,165 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
|
|
|
2569
3492
|
} else {
|
|
2570
3493
|
const sessionId = c.req.header("mcp-session-id");
|
|
2571
3494
|
if (c.req.method === "HEAD") {
|
|
2572
|
-
if (sessionId &&
|
|
2573
|
-
|
|
3495
|
+
if (sessionId && await sessionStore.has(sessionId)) {
|
|
3496
|
+
const session = await sessionStore.get(sessionId);
|
|
3497
|
+
if (session) {
|
|
3498
|
+
session.lastAccessedAt = Date.now();
|
|
3499
|
+
await sessionStore.set(sessionId, session);
|
|
3500
|
+
}
|
|
2574
3501
|
}
|
|
2575
3502
|
return new Response(null, { status: 200 });
|
|
2576
3503
|
}
|
|
3504
|
+
if (sessionId && await sessionStore.has(sessionId) && !transports.has(sessionId)) {
|
|
3505
|
+
console.log(
|
|
3506
|
+
`[MCP] Session metadata found but transport lost (likely hot reload): ${sessionId} - recreating transport`
|
|
3507
|
+
);
|
|
3508
|
+
const server2 = mcpServerInstance.getServerForSession();
|
|
3509
|
+
const transport2 = new FetchStreamableHTTPServerTransport({
|
|
3510
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => sessionId, "sessionIdGenerator"),
|
|
3511
|
+
// Reuse existing session ID
|
|
3512
|
+
onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
|
|
3513
|
+
console.log(`[MCP] Session reconnected: ${sid}`);
|
|
3514
|
+
transports.set(sid, transport2);
|
|
3515
|
+
const metadata = await sessionStore.get(sid);
|
|
3516
|
+
const sessionData = {
|
|
3517
|
+
transport: transport2,
|
|
3518
|
+
server: server2,
|
|
3519
|
+
lastAccessedAt: Date.now(),
|
|
3520
|
+
context: c,
|
|
3521
|
+
honoContext: c,
|
|
3522
|
+
...metadata || {}
|
|
3523
|
+
};
|
|
3524
|
+
sessions.set(sid, sessionData);
|
|
3525
|
+
server2.server.oninitialized = async () => {
|
|
3526
|
+
const clientCapabilities = server2.server.getClientCapabilities();
|
|
3527
|
+
const clientInfo = server2.server.getClientInfo?.() || {};
|
|
3528
|
+
const protocolVersion = server2.server.getProtocolVersion?.() || "unknown";
|
|
3529
|
+
const metadata2 = await sessionStore.get(sid);
|
|
3530
|
+
if (metadata2) {
|
|
3531
|
+
metadata2.clientCapabilities = clientCapabilities;
|
|
3532
|
+
metadata2.clientInfo = clientInfo;
|
|
3533
|
+
metadata2.protocolVersion = String(protocolVersion);
|
|
3534
|
+
await sessionStore.set(sid, metadata2);
|
|
3535
|
+
}
|
|
3536
|
+
const sessionData2 = sessions.get(sid);
|
|
3537
|
+
if (sessionData2) {
|
|
3538
|
+
sessionData2.clientCapabilities = clientCapabilities;
|
|
3539
|
+
}
|
|
3540
|
+
Telemetry.getInstance().trackServerInitialize({
|
|
3541
|
+
protocolVersion: String(protocolVersion),
|
|
3542
|
+
clientInfo: clientInfo || {},
|
|
3543
|
+
clientCapabilities: clientCapabilities || {},
|
|
3544
|
+
sessionId: sid
|
|
3545
|
+
}).catch(
|
|
3546
|
+
(e) => console.debug(`Failed to track server initialize: ${e}`)
|
|
3547
|
+
);
|
|
3548
|
+
if (!isProductionMode2) {
|
|
3549
|
+
console.log(
|
|
3550
|
+
`[MCP] Development mode: Sending list_changed notifications to reconnected session ${sid}`
|
|
3551
|
+
);
|
|
3552
|
+
try {
|
|
3553
|
+
const { sendNotificationToSession: sendNotificationToSession3 } = await import("../../notifications-FLGIFS56.js");
|
|
3554
|
+
await sendNotificationToSession3(
|
|
3555
|
+
sessions,
|
|
3556
|
+
sid,
|
|
3557
|
+
"notifications/tools/list_changed"
|
|
3558
|
+
);
|
|
3559
|
+
await sendNotificationToSession3(
|
|
3560
|
+
sessions,
|
|
3561
|
+
sid,
|
|
3562
|
+
"notifications/resources/list_changed"
|
|
3563
|
+
);
|
|
3564
|
+
await sendNotificationToSession3(
|
|
3565
|
+
sessions,
|
|
3566
|
+
sid,
|
|
3567
|
+
"notifications/prompts/list_changed"
|
|
3568
|
+
);
|
|
3569
|
+
} catch (err) {
|
|
3570
|
+
console.debug(
|
|
3571
|
+
`[MCP] Failed to send list_changed notification:`,
|
|
3572
|
+
err
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
}, "onsessioninitialized"),
|
|
3578
|
+
onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
|
|
3579
|
+
console.log(`[MCP] Session closed: ${sid}`);
|
|
3580
|
+
transports.delete(sid);
|
|
3581
|
+
await streamManager.delete(sid);
|
|
3582
|
+
await sessionStore.delete(sid);
|
|
3583
|
+
sessions.delete(sid);
|
|
3584
|
+
mcpServerInstance.cleanupSessionSubscriptions?.(sid);
|
|
3585
|
+
}, "onsessionclosed")
|
|
3586
|
+
});
|
|
3587
|
+
await server2.connect(transport2);
|
|
3588
|
+
return transport2.handleRequest(c.req.raw);
|
|
3589
|
+
}
|
|
3590
|
+
if (sessionId && !await sessionStore.has(sessionId)) {
|
|
3591
|
+
console.log(
|
|
3592
|
+
`[MCP] Session not found: ${sessionId} - returning 404 (client should re-initialize)`
|
|
3593
|
+
);
|
|
3594
|
+
return c.json(
|
|
3595
|
+
{
|
|
3596
|
+
jsonrpc: "2.0",
|
|
3597
|
+
error: { code: -32001, message: "Session not found" },
|
|
3598
|
+
id: null
|
|
3599
|
+
},
|
|
3600
|
+
404
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
2577
3603
|
if (sessionId && transports.has(sessionId)) {
|
|
2578
3604
|
const transport2 = transports.get(sessionId);
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
3605
|
+
const metadata = await sessionStore.get(sessionId);
|
|
3606
|
+
if (metadata) {
|
|
3607
|
+
metadata.lastAccessedAt = Date.now();
|
|
3608
|
+
await sessionStore.set(sessionId, metadata);
|
|
3609
|
+
}
|
|
3610
|
+
const sessionData = sessions.get(sessionId);
|
|
3611
|
+
if (sessionData) {
|
|
3612
|
+
sessionData.lastAccessedAt = Date.now();
|
|
3613
|
+
sessionData.context = c;
|
|
3614
|
+
sessionData.honoContext = c;
|
|
2584
3615
|
}
|
|
2585
3616
|
return transport2.handleRequest(c.req.raw);
|
|
2586
3617
|
}
|
|
2587
3618
|
const server = mcpServerInstance.getServerForSession();
|
|
2588
3619
|
const transport = new FetchStreamableHTTPServerTransport({
|
|
2589
3620
|
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
2590
|
-
onsessioninitialized: /* @__PURE__ */ __name((sid) => {
|
|
3621
|
+
onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
|
|
2591
3622
|
console.log(`[MCP] Session initialized: ${sid}`);
|
|
2592
3623
|
transports.set(sid, transport);
|
|
2593
|
-
|
|
3624
|
+
const sessionData = {
|
|
2594
3625
|
transport,
|
|
2595
3626
|
server,
|
|
2596
3627
|
lastAccessedAt: Date.now(),
|
|
2597
3628
|
context: c,
|
|
2598
3629
|
honoContext: c
|
|
3630
|
+
};
|
|
3631
|
+
sessions.set(sid, sessionData);
|
|
3632
|
+
await sessionStore.set(sid, {
|
|
3633
|
+
lastAccessedAt: Date.now()
|
|
2599
3634
|
});
|
|
2600
|
-
server.server.oninitialized = () => {
|
|
3635
|
+
server.server.oninitialized = async () => {
|
|
2601
3636
|
const clientCapabilities = server.server.getClientCapabilities();
|
|
2602
3637
|
const clientInfo = server.server.getClientInfo?.() || {};
|
|
2603
3638
|
const protocolVersion = server.server.getProtocolVersion?.() || "unknown";
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
3639
|
+
const metadata = await sessionStore.get(sid);
|
|
3640
|
+
if (metadata) {
|
|
3641
|
+
metadata.clientCapabilities = clientCapabilities;
|
|
3642
|
+
metadata.clientInfo = clientInfo;
|
|
3643
|
+
metadata.protocolVersion = String(protocolVersion);
|
|
3644
|
+
await sessionStore.set(sid, metadata);
|
|
2607
3645
|
console.log(
|
|
2608
3646
|
`[MCP] Captured client capabilities for session ${sid}:`,
|
|
2609
|
-
Object.keys(clientCapabilities)
|
|
3647
|
+
clientCapabilities ? Object.keys(clientCapabilities) : "none"
|
|
2610
3648
|
);
|
|
2611
3649
|
}
|
|
3650
|
+
const sessionData2 = sessions.get(sid);
|
|
3651
|
+
if (sessionData2) {
|
|
3652
|
+
sessionData2.clientCapabilities = clientCapabilities;
|
|
3653
|
+
}
|
|
2612
3654
|
Telemetry.getInstance().trackServerInitialize({
|
|
2613
3655
|
protocolVersion: String(protocolVersion),
|
|
2614
3656
|
clientInfo: clientInfo || {},
|
|
@@ -2619,9 +3661,11 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
|
|
|
2619
3661
|
);
|
|
2620
3662
|
};
|
|
2621
3663
|
}, "onsessioninitialized"),
|
|
2622
|
-
onsessionclosed: /* @__PURE__ */ __name((sid) => {
|
|
3664
|
+
onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
|
|
2623
3665
|
console.log(`[MCP] Session closed: ${sid}`);
|
|
2624
3666
|
transports.delete(sid);
|
|
3667
|
+
await streamManager.delete(sid);
|
|
3668
|
+
await sessionStore.delete(sid);
|
|
2625
3669
|
sessions.delete(sid);
|
|
2626
3670
|
mcpServerInstance.cleanupSessionSubscriptions?.(sid);
|
|
2627
3671
|
}, "onsessionclosed")
|
|
@@ -2943,6 +3987,7 @@ var MCPServerClass = class {
|
|
|
2943
3987
|
serverPort;
|
|
2944
3988
|
serverHost;
|
|
2945
3989
|
serverBaseUrl;
|
|
3990
|
+
favicon;
|
|
2946
3991
|
registeredTools = [];
|
|
2947
3992
|
registeredPrompts = [];
|
|
2948
3993
|
registeredResources = [];
|
|
@@ -3008,6 +4053,7 @@ var MCPServerClass = class {
|
|
|
3008
4053
|
}
|
|
3009
4054
|
this.serverHost = config.host || "localhost";
|
|
3010
4055
|
this.serverBaseUrl = config.baseUrl;
|
|
4056
|
+
this.favicon = config.favicon;
|
|
3011
4057
|
this.nativeServer = new OfficialMcpServer(
|
|
3012
4058
|
{
|
|
3013
4059
|
name: config.name,
|
|
@@ -3072,7 +4118,10 @@ var MCPServerClass = class {
|
|
|
3072
4118
|
"openai/widgetAccessible": widgetConfig.widgetAccessible ?? true,
|
|
3073
4119
|
"openai/resultCanProduceWidget": widgetConfig.resultCanProduceWidget ?? true
|
|
3074
4120
|
};
|
|
3075
|
-
result._meta =
|
|
4121
|
+
result._meta = {
|
|
4122
|
+
...result._meta || {},
|
|
4123
|
+
...responseMeta
|
|
4124
|
+
};
|
|
3076
4125
|
if (result.content?.[0]?.type === "text" && !result.content[0].text) {
|
|
3077
4126
|
result.content[0].text = `Displaying ${widgetName}`;
|
|
3078
4127
|
}
|
|
@@ -3475,6 +4524,9 @@ var MCPServerClass = class {
|
|
|
3475
4524
|
getActiveSessions = getActiveSessions;
|
|
3476
4525
|
sendNotification = sendNotification;
|
|
3477
4526
|
sendNotificationToSession = sendNotificationToSession2;
|
|
4527
|
+
sendToolsListChanged = sendToolsListChanged;
|
|
4528
|
+
sendResourcesListChanged = sendResourcesListChanged;
|
|
4529
|
+
sendPromptsListChanged = sendPromptsListChanged;
|
|
3478
4530
|
/**
|
|
3479
4531
|
* Notify subscribed clients that a resource has been updated
|
|
3480
4532
|
*
|
|
@@ -3727,7 +4779,8 @@ function createMCPServer(name, config = {}) {
|
|
|
3727
4779
|
allowedOrigins: config.allowedOrigins,
|
|
3728
4780
|
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs,
|
|
3729
4781
|
autoCreateSessionOnInvalidId: config.autoCreateSessionOnInvalidId,
|
|
3730
|
-
oauth: config.oauth
|
|
4782
|
+
oauth: config.oauth,
|
|
4783
|
+
favicon: config.favicon
|
|
3731
4784
|
});
|
|
3732
4785
|
return instance;
|
|
3733
4786
|
}
|
|
@@ -4329,7 +5382,12 @@ function requireAnyScope(needed) {
|
|
|
4329
5382
|
}
|
|
4330
5383
|
__name(requireAnyScope, "requireAnyScope");
|
|
4331
5384
|
export {
|
|
5385
|
+
FileSystemSessionStore,
|
|
5386
|
+
InMemorySessionStore,
|
|
5387
|
+
InMemoryStreamManager,
|
|
4332
5388
|
MCPServer,
|
|
5389
|
+
RedisSessionStore,
|
|
5390
|
+
RedisStreamManager,
|
|
4333
5391
|
VERSION,
|
|
4334
5392
|
adaptConnectMiddleware,
|
|
4335
5393
|
adaptMiddleware,
|