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