mcp-use 1.11.0-canary.8 → 1.11.0

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 (91) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{chunk-5RTMAOZ6.js → chunk-A4QJRN7Z.js} +5 -1041
  3. package/dist/{chunk-4LZSXUFM.js → chunk-B7AGEK7F.js} +1 -1
  4. package/dist/{chunk-TAEHPLGV.js → chunk-GN5HOAV3.js} +664 -136
  5. package/dist/chunk-QPIDKGV4.js +1246 -0
  6. package/dist/chunk-UWWLWLS2.js +62 -0
  7. package/dist/{chunk-ZFZPZ4GE.js → chunk-V77WS6CS.js} +9 -0
  8. package/dist/{chunk-EBSNALCB.js → chunk-VRHAF2WT.js} +10 -4
  9. package/dist/{chunk-X7JKFBPN.js → chunk-Y2HHHJQB.js} +159 -8
  10. package/dist/{chunk-JPKFN73V.js → chunk-ZLZOOXMJ.js} +96 -43
  11. package/dist/index.cjs +316 -53
  12. package/dist/index.js +22 -24
  13. package/dist/notifications-FLGIFS56.js +9 -0
  14. package/dist/src/agents/index.cjs +153 -47
  15. package/dist/src/agents/index.d.ts +1 -1
  16. package/dist/src/agents/index.d.ts.map +1 -1
  17. package/dist/src/agents/index.js +7 -10
  18. package/dist/src/agents/mcp_agent.d.ts.map +1 -1
  19. package/dist/src/{client/prompts.d.ts → agents/prompts/index.d.ts} +3 -3
  20. package/dist/src/agents/prompts/index.d.ts.map +1 -0
  21. package/dist/src/browser.cjs +160 -48
  22. package/dist/src/browser.js +10 -12
  23. package/dist/src/client/browser.d.ts.map +1 -1
  24. package/dist/src/client.cjs +3852 -0
  25. package/dist/src/client.d.ts +2 -0
  26. package/dist/src/client.d.ts.map +1 -1
  27. package/dist/src/client.js +21 -0
  28. package/dist/src/config.d.ts.map +1 -1
  29. package/dist/src/connectors/http.d.ts +2 -0
  30. package/dist/src/connectors/http.d.ts.map +1 -1
  31. package/dist/src/react/index.cjs +313 -52
  32. package/dist/src/react/index.js +7 -8
  33. package/dist/src/react/types.d.ts +41 -1
  34. package/dist/src/react/types.d.ts.map +1 -1
  35. package/dist/src/react/useMcp.d.ts.map +1 -1
  36. package/dist/src/server/endpoints/mount-mcp.d.ts.map +1 -1
  37. package/dist/src/server/index.cjs +1339 -256
  38. package/dist/src/server/index.d.ts +2 -0
  39. package/dist/src/server/index.d.ts.map +1 -1
  40. package/dist/src/server/index.js +1119 -156
  41. package/dist/src/server/mcp-server.d.ts +4 -1
  42. package/dist/src/server/mcp-server.d.ts.map +1 -1
  43. package/dist/src/server/notifications/index.d.ts +1 -1
  44. package/dist/src/server/notifications/index.d.ts.map +1 -1
  45. package/dist/src/server/notifications/notification-registration.d.ts +51 -0
  46. package/dist/src/server/notifications/notification-registration.d.ts.map +1 -1
  47. package/dist/src/server/sessions/index.d.ts +3 -1
  48. package/dist/src/server/sessions/index.d.ts.map +1 -1
  49. package/dist/src/server/sessions/session-manager.d.ts +30 -16
  50. package/dist/src/server/sessions/session-manager.d.ts.map +1 -1
  51. package/dist/src/server/sessions/stores/filesystem.d.ts +121 -0
  52. package/dist/src/server/sessions/stores/filesystem.d.ts.map +1 -0
  53. package/dist/src/server/sessions/stores/index.d.ts +94 -0
  54. package/dist/src/server/sessions/stores/index.d.ts.map +1 -0
  55. package/dist/src/server/sessions/stores/memory.d.ts +82 -0
  56. package/dist/src/server/sessions/stores/memory.d.ts.map +1 -0
  57. package/dist/src/server/sessions/stores/redis.d.ts +164 -0
  58. package/dist/src/server/sessions/stores/redis.d.ts.map +1 -0
  59. package/dist/src/server/sessions/streams/index.d.ts +77 -0
  60. package/dist/src/server/sessions/streams/index.d.ts.map +1 -0
  61. package/dist/src/server/sessions/streams/memory.d.ts +76 -0
  62. package/dist/src/server/sessions/streams/memory.d.ts.map +1 -0
  63. package/dist/src/server/sessions/streams/redis.d.ts +146 -0
  64. package/dist/src/server/sessions/streams/redis.d.ts.map +1 -0
  65. package/dist/src/server/types/common.d.ts +82 -28
  66. package/dist/src/server/types/common.d.ts.map +1 -1
  67. package/dist/src/server/types/widget.d.ts +2 -2
  68. package/dist/src/server/types/widget.d.ts.map +1 -1
  69. package/dist/src/server/utils/response-helpers.d.ts +4 -2
  70. package/dist/src/server/utils/response-helpers.d.ts.map +1 -1
  71. package/dist/src/server/widgets/mount-widgets-dev.d.ts.map +1 -1
  72. package/dist/src/server/widgets/ui-resource-registration.d.ts.map +1 -1
  73. package/dist/src/task_managers/index.d.ts +10 -0
  74. package/dist/src/task_managers/index.d.ts.map +1 -1
  75. package/dist/src/task_managers/sse.d.ts +34 -1
  76. package/dist/src/task_managers/sse.d.ts.map +1 -1
  77. package/dist/src/task_managers/streamable_http.d.ts +8 -2
  78. package/dist/src/task_managers/streamable_http.d.ts.map +1 -1
  79. package/dist/src/telemetry/telemetry.d.ts +1 -0
  80. package/dist/src/telemetry/telemetry.d.ts.map +1 -1
  81. package/dist/src/version.d.ts +1 -1
  82. package/dist/src/version.d.ts.map +1 -1
  83. package/dist/{tool-execution-helpers-EYAIJERC.js → tool-execution-helpers-ZUA5D5IO.js} +2 -2
  84. package/dist/tsup.config.d.ts.map +1 -1
  85. package/package.json +62 -52
  86. package/dist/chunk-GVU7C2ZD.js +0 -12
  87. package/dist/chunk-JZNXOM7C.js +0 -204
  88. package/dist/chunk-XKTBHYNM.js +0 -491
  89. package/dist/src/client/prompts.cjs +0 -407
  90. package/dist/src/client/prompts.d.ts.map +0 -1
  91. package/dist/src/client/prompts.js +0 -11
@@ -7,13 +7,18 @@ import {
7
7
  createEnhancedContext,
8
8
  findSessionContext,
9
9
  isValidLogLevel
10
- } from "../../chunk-4LZSXUFM.js";
10
+ } from "../../chunk-B7AGEK7F.js";
11
11
  import {
12
12
  convertToolResultToResourceResult
13
13
  } from "../../chunk-362PI25Z.js";
14
14
  import {
15
15
  convertToolResultToPromptResult
16
16
  } from "../../chunk-2EYAMIT3.js";
17
+ import {
18
+ createRequest,
19
+ sendNotificationToAll,
20
+ sendNotificationToSession
21
+ } from "../../chunk-UWWLWLS2.js";
17
22
  import "../../chunk-KUEVOU4M.js";
18
23
  import {
19
24
  Telemetry,
@@ -25,7 +30,7 @@ import {
25
30
  getPackageVersion,
26
31
  isDeno,
27
32
  pathHelpers
28
- } from "../../chunk-JPKFN73V.js";
33
+ } from "../../chunk-ZLZOOXMJ.js";
29
34
  import "../../chunk-FRUZDWXH.js";
30
35
  import {
31
36
  __name
@@ -40,7 +45,7 @@ import {
40
45
  McpError,
41
46
  ErrorCode
42
47
  } from "@mcp-use/modelcontextprotocol-sdk/types.js";
43
- import { z as z2 } from "zod";
48
+ import { z as z3 } from "zod";
44
49
 
45
50
  // src/server/utils/response-helpers.ts
46
51
  function text(content) {
@@ -314,18 +319,23 @@ function binary(base64Data, mimeType) {
314
319
  }
315
320
  __name(binary, "binary");
316
321
  function widget(config) {
317
- const { props, output, message } = config;
318
- const finalContent = message ? [{ type: "text", text: message }] : output.content || [{ type: "text", text: "" }];
319
- return {
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 = {
320
330
  content: finalContent,
321
- ...output.structuredContent && {
322
- structuredContent: output.structuredContent
323
- },
324
- _meta: {
325
- ...output._meta || {},
326
- "mcp-use/props": props
327
- }
331
+ _meta: meta
328
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;
329
339
  }
330
340
  __name(widget, "widget");
331
341
  function mix(...results) {
@@ -1504,12 +1514,16 @@ export default PostHog;
1504
1514
  const mod = await viteServer.ssrLoadModule(widget2.entry);
1505
1515
  if (mod.widgetMetadata) {
1506
1516
  metadata = mod.widgetMetadata;
1507
- if (metadata.inputs) {
1517
+ const schemaField = metadata.props || metadata.inputs;
1518
+ if (schemaField) {
1508
1519
  try {
1509
- metadata.inputs = metadata.inputs.shape || metadata.inputs;
1520
+ metadata.props = schemaField;
1521
+ if (!metadata.inputs) {
1522
+ metadata.inputs = schemaField;
1523
+ }
1510
1524
  } catch (error2) {
1511
1525
  console.warn(
1512
- `[WIDGET] Failed to extract props schema for ${widget2.name}:`,
1526
+ `[WIDGET] Failed to extract schema for ${widget2.name}:`,
1513
1527
  error2
1514
1528
  );
1515
1529
  }
@@ -1697,6 +1711,7 @@ function setupWidgetRoutes(app, serverConfig) {
1697
1711
  __name(setupWidgetRoutes, "setupWidgetRoutes");
1698
1712
 
1699
1713
  // src/server/widgets/ui-resource-registration.ts
1714
+ import z from "zod";
1700
1715
  function uiResourceRegistration(server, definition) {
1701
1716
  const displayName = definition.title || definition.name;
1702
1717
  if (definition.type === "appsSdk" && definition._meta) {
@@ -1802,69 +1817,76 @@ function uiResourceRegistration(server, definition) {
1802
1817
  }
1803
1818
  }
1804
1819
  }
1805
- server.tool(
1806
- {
1807
- name: definition.name,
1808
- title: definition.title,
1809
- description: definition.description,
1810
- inputs: convertPropsToInputs(definition.props),
1811
- annotations: definition.toolAnnotations,
1812
- _meta: Object.keys(toolMetadata).length > 0 ? toolMetadata : void 0
1813
- },
1814
- async (params) => {
1815
- const uiResource = await createWidgetUIResource(
1816
- definition,
1817
- params,
1818
- serverConfig
1820
+ const widgetMetadata2 = definition._meta?.["mcp-use/widget"];
1821
+ const propsOrSchema = definition.props || widgetMetadata2?.props || widgetMetadata2?.inputs || widgetMetadata2?.schema;
1822
+ const isZodSchema = propsOrSchema && typeof propsOrSchema === "object" && propsOrSchema instanceof z.ZodObject;
1823
+ const toolDefinition = {
1824
+ name: definition.name,
1825
+ title: definition.title,
1826
+ description: definition.description,
1827
+ annotations: definition.toolAnnotations,
1828
+ _meta: Object.keys(toolMetadata).length > 0 ? toolMetadata : void 0
1829
+ };
1830
+ if (isZodSchema) {
1831
+ toolDefinition.schema = propsOrSchema;
1832
+ } else if (propsOrSchema) {
1833
+ toolDefinition.inputs = convertPropsToInputs(
1834
+ propsOrSchema
1835
+ );
1836
+ }
1837
+ server.tool(toolDefinition, async (params) => {
1838
+ const uiResource = await createWidgetUIResource(
1839
+ definition,
1840
+ params,
1841
+ serverConfig
1842
+ );
1843
+ if (definition.type === "appsSdk") {
1844
+ const randomId = Math.random().toString(36).substring(2, 15);
1845
+ const uniqueUri = generateWidgetUri(
1846
+ definition.name,
1847
+ server.buildId,
1848
+ ".html",
1849
+ randomId
1819
1850
  );
1820
- if (definition.type === "appsSdk") {
1821
- const randomId = Math.random().toString(36).substring(2, 15);
1822
- const uniqueUri = generateWidgetUri(
1823
- definition.name,
1824
- server.buildId,
1825
- ".html",
1826
- randomId
1827
- );
1828
- const uniqueToolMetadata = {
1829
- ...toolMetadata,
1830
- "openai/outputTemplate": uniqueUri,
1831
- "mcp-use/props": params
1832
- // Pass params as widget props
1833
- };
1834
- let toolOutputResult;
1835
- if (definition.toolOutput) {
1836
- toolOutputResult = typeof definition.toolOutput === "function" ? definition.toolOutput(params) : definition.toolOutput;
1837
- } else {
1838
- toolOutputResult = {
1839
- content: [
1840
- {
1841
- type: "text",
1842
- text: `Displaying ${displayName}`
1843
- }
1844
- ]
1845
- };
1846
- }
1847
- const content = toolOutputResult.content || [
1848
- { type: "text", text: `Displaying ${displayName}` }
1849
- ];
1850
- return {
1851
- _meta: uniqueToolMetadata,
1852
- content,
1853
- structuredContent: toolOutputResult.structuredContent
1851
+ const uniqueToolMetadata = {
1852
+ ...toolMetadata,
1853
+ "openai/outputTemplate": uniqueUri,
1854
+ "mcp-use/props": params
1855
+ // Pass params as widget props
1856
+ };
1857
+ let toolOutputResult;
1858
+ if (definition.toolOutput) {
1859
+ toolOutputResult = typeof definition.toolOutput === "function" ? definition.toolOutput(params) : definition.toolOutput;
1860
+ } else {
1861
+ toolOutputResult = {
1862
+ content: [
1863
+ {
1864
+ type: "text",
1865
+ text: `Displaying ${displayName}`
1866
+ }
1867
+ ]
1854
1868
  };
1855
1869
  }
1870
+ const content = toolOutputResult.content || [
1871
+ { type: "text", text: `Displaying ${displayName}` }
1872
+ ];
1856
1873
  return {
1857
- content: [
1858
- {
1859
- type: "text",
1860
- text: `Displaying ${displayName}`,
1861
- description: `Show MCP-UI widget for ${displayName}`
1862
- },
1863
- uiResource
1864
- ]
1874
+ _meta: uniqueToolMetadata,
1875
+ content,
1876
+ structuredContent: toolOutputResult.structuredContent
1865
1877
  };
1866
1878
  }
1867
- );
1879
+ return {
1880
+ content: [
1881
+ {
1882
+ type: "text",
1883
+ text: `Displaying ${displayName}`,
1884
+ description: `Show MCP-UI widget for ${displayName}`
1885
+ },
1886
+ uiResource
1887
+ ]
1888
+ };
1889
+ });
1868
1890
  }
1869
1891
  return server;
1870
1892
  }
@@ -1926,9 +1948,9 @@ async function mountInspectorUI(app, serverHost, serverPort, isProduction) {
1926
1948
  __name(mountInspectorUI, "mountInspectorUI");
1927
1949
 
1928
1950
  // src/server/tools/schema-helpers.ts
1929
- import { z } from "zod";
1951
+ import { z as z2 } from "zod";
1930
1952
  function convertZodSchemaToParams(zodSchema) {
1931
- if (!(zodSchema instanceof z.ZodObject)) {
1953
+ if (!(zodSchema instanceof z2.ZodObject)) {
1932
1954
  throw new Error("schema must be a Zod object schema (z.object({...}))");
1933
1955
  }
1934
1956
  const shape = zodSchema.shape;
@@ -1945,22 +1967,22 @@ function createParamsSchema(inputs) {
1945
1967
  let zodType;
1946
1968
  switch (input.type) {
1947
1969
  case "string":
1948
- zodType = z.string();
1970
+ zodType = z2.string();
1949
1971
  break;
1950
1972
  case "number":
1951
- zodType = z.number();
1973
+ zodType = z2.number();
1952
1974
  break;
1953
1975
  case "boolean":
1954
- zodType = z.boolean();
1976
+ zodType = z2.boolean();
1955
1977
  break;
1956
1978
  case "object":
1957
- zodType = z.object({});
1979
+ zodType = z2.object({});
1958
1980
  break;
1959
1981
  case "array":
1960
- zodType = z.array(z.any());
1982
+ zodType = z2.array(z2.any());
1961
1983
  break;
1962
1984
  default:
1963
- zodType = z.any();
1985
+ zodType = z2.any();
1964
1986
  }
1965
1987
  if (input.description) {
1966
1988
  zodType = zodType.describe(input.description);
@@ -2195,7 +2217,7 @@ function registerResource(resourceDefinition, callback) {
2195
2217
  const explicitMimeType = resourceDefinition.mimeType;
2196
2218
  const wrappedCallback = /* @__PURE__ */ __name(async () => {
2197
2219
  const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-NA4MHWOZ.js");
2198
- const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-EYAIJERC.js");
2220
+ const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-ZUA5D5IO.js");
2199
2221
  const initialRequestContext = getRequestContext2();
2200
2222
  const sessions = this.sessions || /* @__PURE__ */ new Map();
2201
2223
  const { requestContext } = findSessionContext2(
@@ -2273,7 +2295,7 @@ function registerResourceTemplate(resourceTemplateDefinition, callback) {
2273
2295
  async (uri) => {
2274
2296
  const params = this.parseTemplateUri(uriTemplate, uri.toString());
2275
2297
  const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-NA4MHWOZ.js");
2276
- const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-EYAIJERC.js");
2298
+ const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-ZUA5D5IO.js");
2277
2299
  const initialRequestContext = getRequestContext2();
2278
2300
  const sessions = this.sessions || /* @__PURE__ */ new Map();
2279
2301
  const { requestContext } = findSessionContext2(
@@ -2328,7 +2350,7 @@ function registerPrompt(promptDefinition, callback) {
2328
2350
  }
2329
2351
  const wrappedCallback = /* @__PURE__ */ __name(async (params, extra) => {
2330
2352
  const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-NA4MHWOZ.js");
2331
- const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-EYAIJERC.js");
2353
+ const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-ZUA5D5IO.js");
2332
2354
  const initialRequestContext = getRequestContext2();
2333
2355
  const sessions = this.sessions || /* @__PURE__ */ new Map();
2334
2356
  const { requestContext } = findSessionContext2(
@@ -2365,25 +2387,6 @@ function registerPrompt(promptDefinition, callback) {
2365
2387
  }
2366
2388
  __name(registerPrompt, "registerPrompt");
2367
2389
 
2368
- // src/server/utils/jsonrpc-helpers.ts
2369
- function createNotification(method, params) {
2370
- return {
2371
- jsonrpc: "2.0",
2372
- method,
2373
- ...params && { params }
2374
- };
2375
- }
2376
- __name(createNotification, "createNotification");
2377
- function createRequest(id, method, params) {
2378
- return {
2379
- jsonrpc: "2.0",
2380
- id,
2381
- method,
2382
- ...params && { params }
2383
- };
2384
- }
2385
- __name(createRequest, "createRequest");
2386
-
2387
2390
  // src/server/roots/roots-registration.ts
2388
2391
  function onRootsChanged(callback) {
2389
2392
  this.onRootsChangedCallback = callback;
@@ -2545,40 +2548,6 @@ async function requestLogger(c, next) {
2545
2548
  }
2546
2549
  __name(requestLogger, "requestLogger");
2547
2550
 
2548
- // src/server/sessions/notifications.ts
2549
- async function sendNotificationToAll(sessions, method, params) {
2550
- const notification = createNotification(method, params);
2551
- for (const [sessionId, session] of sessions.entries()) {
2552
- try {
2553
- await session.transport.send(notification);
2554
- } catch (error2) {
2555
- console.warn(
2556
- `[MCP] Failed to send notification to session ${sessionId}:`,
2557
- error2
2558
- );
2559
- }
2560
- }
2561
- }
2562
- __name(sendNotificationToAll, "sendNotificationToAll");
2563
- async function sendNotificationToSession(sessions, sessionId, method, params) {
2564
- const session = sessions.get(sessionId);
2565
- if (!session) {
2566
- return false;
2567
- }
2568
- const notification = createNotification(method, params);
2569
- try {
2570
- await session.transport.send(notification);
2571
- return true;
2572
- } catch (error2) {
2573
- console.warn(
2574
- `[MCP] Failed to send notification to session ${sessionId}:`,
2575
- error2
2576
- );
2577
- return false;
2578
- }
2579
- }
2580
- __name(sendNotificationToSession, "sendNotificationToSession");
2581
-
2582
2551
  // src/server/notifications/notification-registration.ts
2583
2552
  function getActiveSessions() {
2584
2553
  return Array.from(this.sessions.keys());
@@ -2597,6 +2566,27 @@ async function sendNotificationToSession2(sessionId, method, params) {
2597
2566
  );
2598
2567
  }
2599
2568
  __name(sendNotificationToSession2, "sendNotificationToSession");
2569
+ async function sendToolsListChanged() {
2570
+ await sendNotificationToAll(
2571
+ this.sessions,
2572
+ "notifications/tools/list_changed"
2573
+ );
2574
+ }
2575
+ __name(sendToolsListChanged, "sendToolsListChanged");
2576
+ async function sendResourcesListChanged() {
2577
+ await sendNotificationToAll(
2578
+ this.sessions,
2579
+ "notifications/resources/list_changed"
2580
+ );
2581
+ }
2582
+ __name(sendResourcesListChanged, "sendResourcesListChanged");
2583
+ async function sendPromptsListChanged() {
2584
+ await sendNotificationToAll(
2585
+ this.sessions,
2586
+ "notifications/prompts/list_changed"
2587
+ );
2588
+ }
2589
+ __name(sendPromptsListChanged, "sendPromptsListChanged");
2600
2590
 
2601
2591
  // src/server/sessions/session-manager.ts
2602
2592
  function startIdleCleanup(sessions, idleTimeoutMs, transports, mcpServerInstance) {
@@ -2637,11 +2627,836 @@ function startIdleCleanup(sessions, idleTimeoutMs, transports, mcpServerInstance
2637
2627
  }
2638
2628
  __name(startIdleCleanup, "startIdleCleanup");
2639
2629
 
2630
+ // src/server/sessions/stores/memory.ts
2631
+ var InMemorySessionStore = class {
2632
+ static {
2633
+ __name(this, "InMemorySessionStore");
2634
+ }
2635
+ /**
2636
+ * Internal map storing session metadata
2637
+ * Key: sessionId, Value: SessionMetadata
2638
+ */
2639
+ sessions = /* @__PURE__ */ new Map();
2640
+ /**
2641
+ * Retrieve session metadata by ID
2642
+ */
2643
+ async get(sessionId) {
2644
+ const data = this.sessions.get(sessionId);
2645
+ return data ?? null;
2646
+ }
2647
+ /**
2648
+ * Store or update session metadata
2649
+ */
2650
+ async set(sessionId, data) {
2651
+ this.sessions.set(sessionId, data);
2652
+ }
2653
+ /**
2654
+ * Delete session metadata
2655
+ */
2656
+ async delete(sessionId) {
2657
+ this.sessions.delete(sessionId);
2658
+ }
2659
+ /**
2660
+ * Check if session exists
2661
+ */
2662
+ async has(sessionId) {
2663
+ return this.sessions.has(sessionId);
2664
+ }
2665
+ /**
2666
+ * List all session IDs
2667
+ */
2668
+ async keys() {
2669
+ return Array.from(this.sessions.keys());
2670
+ }
2671
+ /**
2672
+ * Store session metadata with TTL (time-to-live)
2673
+ *
2674
+ * Note: In-memory implementation uses setTimeout for TTL.
2675
+ * For production TTL support, use Redis or another store with native TTL.
2676
+ */
2677
+ async setWithTTL(sessionId, data, ttlMs) {
2678
+ this.sessions.set(sessionId, data);
2679
+ setTimeout(() => {
2680
+ this.sessions.delete(sessionId);
2681
+ console.log(`[MCP] Session ${sessionId} expired after ${ttlMs}ms`);
2682
+ }, ttlMs);
2683
+ }
2684
+ /**
2685
+ * Get the number of active sessions
2686
+ * Useful for monitoring and debugging
2687
+ */
2688
+ get size() {
2689
+ return this.sessions.size;
2690
+ }
2691
+ /**
2692
+ * Clear all sessions
2693
+ * Useful for testing and manual cleanup
2694
+ */
2695
+ async clear() {
2696
+ this.sessions.clear();
2697
+ }
2698
+ };
2699
+
2700
+ // src/server/sessions/stores/redis.ts
2701
+ var RedisSessionStore = class {
2702
+ static {
2703
+ __name(this, "RedisSessionStore");
2704
+ }
2705
+ client;
2706
+ prefix;
2707
+ defaultTTL;
2708
+ constructor(config) {
2709
+ this.client = config.client;
2710
+ this.prefix = config.prefix ?? "mcp:session:";
2711
+ this.defaultTTL = config.defaultTTL ?? 3600;
2712
+ }
2713
+ /**
2714
+ * Get full Redis key for a session ID
2715
+ */
2716
+ getKey(sessionId) {
2717
+ return `${this.prefix}${sessionId}`;
2718
+ }
2719
+ /**
2720
+ * Retrieve session metadata by ID
2721
+ */
2722
+ async get(sessionId) {
2723
+ try {
2724
+ const key = this.getKey(sessionId);
2725
+ const data = await this.client.get(key);
2726
+ if (!data) {
2727
+ return null;
2728
+ }
2729
+ return JSON.parse(data);
2730
+ } catch (error2) {
2731
+ console.error(
2732
+ `[RedisSessionStore] Error getting session ${sessionId}:`,
2733
+ error2
2734
+ );
2735
+ return null;
2736
+ }
2737
+ }
2738
+ /**
2739
+ * Store or update session metadata
2740
+ */
2741
+ async set(sessionId, data) {
2742
+ try {
2743
+ const key = this.getKey(sessionId);
2744
+ const value = JSON.stringify(data);
2745
+ if (this.client.setEx) {
2746
+ await this.client.setEx(key, this.defaultTTL, value);
2747
+ } else if (this.client.setex) {
2748
+ await this.client.setex(key, this.defaultTTL, value);
2749
+ } else {
2750
+ await this.client.set(key, value, { EX: this.defaultTTL });
2751
+ }
2752
+ } catch (error2) {
2753
+ console.error(
2754
+ `[RedisSessionStore] Error setting session ${sessionId}:`,
2755
+ error2
2756
+ );
2757
+ throw error2;
2758
+ }
2759
+ }
2760
+ /**
2761
+ * Delete a session
2762
+ */
2763
+ async delete(sessionId) {
2764
+ try {
2765
+ const key = this.getKey(sessionId);
2766
+ await this.client.del(key);
2767
+ } catch (error2) {
2768
+ console.error(
2769
+ `[RedisSessionStore] Error deleting session ${sessionId}:`,
2770
+ error2
2771
+ );
2772
+ throw error2;
2773
+ }
2774
+ }
2775
+ /**
2776
+ * Check if a session exists
2777
+ */
2778
+ async has(sessionId) {
2779
+ try {
2780
+ const key = this.getKey(sessionId);
2781
+ const exists = await this.client.exists(key);
2782
+ return exists === 1;
2783
+ } catch (error2) {
2784
+ console.error(
2785
+ `[RedisSessionStore] Error checking session ${sessionId}:`,
2786
+ error2
2787
+ );
2788
+ return false;
2789
+ }
2790
+ }
2791
+ /**
2792
+ * List all session IDs
2793
+ *
2794
+ * WARNING: Uses KEYS command which blocks Redis. For production systems with
2795
+ * many sessions, consider using SCAN instead or maintaining a separate SET of
2796
+ * active session IDs.
2797
+ */
2798
+ async keys() {
2799
+ try {
2800
+ const pattern = `${this.prefix}*`;
2801
+ const keys = await this.client.keys(pattern);
2802
+ return keys.map((key) => key.substring(this.prefix.length));
2803
+ } catch (error2) {
2804
+ console.error("[RedisSessionStore] Error listing session keys:", error2);
2805
+ return [];
2806
+ }
2807
+ }
2808
+ /**
2809
+ * Store session metadata with custom TTL (time-to-live)
2810
+ */
2811
+ async setWithTTL(sessionId, data, ttlMs) {
2812
+ try {
2813
+ const key = this.getKey(sessionId);
2814
+ const value = JSON.stringify(data);
2815
+ const ttlSeconds = Math.ceil(ttlMs / 1e3);
2816
+ if (this.client.setEx) {
2817
+ await this.client.setEx(key, ttlSeconds, value);
2818
+ } else if (this.client.setex) {
2819
+ await this.client.setex(key, ttlSeconds, value);
2820
+ } else {
2821
+ await this.client.set(key, value, { EX: ttlSeconds });
2822
+ }
2823
+ } catch (error2) {
2824
+ console.error(
2825
+ `[RedisSessionStore] Error setting session ${sessionId} with TTL:`,
2826
+ error2
2827
+ );
2828
+ throw error2;
2829
+ }
2830
+ }
2831
+ /**
2832
+ * Close Redis connection
2833
+ * Should be called when shutting down the server
2834
+ */
2835
+ async close() {
2836
+ try {
2837
+ await this.client.quit();
2838
+ } catch (error2) {
2839
+ console.error(
2840
+ "[RedisSessionStore] Error closing Redis connection:",
2841
+ error2
2842
+ );
2843
+ throw error2;
2844
+ }
2845
+ }
2846
+ /**
2847
+ * Clear all sessions (useful for testing)
2848
+ * WARNING: This will delete all sessions with the configured prefix
2849
+ *
2850
+ * NOTE: Uses KEYS command which blocks Redis. This is acceptable for testing
2851
+ * but should be avoided in production with large datasets.
2852
+ */
2853
+ async clear() {
2854
+ try {
2855
+ const pattern = `${this.prefix}*`;
2856
+ const keys = await this.client.keys(pattern);
2857
+ if (keys.length > 0) {
2858
+ await this.client.del(keys);
2859
+ }
2860
+ } catch (error2) {
2861
+ console.error("[RedisSessionStore] Error clearing sessions:", error2);
2862
+ throw error2;
2863
+ }
2864
+ }
2865
+ };
2866
+
2867
+ // src/server/sessions/stores/filesystem.ts
2868
+ import { mkdir, writeFile, rename, unlink } from "fs/promises";
2869
+ import { join, dirname } from "path";
2870
+ import { existsSync, readFileSync } from "fs";
2871
+ var FileSystemSessionStore = class {
2872
+ static {
2873
+ __name(this, "FileSystemSessionStore");
2874
+ }
2875
+ sessions = /* @__PURE__ */ new Map();
2876
+ filePath;
2877
+ debounceMs;
2878
+ maxAgeMs;
2879
+ saveTimer = null;
2880
+ saving = false;
2881
+ pendingSave = false;
2882
+ constructor(config = {}) {
2883
+ this.filePath = config.path ?? join(process.cwd(), ".mcp-use", "sessions.json");
2884
+ this.debounceMs = config.debounceMs ?? 100;
2885
+ this.maxAgeMs = config.maxAgeMs ?? 24 * 60 * 60 * 1e3;
2886
+ this.loadSessionsSync();
2887
+ }
2888
+ /**
2889
+ * Load sessions from file synchronously during construction
2890
+ * This ensures sessions are available immediately when the server starts
2891
+ */
2892
+ loadSessionsSync() {
2893
+ try {
2894
+ if (!existsSync(this.filePath)) {
2895
+ console.log(
2896
+ `[FileSystemSessionStore] No session file found at ${this.filePath}, starting fresh`
2897
+ );
2898
+ return;
2899
+ }
2900
+ const data = readFileSync(this.filePath, "utf-8");
2901
+ const parsed = JSON.parse(data);
2902
+ const now = Date.now();
2903
+ let loadedCount = 0;
2904
+ let expiredCount = 0;
2905
+ for (const [sessionId, metadata] of Object.entries(parsed)) {
2906
+ const sessionMetadata = metadata;
2907
+ const age = now - sessionMetadata.lastAccessedAt;
2908
+ if (age > this.maxAgeMs) {
2909
+ expiredCount++;
2910
+ continue;
2911
+ }
2912
+ this.sessions.set(sessionId, sessionMetadata);
2913
+ loadedCount++;
2914
+ }
2915
+ console.log(
2916
+ `[FileSystemSessionStore] Loaded ${loadedCount} session(s) from ${this.filePath}` + (expiredCount > 0 ? ` (cleaned up ${expiredCount} expired)` : "")
2917
+ );
2918
+ } catch (error2) {
2919
+ if (error2.code === "ENOENT") {
2920
+ console.log(`[FileSystemSessionStore] No existing sessions file`);
2921
+ } else if (error2 instanceof SyntaxError) {
2922
+ console.warn(
2923
+ `[FileSystemSessionStore] Corrupted session file, starting fresh:`,
2924
+ error2.message
2925
+ );
2926
+ } else {
2927
+ console.warn(
2928
+ `[FileSystemSessionStore] Error loading sessions, starting fresh:`,
2929
+ error2.message
2930
+ );
2931
+ }
2932
+ }
2933
+ }
2934
+ /**
2935
+ * Retrieve session metadata by ID
2936
+ */
2937
+ async get(sessionId) {
2938
+ const data = this.sessions.get(sessionId);
2939
+ return data ?? null;
2940
+ }
2941
+ /**
2942
+ * Store or update session metadata
2943
+ * Uses debouncing to batch rapid consecutive writes
2944
+ */
2945
+ async set(sessionId, data) {
2946
+ this.sessions.set(sessionId, data);
2947
+ await this.scheduleSave();
2948
+ }
2949
+ /**
2950
+ * Delete session metadata
2951
+ */
2952
+ async delete(sessionId) {
2953
+ this.sessions.delete(sessionId);
2954
+ await this.scheduleSave();
2955
+ }
2956
+ /**
2957
+ * Check if session exists
2958
+ */
2959
+ async has(sessionId) {
2960
+ return this.sessions.has(sessionId);
2961
+ }
2962
+ /**
2963
+ * List all session IDs
2964
+ */
2965
+ async keys() {
2966
+ return Array.from(this.sessions.keys());
2967
+ }
2968
+ /**
2969
+ * Store session metadata with TTL
2970
+ * Note: TTL is enforced on load, not with timers (simple implementation)
2971
+ */
2972
+ async setWithTTL(sessionId, data, ttlMs) {
2973
+ const metadataWithExpiry = {
2974
+ ...data,
2975
+ lastAccessedAt: Date.now()
2976
+ };
2977
+ this.sessions.set(sessionId, metadataWithExpiry);
2978
+ await this.scheduleSave();
2979
+ setTimeout(() => {
2980
+ this.sessions.delete(sessionId);
2981
+ this.scheduleSave();
2982
+ }, ttlMs);
2983
+ }
2984
+ /**
2985
+ * Get the number of active sessions
2986
+ */
2987
+ get size() {
2988
+ return this.sessions.size;
2989
+ }
2990
+ /**
2991
+ * Clear all sessions
2992
+ */
2993
+ async clear() {
2994
+ this.sessions.clear();
2995
+ await this.scheduleSave();
2996
+ }
2997
+ /**
2998
+ * Schedule a save operation with debouncing
2999
+ * Prevents excessive disk I/O from rapid consecutive writes
3000
+ */
3001
+ async scheduleSave() {
3002
+ if (this.saving) {
3003
+ this.pendingSave = true;
3004
+ return;
3005
+ }
3006
+ if (this.saveTimer) {
3007
+ clearTimeout(this.saveTimer);
3008
+ }
3009
+ this.saveTimer = setTimeout(() => {
3010
+ this.performSave();
3011
+ }, this.debounceMs);
3012
+ }
3013
+ /**
3014
+ * Perform the actual save operation with atomic writes
3015
+ * Uses write-to-temp-then-rename pattern to prevent corruption
3016
+ */
3017
+ async performSave() {
3018
+ this.saveTimer = null;
3019
+ this.saving = true;
3020
+ this.pendingSave = false;
3021
+ try {
3022
+ const dir = dirname(this.filePath);
3023
+ await mkdir(dir, { recursive: true });
3024
+ const data = {};
3025
+ for (const [sessionId, metadata] of Array.from(this.sessions.entries())) {
3026
+ data[sessionId] = metadata;
3027
+ }
3028
+ const tempPath = `${this.filePath}.tmp`;
3029
+ await writeFile(tempPath, JSON.stringify(data, null, 2), "utf-8");
3030
+ await rename(tempPath, this.filePath);
3031
+ console.debug(
3032
+ `[FileSystemSessionStore] Saved ${this.sessions.size} session(s) to ${this.filePath}`
3033
+ );
3034
+ } catch (error2) {
3035
+ console.error(
3036
+ `[FileSystemSessionStore] Error saving sessions:`,
3037
+ error2.message
3038
+ );
3039
+ try {
3040
+ const tempPath = `${this.filePath}.tmp`;
3041
+ if (existsSync(tempPath)) {
3042
+ await unlink(tempPath);
3043
+ }
3044
+ } catch {
3045
+ }
3046
+ } finally {
3047
+ this.saving = false;
3048
+ if (this.pendingSave) {
3049
+ await this.scheduleSave();
3050
+ }
3051
+ }
3052
+ }
3053
+ /**
3054
+ * Force an immediate save (bypasses debouncing)
3055
+ * Useful for ensuring persistence before process exit
3056
+ */
3057
+ async flush() {
3058
+ if (this.saveTimer) {
3059
+ clearTimeout(this.saveTimer);
3060
+ this.saveTimer = null;
3061
+ }
3062
+ await this.performSave();
3063
+ }
3064
+ };
3065
+
3066
+ // src/server/sessions/streams/memory.ts
3067
+ var InMemoryStreamManager = class {
3068
+ static {
3069
+ __name(this, "InMemoryStreamManager");
3070
+ }
3071
+ /**
3072
+ * Map of active SSE stream controllers
3073
+ * Key: sessionId, Value: ReadableStreamDefaultController
3074
+ */
3075
+ streams = /* @__PURE__ */ new Map();
3076
+ /**
3077
+ * Text encoder for converting strings to Uint8Array
3078
+ */
3079
+ textEncoder = new TextEncoder();
3080
+ /**
3081
+ * Register an active SSE stream controller
3082
+ */
3083
+ async create(sessionId, controller) {
3084
+ this.streams.set(sessionId, controller);
3085
+ }
3086
+ /**
3087
+ * Send data to active SSE streams
3088
+ *
3089
+ * Directly enqueues data to in-memory controllers.
3090
+ * For distributed deployments, use RedisStreamManager instead.
3091
+ */
3092
+ async send(sessionIds, data) {
3093
+ const encoded = this.textEncoder.encode(data);
3094
+ if (!sessionIds) {
3095
+ for (const [_id, controller] of this.streams.entries()) {
3096
+ try {
3097
+ controller.enqueue(encoded);
3098
+ } catch (error2) {
3099
+ console.warn(
3100
+ `[InMemoryStreamManager] Failed to send to session ${_id}:`,
3101
+ error2
3102
+ );
3103
+ }
3104
+ }
3105
+ } else {
3106
+ for (const sessionId of sessionIds) {
3107
+ const controller = this.streams.get(sessionId);
3108
+ if (controller) {
3109
+ try {
3110
+ controller.enqueue(encoded);
3111
+ } catch (error2) {
3112
+ console.warn(
3113
+ `[InMemoryStreamManager] Failed to send to session ${sessionId}:`,
3114
+ error2
3115
+ );
3116
+ }
3117
+ }
3118
+ }
3119
+ }
3120
+ }
3121
+ /**
3122
+ * Remove an active SSE stream
3123
+ */
3124
+ async delete(sessionId) {
3125
+ const controller = this.streams.get(sessionId);
3126
+ if (controller) {
3127
+ try {
3128
+ controller.close();
3129
+ } catch (error2) {
3130
+ console.debug(
3131
+ `[InMemoryStreamManager] Controller already closed for session ${sessionId}`
3132
+ );
3133
+ }
3134
+ this.streams.delete(sessionId);
3135
+ }
3136
+ }
3137
+ /**
3138
+ * Check if an active stream exists
3139
+ */
3140
+ async has(sessionId) {
3141
+ return this.streams.has(sessionId);
3142
+ }
3143
+ /**
3144
+ * Close all active streams
3145
+ */
3146
+ async close() {
3147
+ for (const [sessionId, controller] of this.streams.entries()) {
3148
+ try {
3149
+ controller.close();
3150
+ } catch (error2) {
3151
+ console.debug(
3152
+ `[InMemoryStreamManager] Error closing stream for ${sessionId}:`,
3153
+ error2
3154
+ );
3155
+ }
3156
+ }
3157
+ this.streams.clear();
3158
+ }
3159
+ /**
3160
+ * Get the number of active streams
3161
+ * Useful for monitoring
3162
+ */
3163
+ get size() {
3164
+ return this.streams.size;
3165
+ }
3166
+ };
3167
+
3168
+ // src/server/sessions/streams/redis.ts
3169
+ var RedisStreamManager = class {
3170
+ static {
3171
+ __name(this, "RedisStreamManager");
3172
+ }
3173
+ pubSubClient;
3174
+ client;
3175
+ prefix;
3176
+ heartbeatInterval;
3177
+ textEncoder = new TextEncoder();
3178
+ /**
3179
+ * Map of local controllers (only on this server instance)
3180
+ * Key: sessionId, Value: controller
3181
+ */
3182
+ localControllers = /* @__PURE__ */ new Map();
3183
+ /**
3184
+ * Map of heartbeat intervals for keeping sessions alive
3185
+ * Key: sessionId, Value: interval timer
3186
+ */
3187
+ heartbeats = /* @__PURE__ */ new Map();
3188
+ constructor(config) {
3189
+ this.pubSubClient = config.pubSubClient;
3190
+ this.client = config.client;
3191
+ this.prefix = config.prefix ?? "mcp:stream:";
3192
+ this.heartbeatInterval = config.heartbeatInterval ?? 10;
3193
+ }
3194
+ /**
3195
+ * Get the Redis channel name for a session
3196
+ */
3197
+ getChannel(sessionId) {
3198
+ return `${this.prefix}${sessionId}`;
3199
+ }
3200
+ /**
3201
+ * Get the Redis key for tracking active sessions
3202
+ */
3203
+ getAvailableKey(sessionId) {
3204
+ return `available:${this.prefix}${sessionId}`;
3205
+ }
3206
+ /**
3207
+ * Get the Redis key for the active sessions SET
3208
+ */
3209
+ getActiveSessionsKey() {
3210
+ return `${this.prefix}active`;
3211
+ }
3212
+ /**
3213
+ * Register an active SSE stream and subscribe to Redis channel
3214
+ */
3215
+ async create(sessionId, controller) {
3216
+ try {
3217
+ this.localControllers.set(sessionId, controller);
3218
+ const availableKey = this.getAvailableKey(sessionId);
3219
+ await this.client.set(availableKey, "active");
3220
+ if (this.client.expire) {
3221
+ await this.client.expire(availableKey, this.heartbeatInterval * 2);
3222
+ }
3223
+ const activeSessionsKey = this.getActiveSessionsKey();
3224
+ if (this.client.sAdd) {
3225
+ await this.client.sAdd(activeSessionsKey, sessionId);
3226
+ if (this.client.expire) {
3227
+ await this.client.expire(
3228
+ activeSessionsKey,
3229
+ this.heartbeatInterval * 2
3230
+ );
3231
+ }
3232
+ }
3233
+ const heartbeat = setInterval(async () => {
3234
+ try {
3235
+ if (this.client.expire) {
3236
+ await this.client.expire(availableKey, this.heartbeatInterval * 2);
3237
+ const activeSessionsKey2 = this.getActiveSessionsKey();
3238
+ await this.client.expire(
3239
+ activeSessionsKey2,
3240
+ this.heartbeatInterval * 2
3241
+ );
3242
+ }
3243
+ } catch (error2) {
3244
+ console.warn(
3245
+ `[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
3246
+ error2
3247
+ );
3248
+ }
3249
+ }, this.heartbeatInterval * 1e3);
3250
+ this.heartbeats.set(sessionId, heartbeat);
3251
+ const channel = this.getChannel(sessionId);
3252
+ if (!this.pubSubClient.subscribe) {
3253
+ throw new Error(
3254
+ "[RedisStreamManager] Redis client does not support subscribe method"
3255
+ );
3256
+ }
3257
+ await this.pubSubClient.subscribe(channel, (message) => {
3258
+ const localController = this.localControllers.get(sessionId);
3259
+ if (localController) {
3260
+ try {
3261
+ localController.enqueue(this.textEncoder.encode(message));
3262
+ } catch (error2) {
3263
+ console.warn(
3264
+ `[RedisStreamManager] Failed to enqueue message for ${sessionId}:`,
3265
+ error2
3266
+ );
3267
+ }
3268
+ }
3269
+ });
3270
+ const deleteChannel = `delete:${this.getChannel(sessionId)}`;
3271
+ await this.pubSubClient.subscribe(deleteChannel, async () => {
3272
+ await this.delete(sessionId);
3273
+ });
3274
+ console.log(
3275
+ `[RedisStreamManager] Created stream for session ${sessionId}`
3276
+ );
3277
+ } catch (error2) {
3278
+ console.error(
3279
+ `[RedisStreamManager] Error creating stream for ${sessionId}:`,
3280
+ error2
3281
+ );
3282
+ throw error2;
3283
+ }
3284
+ }
3285
+ /**
3286
+ * Send data to sessions via Redis Pub/Sub
3287
+ *
3288
+ * This works across distributed servers - any server with an active
3289
+ * SSE connection for the target session will receive and forward the message.
3290
+ *
3291
+ * Note: Uses the regular client (not pubSubClient) for publishing.
3292
+ * In node-redis v5+, clients in subscriber mode cannot publish.
3293
+ */
3294
+ async send(sessionIds, data) {
3295
+ try {
3296
+ if (!sessionIds) {
3297
+ const activeSessionsKey = this.getActiveSessionsKey();
3298
+ if (this.client.sMembers) {
3299
+ const sessionIds2 = await this.client.sMembers(activeSessionsKey);
3300
+ for (const sessionId of sessionIds2) {
3301
+ const channel = this.getChannel(sessionId);
3302
+ if (!this.client.publish) {
3303
+ throw new Error(
3304
+ "[RedisStreamManager] Redis client does not support publish method"
3305
+ );
3306
+ }
3307
+ await this.client.publish(channel, data);
3308
+ }
3309
+ } else {
3310
+ const pattern = `available:${this.prefix}*`;
3311
+ const keys = await this.client.keys(pattern);
3312
+ for (const key of keys) {
3313
+ const sessionId = key.replace(`available:${this.prefix}`, "");
3314
+ const channel = this.getChannel(sessionId);
3315
+ if (!this.client.publish) {
3316
+ throw new Error(
3317
+ "[RedisStreamManager] Redis client does not support publish method"
3318
+ );
3319
+ }
3320
+ await this.client.publish(channel, data);
3321
+ }
3322
+ }
3323
+ } else {
3324
+ for (const sessionId of sessionIds) {
3325
+ const channel = this.getChannel(sessionId);
3326
+ if (!this.client.publish) {
3327
+ throw new Error(
3328
+ "[RedisStreamManager] Redis client does not support publish method"
3329
+ );
3330
+ }
3331
+ await this.client.publish(channel, data);
3332
+ }
3333
+ }
3334
+ } catch (error2) {
3335
+ console.error(`[RedisStreamManager] Error sending to sessions:`, error2);
3336
+ throw error2;
3337
+ }
3338
+ }
3339
+ /**
3340
+ * Remove an active SSE stream
3341
+ */
3342
+ async delete(sessionId) {
3343
+ try {
3344
+ const heartbeat = this.heartbeats.get(sessionId);
3345
+ if (heartbeat) {
3346
+ clearInterval(heartbeat);
3347
+ this.heartbeats.delete(sessionId);
3348
+ }
3349
+ const channel = this.getChannel(sessionId);
3350
+ const deleteChannel = `delete:${channel}`;
3351
+ if (!this.pubSubClient.unsubscribe) {
3352
+ throw new Error(
3353
+ "[RedisStreamManager] Redis client does not support unsubscribe method"
3354
+ );
3355
+ }
3356
+ await this.pubSubClient.unsubscribe(channel);
3357
+ await this.pubSubClient.unsubscribe(deleteChannel);
3358
+ if (!this.client.publish) {
3359
+ throw new Error(
3360
+ "[RedisStreamManager] Redis client does not support publish method"
3361
+ );
3362
+ }
3363
+ await this.client.publish(deleteChannel, "");
3364
+ await this.client.del(this.getAvailableKey(sessionId));
3365
+ const activeSessionsKey = this.getActiveSessionsKey();
3366
+ if (this.client.sRem) {
3367
+ await this.client.sRem(activeSessionsKey, sessionId);
3368
+ }
3369
+ const controller = this.localControllers.get(sessionId);
3370
+ if (controller) {
3371
+ try {
3372
+ controller.close();
3373
+ } catch (error2) {
3374
+ console.debug(
3375
+ `[RedisStreamManager] Controller already closed for ${sessionId}`
3376
+ );
3377
+ }
3378
+ this.localControllers.delete(sessionId);
3379
+ }
3380
+ console.log(
3381
+ `[RedisStreamManager] Deleted stream for session ${sessionId}`
3382
+ );
3383
+ } catch (error2) {
3384
+ console.error(
3385
+ `[RedisStreamManager] Error deleting stream for ${sessionId}:`,
3386
+ error2
3387
+ );
3388
+ throw error2;
3389
+ }
3390
+ }
3391
+ /**
3392
+ * Check if a session has an active stream (on ANY server)
3393
+ */
3394
+ async has(sessionId) {
3395
+ try {
3396
+ const availableKey = this.getAvailableKey(sessionId);
3397
+ const exists = await this.client.exists(availableKey);
3398
+ return exists === 1;
3399
+ } catch (error2) {
3400
+ console.error(
3401
+ `[RedisStreamManager] Error checking session ${sessionId}:`,
3402
+ error2
3403
+ );
3404
+ return false;
3405
+ }
3406
+ }
3407
+ /**
3408
+ * Close all connections and cleanup
3409
+ */
3410
+ async close() {
3411
+ try {
3412
+ for (const heartbeat of this.heartbeats.values()) {
3413
+ clearInterval(heartbeat);
3414
+ }
3415
+ this.heartbeats.clear();
3416
+ const activeSessionsKey = this.getActiveSessionsKey();
3417
+ const sessionIdsToCleanup = Array.from(this.localControllers.keys());
3418
+ for (const sessionId of sessionIdsToCleanup) {
3419
+ await this.client.del(this.getAvailableKey(sessionId));
3420
+ if (this.client.sRem) {
3421
+ await this.client.sRem(activeSessionsKey, sessionId);
3422
+ }
3423
+ }
3424
+ for (const controller of this.localControllers.values()) {
3425
+ try {
3426
+ controller.close();
3427
+ } catch (error2) {
3428
+ }
3429
+ }
3430
+ this.localControllers.clear();
3431
+ console.log(`[RedisStreamManager] Closed all streams`);
3432
+ } catch (error2) {
3433
+ console.error(`[RedisStreamManager] Error during close:`, error2);
3434
+ throw error2;
3435
+ }
3436
+ }
3437
+ /**
3438
+ * Get count of active local streams on this server instance
3439
+ */
3440
+ get localSize() {
3441
+ return this.localControllers.size;
3442
+ }
3443
+ };
3444
+
2640
3445
  // src/server/endpoints/mount-mcp.ts
3446
+ import { join as join2 } from "path";
2641
3447
  async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMode2) {
2642
3448
  const { FetchStreamableHTTPServerTransport } = await import("@mcp-use/modelcontextprotocol-sdk/experimental/fetch-streamable-http/index.js");
2643
3449
  const idleTimeoutMs = config.sessionIdleTimeoutMs ?? 3e5;
3450
+ const sessionStore = config.sessionStore ?? (isProductionMode2 ? new InMemorySessionStore() : new FileSystemSessionStore({
3451
+ path: join2(process.cwd(), ".mcp-use", "sessions.json")
3452
+ }));
3453
+ const streamManager = config.streamManager ?? new InMemoryStreamManager();
2644
3454
  const transports = /* @__PURE__ */ new Map();
3455
+ if (config.autoCreateSessionOnInvalidId !== void 0) {
3456
+ console.warn(
3457
+ "[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"
3458
+ );
3459
+ }
2645
3460
  let idleCleanupInterval;
2646
3461
  if (!config.stateless && idleTimeoutMs > 0) {
2647
3462
  idleCleanupInterval = startIdleCleanup(
@@ -2652,15 +3467,34 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
2652
3467
  );
2653
3468
  }
2654
3469
  const handleRequest = /* @__PURE__ */ __name(async (c) => {
2655
- if (config.stateless) {
3470
+ const acceptHeader = c.req.header("Accept") || c.req.header("accept") || "";
3471
+ const clientSupportsSSE = acceptHeader.includes("text/event-stream");
3472
+ const useStatelessMode = config.stateless || !clientSupportsSSE;
3473
+ if (useStatelessMode) {
2656
3474
  const server = mcpServerInstance.getServerForSession();
2657
3475
  const transport = new FetchStreamableHTTPServerTransport({
2658
- sessionIdGenerator: void 0
3476
+ sessionIdGenerator: void 0,
2659
3477
  // No session tracking
3478
+ // Enable plain JSON responses ONLY if client doesn't support SSE
3479
+ // This allows k6/curl to work while maintaining SSE format for compatible clients
3480
+ enableJsonResponse: !clientSupportsSSE
2660
3481
  });
2661
3482
  try {
2662
3483
  await server.connect(transport);
2663
- return await transport.handleRequest(c.req.raw);
3484
+ const request = c.req.raw;
3485
+ if (!clientSupportsSSE) {
3486
+ const modifiedRequest = new Request(request.url, {
3487
+ method: request.method,
3488
+ headers: {
3489
+ ...Object.fromEntries(request.headers.entries()),
3490
+ Accept: "application/json, text/event-stream"
3491
+ },
3492
+ body: request.body,
3493
+ ...request.body && { duplex: "half" }
3494
+ });
3495
+ return await transport.handleRequest(modifiedRequest);
3496
+ }
3497
+ return await transport.handleRequest(request);
2664
3498
  } catch (error2) {
2665
3499
  console.error("[MCP] Stateless request error:", error2);
2666
3500
  transport.close();
@@ -2670,46 +3504,165 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
2670
3504
  } else {
2671
3505
  const sessionId = c.req.header("mcp-session-id");
2672
3506
  if (c.req.method === "HEAD") {
2673
- if (sessionId && sessions.has(sessionId)) {
2674
- sessions.get(sessionId).lastAccessedAt = Date.now();
3507
+ if (sessionId && await sessionStore.has(sessionId)) {
3508
+ const session = await sessionStore.get(sessionId);
3509
+ if (session) {
3510
+ session.lastAccessedAt = Date.now();
3511
+ await sessionStore.set(sessionId, session);
3512
+ }
2675
3513
  }
2676
3514
  return new Response(null, { status: 200 });
2677
3515
  }
3516
+ if (sessionId && await sessionStore.has(sessionId) && !transports.has(sessionId)) {
3517
+ console.log(
3518
+ `[MCP] Session metadata found but transport lost (likely hot reload): ${sessionId} - recreating transport`
3519
+ );
3520
+ const server2 = mcpServerInstance.getServerForSession();
3521
+ const transport2 = new FetchStreamableHTTPServerTransport({
3522
+ sessionIdGenerator: /* @__PURE__ */ __name(() => sessionId, "sessionIdGenerator"),
3523
+ // Reuse existing session ID
3524
+ onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
3525
+ console.log(`[MCP] Session reconnected: ${sid}`);
3526
+ transports.set(sid, transport2);
3527
+ const metadata = await sessionStore.get(sid);
3528
+ const sessionData = {
3529
+ transport: transport2,
3530
+ server: server2,
3531
+ lastAccessedAt: Date.now(),
3532
+ context: c,
3533
+ honoContext: c,
3534
+ ...metadata || {}
3535
+ };
3536
+ sessions.set(sid, sessionData);
3537
+ server2.server.oninitialized = async () => {
3538
+ const clientCapabilities = server2.server.getClientCapabilities();
3539
+ const clientInfo = server2.server.getClientInfo?.() || {};
3540
+ const protocolVersion = server2.server.getProtocolVersion?.() || "unknown";
3541
+ const metadata2 = await sessionStore.get(sid);
3542
+ if (metadata2) {
3543
+ metadata2.clientCapabilities = clientCapabilities;
3544
+ metadata2.clientInfo = clientInfo;
3545
+ metadata2.protocolVersion = String(protocolVersion);
3546
+ await sessionStore.set(sid, metadata2);
3547
+ }
3548
+ const sessionData2 = sessions.get(sid);
3549
+ if (sessionData2) {
3550
+ sessionData2.clientCapabilities = clientCapabilities;
3551
+ }
3552
+ Telemetry.getInstance().trackServerInitialize({
3553
+ protocolVersion: String(protocolVersion),
3554
+ clientInfo: clientInfo || {},
3555
+ clientCapabilities: clientCapabilities || {},
3556
+ sessionId: sid
3557
+ }).catch(
3558
+ (e) => console.debug(`Failed to track server initialize: ${e}`)
3559
+ );
3560
+ if (!isProductionMode2) {
3561
+ console.log(
3562
+ `[MCP] Development mode: Sending list_changed notifications to reconnected session ${sid}`
3563
+ );
3564
+ try {
3565
+ const { sendNotificationToSession: sendNotificationToSession3 } = await import("../../notifications-FLGIFS56.js");
3566
+ await sendNotificationToSession3(
3567
+ sessions,
3568
+ sid,
3569
+ "notifications/tools/list_changed"
3570
+ );
3571
+ await sendNotificationToSession3(
3572
+ sessions,
3573
+ sid,
3574
+ "notifications/resources/list_changed"
3575
+ );
3576
+ await sendNotificationToSession3(
3577
+ sessions,
3578
+ sid,
3579
+ "notifications/prompts/list_changed"
3580
+ );
3581
+ } catch (err) {
3582
+ console.debug(
3583
+ `[MCP] Failed to send list_changed notification:`,
3584
+ err
3585
+ );
3586
+ }
3587
+ }
3588
+ };
3589
+ }, "onsessioninitialized"),
3590
+ onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
3591
+ console.log(`[MCP] Session closed: ${sid}`);
3592
+ transports.delete(sid);
3593
+ await streamManager.delete(sid);
3594
+ await sessionStore.delete(sid);
3595
+ sessions.delete(sid);
3596
+ mcpServerInstance.cleanupSessionSubscriptions?.(sid);
3597
+ }, "onsessionclosed")
3598
+ });
3599
+ await server2.connect(transport2);
3600
+ return transport2.handleRequest(c.req.raw);
3601
+ }
3602
+ if (sessionId && !await sessionStore.has(sessionId)) {
3603
+ console.log(
3604
+ `[MCP] Session not found: ${sessionId} - returning 404 (client should re-initialize)`
3605
+ );
3606
+ return c.json(
3607
+ {
3608
+ jsonrpc: "2.0",
3609
+ error: { code: -32001, message: "Session not found" },
3610
+ id: null
3611
+ },
3612
+ 404
3613
+ );
3614
+ }
2678
3615
  if (sessionId && transports.has(sessionId)) {
2679
3616
  const transport2 = transports.get(sessionId);
2680
- if (sessions.has(sessionId)) {
2681
- const session = sessions.get(sessionId);
2682
- session.lastAccessedAt = Date.now();
2683
- session.context = c;
2684
- session.honoContext = c;
3617
+ const metadata = await sessionStore.get(sessionId);
3618
+ if (metadata) {
3619
+ metadata.lastAccessedAt = Date.now();
3620
+ await sessionStore.set(sessionId, metadata);
3621
+ }
3622
+ const sessionData = sessions.get(sessionId);
3623
+ if (sessionData) {
3624
+ sessionData.lastAccessedAt = Date.now();
3625
+ sessionData.context = c;
3626
+ sessionData.honoContext = c;
2685
3627
  }
2686
3628
  return transport2.handleRequest(c.req.raw);
2687
3629
  }
2688
3630
  const server = mcpServerInstance.getServerForSession();
2689
3631
  const transport = new FetchStreamableHTTPServerTransport({
2690
3632
  sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
2691
- onsessioninitialized: /* @__PURE__ */ __name((sid) => {
3633
+ onsessioninitialized: /* @__PURE__ */ __name(async (sid) => {
2692
3634
  console.log(`[MCP] Session initialized: ${sid}`);
2693
3635
  transports.set(sid, transport);
2694
- sessions.set(sid, {
3636
+ const sessionData = {
2695
3637
  transport,
2696
3638
  server,
2697
3639
  lastAccessedAt: Date.now(),
2698
3640
  context: c,
2699
3641
  honoContext: c
3642
+ };
3643
+ sessions.set(sid, sessionData);
3644
+ await sessionStore.set(sid, {
3645
+ lastAccessedAt: Date.now()
2700
3646
  });
2701
- server.server.oninitialized = () => {
3647
+ server.server.oninitialized = async () => {
2702
3648
  const clientCapabilities = server.server.getClientCapabilities();
2703
3649
  const clientInfo = server.server.getClientInfo?.() || {};
2704
3650
  const protocolVersion = server.server.getProtocolVersion?.() || "unknown";
2705
- if (clientCapabilities && sessions.has(sid)) {
2706
- const session = sessions.get(sid);
2707
- session.clientCapabilities = clientCapabilities;
3651
+ const metadata = await sessionStore.get(sid);
3652
+ if (metadata) {
3653
+ metadata.clientCapabilities = clientCapabilities;
3654
+ metadata.clientInfo = clientInfo;
3655
+ metadata.protocolVersion = String(protocolVersion);
3656
+ await sessionStore.set(sid, metadata);
2708
3657
  console.log(
2709
3658
  `[MCP] Captured client capabilities for session ${sid}:`,
2710
- Object.keys(clientCapabilities)
3659
+ clientCapabilities ? Object.keys(clientCapabilities) : "none"
2711
3660
  );
2712
3661
  }
3662
+ const sessionData2 = sessions.get(sid);
3663
+ if (sessionData2) {
3664
+ sessionData2.clientCapabilities = clientCapabilities;
3665
+ }
2713
3666
  Telemetry.getInstance().trackServerInitialize({
2714
3667
  protocolVersion: String(protocolVersion),
2715
3668
  clientInfo: clientInfo || {},
@@ -2720,9 +3673,11 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
2720
3673
  );
2721
3674
  };
2722
3675
  }, "onsessioninitialized"),
2723
- onsessionclosed: /* @__PURE__ */ __name((sid) => {
3676
+ onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
2724
3677
  console.log(`[MCP] Session closed: ${sid}`);
2725
3678
  transports.delete(sid);
3679
+ await streamManager.delete(sid);
3680
+ await sessionStore.delete(sid);
2726
3681
  sessions.delete(sid);
2727
3682
  mcpServerInstance.cleanupSessionSubscriptions?.(sid);
2728
3683
  }, "onsessionclosed")
@@ -3509,7 +4464,7 @@ var MCPServerClass = class {
3509
4464
  );
3510
4465
  }
3511
4466
  newServer.server.setRequestHandler(
3512
- z2.object({ method: z2.literal("logging/setLevel") }).passthrough(),
4467
+ z3.object({ method: z3.literal("logging/setLevel") }).passthrough(),
3513
4468
  (async (request, extra) => {
3514
4469
  const level = request.params?.level;
3515
4470
  if (!level) {
@@ -3581,6 +4536,9 @@ var MCPServerClass = class {
3581
4536
  getActiveSessions = getActiveSessions;
3582
4537
  sendNotification = sendNotification;
3583
4538
  sendNotificationToSession = sendNotificationToSession2;
4539
+ sendToolsListChanged = sendToolsListChanged;
4540
+ sendResourcesListChanged = sendResourcesListChanged;
4541
+ sendPromptsListChanged = sendPromptsListChanged;
3584
4542
  /**
3585
4543
  * Notify subscribed clients that a resource has been updated
3586
4544
  *
@@ -4436,7 +5394,12 @@ function requireAnyScope(needed) {
4436
5394
  }
4437
5395
  __name(requireAnyScope, "requireAnyScope");
4438
5396
  export {
5397
+ FileSystemSessionStore,
5398
+ InMemorySessionStore,
5399
+ InMemoryStreamManager,
4439
5400
  MCPServer,
5401
+ RedisSessionStore,
5402
+ RedisStreamManager,
4440
5403
  VERSION,
4441
5404
  adaptConnectMiddleware,
4442
5405
  adaptMiddleware,