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.
Files changed (98) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{chunk-REYY7LSD.js → chunk-5QFJZ7H3.js} +2 -2
  3. package/dist/chunk-D3CNYAYE.js +1055 -0
  4. package/dist/{chunk-OD6B7KGQ.js → chunk-ESMOFYJ6.js} +27 -2100
  5. package/dist/{chunk-WTGUJLTR.js → chunk-F3BZFJCD.js} +167 -7
  6. package/dist/chunk-GXNAXUDI.js +0 -0
  7. package/dist/{chunk-QP7MQ2UJ.js → chunk-HU2DGJ5J.js} +175 -133
  8. package/dist/{chunk-REX2YTWF.js → chunk-M7WATKYM.js} +1 -1
  9. package/dist/chunk-MFSO5PUW.js +1049 -0
  10. package/dist/{chunk-5LBXMCKC.js → chunk-N3DO4P2L.js} +27 -2100
  11. package/dist/{chunk-M7CHBY4S.js → chunk-OWPXM4QQ.js} +1 -1
  12. package/dist/{chunk-3QVRNWW7.js → chunk-Q5LZL6BH.js} +1 -1
  13. package/dist/{chunk-ZN3MKSKM.js → chunk-UCPSHMNO.js} +1 -1
  14. package/dist/chunk-UWWLWLS2.js +62 -0
  15. package/dist/chunk-WW3A2EKQ.js +1055 -0
  16. package/dist/{chunk-CHHWJQVC.js → chunk-XEFWIBQF.js} +1 -1
  17. package/dist/index.cjs +211 -10
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +31 -28
  21. package/dist/notifications-FLGIFS56.js +9 -0
  22. package/dist/src/adapters/index.cjs +1346 -0
  23. package/dist/src/adapters/index.js +11 -0
  24. package/dist/src/agents/index.cjs +46 -4
  25. package/dist/src/agents/index.js +8 -6
  26. package/dist/src/browser.cjs +46 -5
  27. package/dist/src/browser.d.ts +1 -1
  28. package/dist/src/browser.d.ts.map +1 -1
  29. package/dist/src/browser.js +15 -13
  30. package/dist/src/client/prompts.js +4 -4
  31. package/dist/src/client.cjs +3787 -0
  32. package/dist/src/client.js +20 -0
  33. package/dist/src/react/index.cjs +211 -9
  34. package/dist/src/react/index.js +5 -5
  35. package/dist/src/react/types.d.ts +41 -1
  36. package/dist/src/react/types.d.ts.map +1 -1
  37. package/dist/src/react/useMcp.d.ts.map +1 -1
  38. package/dist/src/react/useWidget.d.ts +11 -7
  39. package/dist/src/react/useWidget.d.ts.map +1 -1
  40. package/dist/src/react/widget-types.d.ts +6 -2
  41. package/dist/src/react/widget-types.d.ts.map +1 -1
  42. package/dist/src/server/endpoints/mount-mcp.d.ts.map +1 -1
  43. package/dist/src/server/index.cjs +1269 -144
  44. package/dist/src/server/index.d.ts +2 -0
  45. package/dist/src/server/index.d.ts.map +1 -1
  46. package/dist/src/server/index.js +1158 -100
  47. package/dist/src/server/mcp-server.d.ts +5 -1
  48. package/dist/src/server/mcp-server.d.ts.map +1 -1
  49. package/dist/src/server/notifications/index.d.ts +1 -1
  50. package/dist/src/server/notifications/index.d.ts.map +1 -1
  51. package/dist/src/server/notifications/notification-registration.d.ts +51 -0
  52. package/dist/src/server/notifications/notification-registration.d.ts.map +1 -1
  53. package/dist/src/server/sessions/index.d.ts +3 -1
  54. package/dist/src/server/sessions/index.d.ts.map +1 -1
  55. package/dist/src/server/sessions/session-manager.d.ts +30 -16
  56. package/dist/src/server/sessions/session-manager.d.ts.map +1 -1
  57. package/dist/src/server/sessions/stores/filesystem.d.ts +121 -0
  58. package/dist/src/server/sessions/stores/filesystem.d.ts.map +1 -0
  59. package/dist/src/server/sessions/stores/index.d.ts +94 -0
  60. package/dist/src/server/sessions/stores/index.d.ts.map +1 -0
  61. package/dist/src/server/sessions/stores/memory.d.ts +82 -0
  62. package/dist/src/server/sessions/stores/memory.d.ts.map +1 -0
  63. package/dist/src/server/sessions/stores/redis.d.ts +164 -0
  64. package/dist/src/server/sessions/stores/redis.d.ts.map +1 -0
  65. package/dist/src/server/sessions/streams/index.d.ts +77 -0
  66. package/dist/src/server/sessions/streams/index.d.ts.map +1 -0
  67. package/dist/src/server/sessions/streams/memory.d.ts +76 -0
  68. package/dist/src/server/sessions/streams/memory.d.ts.map +1 -0
  69. package/dist/src/server/sessions/streams/redis.d.ts +146 -0
  70. package/dist/src/server/sessions/streams/redis.d.ts.map +1 -0
  71. package/dist/src/server/types/common.d.ts +105 -28
  72. package/dist/src/server/types/common.d.ts.map +1 -1
  73. package/dist/src/server/types/resource.d.ts +16 -0
  74. package/dist/src/server/types/resource.d.ts.map +1 -1
  75. package/dist/src/server/types/widget.d.ts +21 -2
  76. package/dist/src/server/types/widget.d.ts.map +1 -1
  77. package/dist/src/server/utils/response-helpers.d.ts +12 -6
  78. package/dist/src/server/utils/response-helpers.d.ts.map +1 -1
  79. package/dist/src/server/widgets/index.d.ts +1 -1
  80. package/dist/src/server/widgets/index.d.ts.map +1 -1
  81. package/dist/src/server/widgets/mount-widgets-dev.d.ts.map +1 -1
  82. package/dist/src/server/widgets/setup-widget-routes.d.ts.map +1 -1
  83. package/dist/src/server/widgets/ui-resource-registration.d.ts.map +1 -1
  84. package/dist/src/server/widgets/widget-helpers.d.ts +22 -0
  85. package/dist/src/server/widgets/widget-helpers.d.ts.map +1 -1
  86. package/dist/src/server/widgets/widget-types.d.ts +2 -0
  87. package/dist/src/server/widgets/widget-types.d.ts.map +1 -1
  88. package/dist/src/task_managers/index.d.ts +10 -0
  89. package/dist/src/task_managers/index.d.ts.map +1 -1
  90. package/dist/src/task_managers/sse.d.ts +34 -1
  91. package/dist/src/task_managers/sse.d.ts.map +1 -1
  92. package/dist/src/task_managers/streamable_http.d.ts +8 -2
  93. package/dist/src/task_managers/streamable_http.d.ts.map +1 -1
  94. package/dist/src/version.d.ts +1 -1
  95. package/dist/{tool-execution-helpers-PAFGGAGL.js → tool-execution-helpers-MXVN6YNU.js} +2 -2
  96. package/dist/tsup.config.d.ts.map +1 -1
  97. package/package.json +29 -5
  98. /package/dist/{chunk-H4BZVTGK.js → chunk-LGDFGYRL.js} +0 -0
@@ -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-ZN3MKSKM.js";
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-3QVRNWW7.js";
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 { data, message } = config;
318
- return {
319
- content: [
320
- {
321
- type: "text",
322
- text: message || ""
323
- }
324
- ],
325
- // structuredContent will be injected as window.openai.toolOutput by Apps SDK
326
- structuredContent: data
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-PAFGGAGL.js");
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-PAFGGAGL.js");
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-PAFGGAGL.js");
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
- if (config.stateless) {
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
- return await transport.handleRequest(c.req.raw);
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 && sessions.has(sessionId)) {
2573
- sessions.get(sessionId).lastAccessedAt = Date.now();
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
- if (sessions.has(sessionId)) {
2580
- const session = sessions.get(sessionId);
2581
- session.lastAccessedAt = Date.now();
2582
- session.context = c;
2583
- session.honoContext = c;
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
- sessions.set(sid, {
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
- if (clientCapabilities && sessions.has(sid)) {
2605
- const session = sessions.get(sid);
2606
- session.clientCapabilities = clientCapabilities;
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 = responseMeta;
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,