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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/{chunk-5RTMAOZ6.js → chunk-A4QJRN7Z.js} +5 -1041
- package/dist/{chunk-4LZSXUFM.js → chunk-B7AGEK7F.js} +1 -1
- package/dist/{chunk-TAEHPLGV.js → chunk-GN5HOAV3.js} +664 -136
- package/dist/chunk-QPIDKGV4.js +1246 -0
- package/dist/chunk-UWWLWLS2.js +62 -0
- package/dist/{chunk-ZFZPZ4GE.js → chunk-V77WS6CS.js} +9 -0
- package/dist/{chunk-EBSNALCB.js → chunk-VRHAF2WT.js} +10 -4
- package/dist/{chunk-X7JKFBPN.js → chunk-Y2HHHJQB.js} +159 -8
- package/dist/{chunk-JPKFN73V.js → chunk-ZLZOOXMJ.js} +96 -43
- package/dist/index.cjs +316 -53
- package/dist/index.js +22 -24
- package/dist/notifications-FLGIFS56.js +9 -0
- package/dist/src/agents/index.cjs +153 -47
- package/dist/src/agents/index.d.ts +1 -1
- package/dist/src/agents/index.d.ts.map +1 -1
- package/dist/src/agents/index.js +7 -10
- package/dist/src/agents/mcp_agent.d.ts.map +1 -1
- package/dist/src/{client/prompts.d.ts → agents/prompts/index.d.ts} +3 -3
- package/dist/src/agents/prompts/index.d.ts.map +1 -0
- package/dist/src/browser.cjs +160 -48
- package/dist/src/browser.js +10 -12
- package/dist/src/client/browser.d.ts.map +1 -1
- package/dist/src/client.cjs +3852 -0
- package/dist/src/client.d.ts +2 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +21 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/connectors/http.d.ts +2 -0
- package/dist/src/connectors/http.d.ts.map +1 -1
- package/dist/src/react/index.cjs +313 -52
- package/dist/src/react/index.js +7 -8
- package/dist/src/react/types.d.ts +41 -1
- package/dist/src/react/types.d.ts.map +1 -1
- package/dist/src/react/useMcp.d.ts.map +1 -1
- package/dist/src/server/endpoints/mount-mcp.d.ts.map +1 -1
- package/dist/src/server/index.cjs +1339 -256
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +1119 -156
- package/dist/src/server/mcp-server.d.ts +4 -1
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/notifications/index.d.ts +1 -1
- package/dist/src/server/notifications/index.d.ts.map +1 -1
- package/dist/src/server/notifications/notification-registration.d.ts +51 -0
- package/dist/src/server/notifications/notification-registration.d.ts.map +1 -1
- package/dist/src/server/sessions/index.d.ts +3 -1
- package/dist/src/server/sessions/index.d.ts.map +1 -1
- package/dist/src/server/sessions/session-manager.d.ts +30 -16
- package/dist/src/server/sessions/session-manager.d.ts.map +1 -1
- package/dist/src/server/sessions/stores/filesystem.d.ts +121 -0
- package/dist/src/server/sessions/stores/filesystem.d.ts.map +1 -0
- package/dist/src/server/sessions/stores/index.d.ts +94 -0
- package/dist/src/server/sessions/stores/index.d.ts.map +1 -0
- package/dist/src/server/sessions/stores/memory.d.ts +82 -0
- package/dist/src/server/sessions/stores/memory.d.ts.map +1 -0
- package/dist/src/server/sessions/stores/redis.d.ts +164 -0
- package/dist/src/server/sessions/stores/redis.d.ts.map +1 -0
- package/dist/src/server/sessions/streams/index.d.ts +77 -0
- package/dist/src/server/sessions/streams/index.d.ts.map +1 -0
- package/dist/src/server/sessions/streams/memory.d.ts +76 -0
- package/dist/src/server/sessions/streams/memory.d.ts.map +1 -0
- package/dist/src/server/sessions/streams/redis.d.ts +146 -0
- package/dist/src/server/sessions/streams/redis.d.ts.map +1 -0
- package/dist/src/server/types/common.d.ts +82 -28
- package/dist/src/server/types/common.d.ts.map +1 -1
- package/dist/src/server/types/widget.d.ts +2 -2
- package/dist/src/server/types/widget.d.ts.map +1 -1
- package/dist/src/server/utils/response-helpers.d.ts +4 -2
- package/dist/src/server/utils/response-helpers.d.ts.map +1 -1
- package/dist/src/server/widgets/mount-widgets-dev.d.ts.map +1 -1
- package/dist/src/server/widgets/ui-resource-registration.d.ts.map +1 -1
- package/dist/src/task_managers/index.d.ts +10 -0
- package/dist/src/task_managers/index.d.ts.map +1 -1
- package/dist/src/task_managers/sse.d.ts +34 -1
- package/dist/src/task_managers/sse.d.ts.map +1 -1
- package/dist/src/task_managers/streamable_http.d.ts +8 -2
- package/dist/src/task_managers/streamable_http.d.ts.map +1 -1
- package/dist/src/telemetry/telemetry.d.ts +1 -0
- package/dist/src/telemetry/telemetry.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/{tool-execution-helpers-EYAIJERC.js → tool-execution-helpers-ZUA5D5IO.js} +2 -2
- package/dist/tsup.config.d.ts.map +1 -1
- package/package.json +62 -52
- package/dist/chunk-GVU7C2ZD.js +0 -12
- package/dist/chunk-JZNXOM7C.js +0 -204
- package/dist/chunk-XKTBHYNM.js +0 -491
- package/dist/src/client/prompts.cjs +0 -407
- package/dist/src/client/prompts.d.ts.map +0 -1
- package/dist/src/client/prompts.js +0 -11
package/dist/src/server/index.js
CHANGED
|
@@ -7,13 +7,18 @@ import {
|
|
|
7
7
|
createEnhancedContext,
|
|
8
8
|
findSessionContext,
|
|
9
9
|
isValidLogLevel
|
|
10
|
-
} from "../../chunk-
|
|
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-
|
|
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
|
|
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
|
|
318
|
-
const
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1517
|
+
const schemaField = metadata.props || metadata.inputs;
|
|
1518
|
+
if (schemaField) {
|
|
1508
1519
|
try {
|
|
1509
|
-
metadata.
|
|
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
|
|
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
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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
|
|
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 =
|
|
1970
|
+
zodType = z2.string();
|
|
1949
1971
|
break;
|
|
1950
1972
|
case "number":
|
|
1951
|
-
zodType =
|
|
1973
|
+
zodType = z2.number();
|
|
1952
1974
|
break;
|
|
1953
1975
|
case "boolean":
|
|
1954
|
-
zodType =
|
|
1976
|
+
zodType = z2.boolean();
|
|
1955
1977
|
break;
|
|
1956
1978
|
case "object":
|
|
1957
|
-
zodType =
|
|
1979
|
+
zodType = z2.object({});
|
|
1958
1980
|
break;
|
|
1959
1981
|
case "array":
|
|
1960
|
-
zodType =
|
|
1982
|
+
zodType = z2.array(z2.any());
|
|
1961
1983
|
break;
|
|
1962
1984
|
default:
|
|
1963
|
-
zodType =
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
2674
|
-
|
|
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
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
-
|
|
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,
|