mcp-use 1.5.0-canary.4 → 1.5.0-canary.6
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-WERYJ6PF.js → chunk-2AOGMX4T.js} +1 -1
- package/dist/{chunk-DSBKVAWD.js → chunk-2JBWOW4S.js} +152 -0
- package/dist/{chunk-UT7O4SIJ.js → chunk-BWOTID2D.js} +209 -75
- package/dist/{chunk-GPAOZN2F.js → chunk-QRABML5H.js} +15 -3
- package/dist/index.cjs +395 -84
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -10
- package/dist/src/agents/index.cjs +1 -0
- package/dist/src/agents/index.js +2 -2
- package/dist/src/browser.cjs +351 -72
- package/dist/src/browser.d.ts +2 -0
- package/dist/src/browser.d.ts.map +1 -1
- package/dist/src/browser.js +2 -2
- package/dist/src/client/browser.d.ts.map +1 -1
- package/dist/src/client/prompts.cjs +3 -0
- package/dist/src/client/prompts.js +2 -2
- package/dist/src/client.d.ts +8 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/config.d.ts +2 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/connectors/base.d.ts +79 -1
- package/dist/src/connectors/base.d.ts.map +1 -1
- package/dist/src/connectors/http.d.ts +1 -0
- package/dist/src/connectors/http.d.ts.map +1 -1
- package/dist/src/connectors/stdio.d.ts.map +1 -1
- package/dist/src/connectors/websocket.d.ts +6 -0
- package/dist/src/connectors/websocket.d.ts.map +1 -1
- package/dist/src/react/index.cjs +365 -73
- package/dist/src/react/index.js +3 -3
- package/dist/src/react/types.d.ts +9 -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/adapters/mcp-ui-adapter.d.ts +1 -0
- package/dist/src/server/adapters/mcp-ui-adapter.d.ts.map +1 -1
- package/dist/src/server/index.cjs +432 -27
- package/dist/src/server/index.js +432 -27
- package/dist/src/server/mcp-server.d.ts +179 -2
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/types/common.d.ts +2 -0
- package/dist/src/server/types/common.d.ts.map +1 -1
- package/dist/src/session.d.ts +40 -1
- package/dist/src/session.d.ts.map +1 -1
- package/package.json +10 -4
|
@@ -108,7 +108,8 @@ function createAppsSdkResource(uri, htmlTemplate, metadata) {
|
|
|
108
108
|
}
|
|
109
109
|
__name(createAppsSdkResource, "createAppsSdkResource");
|
|
110
110
|
function createUIResourceFromDefinition(definition, params, config) {
|
|
111
|
-
const
|
|
111
|
+
const buildIdPart = config.buildId ? `-${config.buildId}` : "";
|
|
112
|
+
const uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}${buildIdPart}.html` : `ui://widget/${definition.name}${buildIdPart}`;
|
|
112
113
|
const encoding = definition.encoding || "text";
|
|
113
114
|
switch (definition.type) {
|
|
114
115
|
case "externalUrl": {
|
|
@@ -334,6 +335,10 @@ async function requestLogger(c, next) {
|
|
|
334
335
|
__name(requestLogger, "requestLogger");
|
|
335
336
|
|
|
336
337
|
// src/server/mcp-server.ts
|
|
338
|
+
function generateUUID() {
|
|
339
|
+
return globalThis.crypto.randomUUID();
|
|
340
|
+
}
|
|
341
|
+
__name(generateUUID, "generateUUID");
|
|
337
342
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
338
343
|
var isDeno = typeof globalThis.Deno !== "undefined";
|
|
339
344
|
function getEnv(key) {
|
|
@@ -429,6 +434,9 @@ var McpServer = class {
|
|
|
429
434
|
registeredTools = [];
|
|
430
435
|
registeredPrompts = [];
|
|
431
436
|
registeredResources = [];
|
|
437
|
+
buildId;
|
|
438
|
+
sessions = /* @__PURE__ */ new Map();
|
|
439
|
+
idleCleanupInterval;
|
|
432
440
|
/**
|
|
433
441
|
* Creates a new MCP server instance with Hono integration
|
|
434
442
|
*
|
|
@@ -461,7 +469,9 @@ var McpServer = class {
|
|
|
461
469
|
"mcp-session-id",
|
|
462
470
|
"X-Proxy-Token",
|
|
463
471
|
"X-Target-URL"
|
|
464
|
-
]
|
|
472
|
+
],
|
|
473
|
+
// Expose mcp-session-id so browser clients can read it from responses
|
|
474
|
+
exposeHeaders: ["mcp-session-id"]
|
|
465
475
|
})
|
|
466
476
|
);
|
|
467
477
|
this.app.use("*", requestLogger);
|
|
@@ -701,6 +711,11 @@ var McpServer = class {
|
|
|
701
711
|
*/
|
|
702
712
|
tool(toolDefinition) {
|
|
703
713
|
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
714
|
+
const context = {
|
|
715
|
+
sample: /* @__PURE__ */ __name(async (params, options) => {
|
|
716
|
+
return await this.createMessage(params, options);
|
|
717
|
+
}, "sample")
|
|
718
|
+
};
|
|
704
719
|
this.server.registerTool(
|
|
705
720
|
toolDefinition.name,
|
|
706
721
|
{
|
|
@@ -711,6 +726,9 @@ var McpServer = class {
|
|
|
711
726
|
_meta: toolDefinition._meta
|
|
712
727
|
},
|
|
713
728
|
async (params) => {
|
|
729
|
+
if (toolDefinition.cb.length >= 2) {
|
|
730
|
+
return await toolDefinition.cb(params, context);
|
|
731
|
+
}
|
|
714
732
|
return await toolDefinition.cb(params);
|
|
715
733
|
}
|
|
716
734
|
);
|
|
@@ -767,6 +785,46 @@ var McpServer = class {
|
|
|
767
785
|
this.registeredPrompts.push(promptDefinition.name);
|
|
768
786
|
return this;
|
|
769
787
|
}
|
|
788
|
+
/**
|
|
789
|
+
* Request LLM sampling from connected clients.
|
|
790
|
+
*
|
|
791
|
+
* This method allows server tools to request LLM completions from clients
|
|
792
|
+
* that support the sampling capability. The client will handle model selection,
|
|
793
|
+
* user approval (human-in-the-loop), and return the generated response.
|
|
794
|
+
*
|
|
795
|
+
* @param params - Sampling request parameters including messages, model preferences, etc.
|
|
796
|
+
* @param options - Optional request options (timeouts, cancellation, etc.)
|
|
797
|
+
* @returns Promise resolving to the generated message from the client's LLM
|
|
798
|
+
*
|
|
799
|
+
* @example
|
|
800
|
+
* ```typescript
|
|
801
|
+
* // In a tool callback
|
|
802
|
+
* server.tool({
|
|
803
|
+
* name: 'analyze-sentiment',
|
|
804
|
+
* description: 'Analyze sentiment using LLM',
|
|
805
|
+
* inputs: [{ name: 'text', type: 'string', required: true }],
|
|
806
|
+
* cb: async (params, ctx) => {
|
|
807
|
+
* const result = await ctx.sample({
|
|
808
|
+
* messages: [{
|
|
809
|
+
* role: 'user',
|
|
810
|
+
* content: { type: 'text', text: `Analyze sentiment: ${params.text}` }
|
|
811
|
+
* }],
|
|
812
|
+
* modelPreferences: {
|
|
813
|
+
* intelligencePriority: 0.8,
|
|
814
|
+
* speedPriority: 0.5
|
|
815
|
+
* }
|
|
816
|
+
* });
|
|
817
|
+
* return {
|
|
818
|
+
* content: [{ type: 'text', text: result.content.text }]
|
|
819
|
+
* };
|
|
820
|
+
* }
|
|
821
|
+
* })
|
|
822
|
+
* ```
|
|
823
|
+
*/
|
|
824
|
+
async createMessage(params, options) {
|
|
825
|
+
console.log("createMessage", params, options);
|
|
826
|
+
return await this.server.server.createMessage(params, options);
|
|
827
|
+
}
|
|
770
828
|
/**
|
|
771
829
|
* Register a UI widget as both a tool and a resource
|
|
772
830
|
*
|
|
@@ -840,19 +898,19 @@ var McpServer = class {
|
|
|
840
898
|
let mimeType;
|
|
841
899
|
switch (definition.type) {
|
|
842
900
|
case "externalUrl":
|
|
843
|
-
resourceUri =
|
|
901
|
+
resourceUri = this.generateWidgetUri(definition.widget);
|
|
844
902
|
mimeType = "text/uri-list";
|
|
845
903
|
break;
|
|
846
904
|
case "rawHtml":
|
|
847
|
-
resourceUri =
|
|
905
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
848
906
|
mimeType = "text/html";
|
|
849
907
|
break;
|
|
850
908
|
case "remoteDom":
|
|
851
|
-
resourceUri =
|
|
909
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
852
910
|
mimeType = "application/vnd.mcp-ui.remote-dom+javascript";
|
|
853
911
|
break;
|
|
854
912
|
case "appsSdk":
|
|
855
|
-
resourceUri =
|
|
913
|
+
resourceUri = this.generateWidgetUri(definition.name, ".html");
|
|
856
914
|
mimeType = "text/html+skybridge";
|
|
857
915
|
break;
|
|
858
916
|
default:
|
|
@@ -871,16 +929,19 @@ var McpServer = class {
|
|
|
871
929
|
readCallback: /* @__PURE__ */ __name(async () => {
|
|
872
930
|
const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
|
|
873
931
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
932
|
+
uiResource.resource.uri = resourceUri;
|
|
874
933
|
return {
|
|
875
934
|
contents: [uiResource.resource]
|
|
876
935
|
};
|
|
877
936
|
}, "readCallback")
|
|
878
937
|
});
|
|
879
938
|
if (definition.type === "appsSdk") {
|
|
939
|
+
const buildIdPart = this.buildId ? `-${this.buildId}` : "";
|
|
940
|
+
const uriTemplate = `ui://widget/${definition.name}${buildIdPart}-{id}.html`;
|
|
880
941
|
this.resourceTemplate({
|
|
881
942
|
name: `${definition.name}-dynamic`,
|
|
882
943
|
resourceTemplate: {
|
|
883
|
-
uriTemplate
|
|
944
|
+
uriTemplate,
|
|
884
945
|
name: definition.title || definition.name,
|
|
885
946
|
description: definition.description,
|
|
886
947
|
mimeType
|
|
@@ -891,6 +952,7 @@ var McpServer = class {
|
|
|
891
952
|
annotations: definition.annotations,
|
|
892
953
|
readCallback: /* @__PURE__ */ __name(async (uri, params) => {
|
|
893
954
|
const uiResource = this.createWidgetUIResource(definition, {});
|
|
955
|
+
uiResource.resource.uri = uri.toString();
|
|
894
956
|
return {
|
|
895
957
|
contents: [uiResource.resource]
|
|
896
958
|
};
|
|
@@ -922,7 +984,11 @@ var McpServer = class {
|
|
|
922
984
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
923
985
|
if (definition.type === "appsSdk") {
|
|
924
986
|
const randomId = Math.random().toString(36).substring(2, 15);
|
|
925
|
-
const uniqueUri =
|
|
987
|
+
const uniqueUri = this.generateWidgetUri(
|
|
988
|
+
definition.name,
|
|
989
|
+
".html",
|
|
990
|
+
randomId
|
|
991
|
+
);
|
|
926
992
|
const uniqueToolMetadata = {
|
|
927
993
|
...toolMetadata,
|
|
928
994
|
"openai/outputTemplate": uniqueUri
|
|
@@ -979,7 +1045,8 @@ var McpServer = class {
|
|
|
979
1045
|
}
|
|
980
1046
|
const urlConfig = {
|
|
981
1047
|
baseUrl: configBaseUrl,
|
|
982
|
-
port: configPort
|
|
1048
|
+
port: configPort,
|
|
1049
|
+
buildId: this.buildId
|
|
983
1050
|
};
|
|
984
1051
|
const uiResource = createUIResourceFromDefinition(
|
|
985
1052
|
definition,
|
|
@@ -994,6 +1061,25 @@ var McpServer = class {
|
|
|
994
1061
|
}
|
|
995
1062
|
return uiResource;
|
|
996
1063
|
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Generate a widget URI with optional build ID for cache busting
|
|
1066
|
+
*
|
|
1067
|
+
* @private
|
|
1068
|
+
* @param widgetName - Widget name/identifier
|
|
1069
|
+
* @param extension - Optional file extension (e.g., '.html')
|
|
1070
|
+
* @param suffix - Optional suffix (e.g., random ID for dynamic URIs)
|
|
1071
|
+
* @returns Widget URI with build ID if available
|
|
1072
|
+
*/
|
|
1073
|
+
generateWidgetUri(widgetName, extension = "", suffix = "") {
|
|
1074
|
+
const parts = [widgetName];
|
|
1075
|
+
if (this.buildId) {
|
|
1076
|
+
parts.push(this.buildId);
|
|
1077
|
+
}
|
|
1078
|
+
if (suffix) {
|
|
1079
|
+
parts.push(suffix);
|
|
1080
|
+
}
|
|
1081
|
+
return `ui://widget/${parts.join("-")}${extension}`;
|
|
1082
|
+
}
|
|
997
1083
|
/**
|
|
998
1084
|
* Build a complete URL for a widget including query parameters
|
|
999
1085
|
*
|
|
@@ -1539,6 +1625,10 @@ if (container && Component) {
|
|
|
1539
1625
|
"utf8"
|
|
1540
1626
|
);
|
|
1541
1627
|
const manifest = JSON.parse(manifestContent);
|
|
1628
|
+
if (manifest.buildId && typeof manifest.buildId === "string") {
|
|
1629
|
+
this.buildId = manifest.buildId;
|
|
1630
|
+
console.log(`[WIDGETS] Build ID: ${this.buildId}`);
|
|
1631
|
+
}
|
|
1542
1632
|
if (manifest.widgets && typeof manifest.widgets === "object" && !Array.isArray(manifest.widgets)) {
|
|
1543
1633
|
widgets = Object.keys(manifest.widgets);
|
|
1544
1634
|
widgetsMetadata = manifest.widgets;
|
|
@@ -1708,8 +1798,7 @@ if (container && Component) {
|
|
|
1708
1798
|
*
|
|
1709
1799
|
* Sets up the HTTP transport layer for the MCP server, creating endpoints for
|
|
1710
1800
|
* Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup.
|
|
1711
|
-
*
|
|
1712
|
-
* concurrent client connections.
|
|
1801
|
+
* Transports are reused per session ID to maintain state across requests.
|
|
1713
1802
|
*
|
|
1714
1803
|
* This method is called automatically when the server starts listening and ensures
|
|
1715
1804
|
* that MCP clients can communicate with the server over HTTP.
|
|
@@ -1727,6 +1816,55 @@ if (container && Component) {
|
|
|
1727
1816
|
if (this.mcpMounted) return;
|
|
1728
1817
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1729
1818
|
const endpoint = "/mcp";
|
|
1819
|
+
const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
|
|
1820
|
+
const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
|
|
1821
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1822
|
+
const session = this.sessions.get(sessionId);
|
|
1823
|
+
session.lastAccessedAt = Date.now();
|
|
1824
|
+
return session.transport;
|
|
1825
|
+
}
|
|
1826
|
+
if (!isInit && sessionId) {
|
|
1827
|
+
return null;
|
|
1828
|
+
}
|
|
1829
|
+
if (!isInit && !sessionId) {
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1832
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1833
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
1834
|
+
enableJsonResponse: true,
|
|
1835
|
+
allowedOrigins: this.config.allowedOrigins,
|
|
1836
|
+
enableDnsRebindingProtection: this.config.allowedOrigins !== void 0 && this.config.allowedOrigins.length > 0,
|
|
1837
|
+
onsessioninitialized: /* @__PURE__ */ __name((id) => {
|
|
1838
|
+
if (id) {
|
|
1839
|
+
this.sessions.set(id, {
|
|
1840
|
+
transport,
|
|
1841
|
+
lastAccessedAt: Date.now()
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
}, "onsessioninitialized"),
|
|
1845
|
+
onsessionclosed: /* @__PURE__ */ __name((id) => {
|
|
1846
|
+
if (id) {
|
|
1847
|
+
this.sessions.delete(id);
|
|
1848
|
+
}
|
|
1849
|
+
}, "onsessionclosed")
|
|
1850
|
+
});
|
|
1851
|
+
await this.server.connect(transport);
|
|
1852
|
+
return transport;
|
|
1853
|
+
}, "getOrCreateTransport");
|
|
1854
|
+
if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
|
|
1855
|
+
this.idleCleanupInterval = setInterval(() => {
|
|
1856
|
+
const now = Date.now();
|
|
1857
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1858
|
+
if (now - session.lastAccessedAt > idleTimeoutMs) {
|
|
1859
|
+
try {
|
|
1860
|
+
session.transport.close();
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
}
|
|
1863
|
+
this.sessions.delete(sessionId);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}, 6e4);
|
|
1867
|
+
}
|
|
1730
1868
|
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1731
1869
|
const req = c.req.raw;
|
|
1732
1870
|
const responseBody = [];
|
|
@@ -1836,21 +1974,51 @@ if (container && Component) {
|
|
|
1836
1974
|
}, "createExpressLikeObjects");
|
|
1837
1975
|
this.app.post(endpoint, async (c) => {
|
|
1838
1976
|
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1977
|
+
let body = {};
|
|
1839
1978
|
try {
|
|
1840
|
-
|
|
1979
|
+
body = await c.req.json();
|
|
1980
|
+
expressReq.body = body;
|
|
1841
1981
|
} catch {
|
|
1842
1982
|
expressReq.body = {};
|
|
1843
1983
|
}
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1984
|
+
const isInit = body?.method === "initialize";
|
|
1985
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
1986
|
+
const transport = await getOrCreateTransport(sessionId, isInit);
|
|
1987
|
+
if (!transport) {
|
|
1988
|
+
if (sessionId) {
|
|
1989
|
+
return c.json(
|
|
1990
|
+
{
|
|
1991
|
+
jsonrpc: "2.0",
|
|
1992
|
+
error: {
|
|
1993
|
+
code: -32e3,
|
|
1994
|
+
message: "Session not found or expired"
|
|
1995
|
+
},
|
|
1996
|
+
id: null
|
|
1997
|
+
},
|
|
1998
|
+
404
|
|
1999
|
+
);
|
|
2000
|
+
} else {
|
|
2001
|
+
return c.json(
|
|
2002
|
+
{
|
|
2003
|
+
jsonrpc: "2.0",
|
|
2004
|
+
error: {
|
|
2005
|
+
code: -32e3,
|
|
2006
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2007
|
+
},
|
|
2008
|
+
id: null
|
|
2009
|
+
},
|
|
2010
|
+
400
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2015
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2016
|
+
}
|
|
1848
2017
|
if (expressRes._closeHandler) {
|
|
1849
2018
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1850
2019
|
transport.close();
|
|
1851
2020
|
});
|
|
1852
2021
|
}
|
|
1853
|
-
await this.server.connect(transport);
|
|
1854
2022
|
await this.waitForRequestComplete(
|
|
1855
2023
|
transport,
|
|
1856
2024
|
expressReq,
|
|
@@ -1864,10 +2032,38 @@ if (container && Component) {
|
|
|
1864
2032
|
return c.text("", 200);
|
|
1865
2033
|
});
|
|
1866
2034
|
this.app.get(endpoint, async (c) => {
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2035
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2036
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2037
|
+
if (!transport) {
|
|
2038
|
+
if (sessionId) {
|
|
2039
|
+
return c.json(
|
|
2040
|
+
{
|
|
2041
|
+
jsonrpc: "2.0",
|
|
2042
|
+
error: {
|
|
2043
|
+
code: -32e3,
|
|
2044
|
+
message: "Session not found or expired"
|
|
2045
|
+
},
|
|
2046
|
+
id: null
|
|
2047
|
+
},
|
|
2048
|
+
404
|
|
2049
|
+
);
|
|
2050
|
+
} else {
|
|
2051
|
+
return c.json(
|
|
2052
|
+
{
|
|
2053
|
+
jsonrpc: "2.0",
|
|
2054
|
+
error: {
|
|
2055
|
+
code: -32e3,
|
|
2056
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2057
|
+
},
|
|
2058
|
+
id: null
|
|
2059
|
+
},
|
|
2060
|
+
400
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2065
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2066
|
+
}
|
|
1871
2067
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1872
2068
|
transport.close();
|
|
1873
2069
|
});
|
|
@@ -1963,7 +2159,6 @@ if (container && Component) {
|
|
|
1963
2159
|
headers: c.req.header(),
|
|
1964
2160
|
method: c.req.method
|
|
1965
2161
|
};
|
|
1966
|
-
await this.server.connect(transport);
|
|
1967
2162
|
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
1968
2163
|
console.error("MCP Transport error:", err);
|
|
1969
2164
|
try {
|
|
@@ -1975,14 +2170,38 @@ if (container && Component) {
|
|
|
1975
2170
|
});
|
|
1976
2171
|
this.app.delete(endpoint, async (c) => {
|
|
1977
2172
|
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1978
|
-
const
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2173
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2174
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2175
|
+
if (!transport) {
|
|
2176
|
+
if (sessionId) {
|
|
2177
|
+
return c.json(
|
|
2178
|
+
{
|
|
2179
|
+
jsonrpc: "2.0",
|
|
2180
|
+
error: {
|
|
2181
|
+
code: -32e3,
|
|
2182
|
+
message: "Session not found or expired"
|
|
2183
|
+
},
|
|
2184
|
+
id: null
|
|
2185
|
+
},
|
|
2186
|
+
404
|
|
2187
|
+
);
|
|
2188
|
+
} else {
|
|
2189
|
+
return c.json(
|
|
2190
|
+
{
|
|
2191
|
+
jsonrpc: "2.0",
|
|
2192
|
+
error: {
|
|
2193
|
+
code: -32e3,
|
|
2194
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2195
|
+
},
|
|
2196
|
+
id: null
|
|
2197
|
+
},
|
|
2198
|
+
400
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
1982
2202
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1983
2203
|
transport.close();
|
|
1984
2204
|
});
|
|
1985
|
-
await this.server.connect(transport);
|
|
1986
2205
|
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
1987
2206
|
const response = getResponse();
|
|
1988
2207
|
if (response) {
|
|
@@ -2198,6 +2417,192 @@ if (container && Component) {
|
|
|
2198
2417
|
return result;
|
|
2199
2418
|
};
|
|
2200
2419
|
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Get array of active session IDs
|
|
2422
|
+
*
|
|
2423
|
+
* Returns an array of all currently active session IDs. This is useful for
|
|
2424
|
+
* sending targeted notifications to specific clients or iterating over
|
|
2425
|
+
* connected clients.
|
|
2426
|
+
*
|
|
2427
|
+
* Note: This only works in stateful mode. In stateless mode (edge environments),
|
|
2428
|
+
* this will return an empty array.
|
|
2429
|
+
*
|
|
2430
|
+
* @returns Array of active session ID strings
|
|
2431
|
+
*
|
|
2432
|
+
* @example
|
|
2433
|
+
* ```typescript
|
|
2434
|
+
* const sessions = server.getActiveSessions();
|
|
2435
|
+
* console.log(`${sessions.length} clients connected`);
|
|
2436
|
+
*
|
|
2437
|
+
* // Send notification to first connected client
|
|
2438
|
+
* if (sessions.length > 0) {
|
|
2439
|
+
* server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
|
|
2440
|
+
* }
|
|
2441
|
+
* ```
|
|
2442
|
+
*/
|
|
2443
|
+
getActiveSessions() {
|
|
2444
|
+
return Array.from(this.sessions.keys());
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Send a notification to all connected clients
|
|
2448
|
+
*
|
|
2449
|
+
* Broadcasts a JSON-RPC notification to all active sessions. Notifications are
|
|
2450
|
+
* one-way messages that do not expect a response from the client.
|
|
2451
|
+
*
|
|
2452
|
+
* Note: This only works in stateful mode with active sessions. If no sessions
|
|
2453
|
+
* are connected, the notification is silently discarded (per MCP spec: "server MAY send").
|
|
2454
|
+
*
|
|
2455
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2456
|
+
* @param params - Optional parameters to include in the notification
|
|
2457
|
+
*
|
|
2458
|
+
* @example
|
|
2459
|
+
* ```typescript
|
|
2460
|
+
* // Send a simple notification to all clients
|
|
2461
|
+
* server.sendNotification("custom/server-status", {
|
|
2462
|
+
* status: "ready",
|
|
2463
|
+
* timestamp: new Date().toISOString()
|
|
2464
|
+
* });
|
|
2465
|
+
*
|
|
2466
|
+
* // Notify all clients that resources have changed
|
|
2467
|
+
* server.sendNotification("notifications/resources/list_changed");
|
|
2468
|
+
* ```
|
|
2469
|
+
*/
|
|
2470
|
+
async sendNotification(method, params) {
|
|
2471
|
+
const notification = {
|
|
2472
|
+
jsonrpc: "2.0",
|
|
2473
|
+
method,
|
|
2474
|
+
...params && { params }
|
|
2475
|
+
};
|
|
2476
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2477
|
+
try {
|
|
2478
|
+
await session.transport.send(notification);
|
|
2479
|
+
} catch (error) {
|
|
2480
|
+
console.warn(
|
|
2481
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2482
|
+
error
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Send a notification to a specific client session
|
|
2489
|
+
*
|
|
2490
|
+
* Sends a JSON-RPC notification to a single client identified by their session ID.
|
|
2491
|
+
* This allows sending customized notifications to individual clients.
|
|
2492
|
+
*
|
|
2493
|
+
* Note: This only works in stateful mode. If the session ID doesn't exist,
|
|
2494
|
+
* the notification is silently discarded.
|
|
2495
|
+
*
|
|
2496
|
+
* @param sessionId - The target session ID (from getActiveSessions())
|
|
2497
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2498
|
+
* @param params - Optional parameters to include in the notification
|
|
2499
|
+
* @returns true if the notification was sent, false if session not found
|
|
2500
|
+
*
|
|
2501
|
+
* @example
|
|
2502
|
+
* ```typescript
|
|
2503
|
+
* const sessions = server.getActiveSessions();
|
|
2504
|
+
*
|
|
2505
|
+
* // Send different messages to different clients
|
|
2506
|
+
* sessions.forEach((sessionId, index) => {
|
|
2507
|
+
* server.sendNotificationToSession(sessionId, "custom/welcome", {
|
|
2508
|
+
* message: `Hello client #${index + 1}!`,
|
|
2509
|
+
* clientNumber: index + 1
|
|
2510
|
+
* });
|
|
2511
|
+
* });
|
|
2512
|
+
* ```
|
|
2513
|
+
*/
|
|
2514
|
+
async sendNotificationToSession(sessionId, method, params) {
|
|
2515
|
+
const session = this.sessions.get(sessionId);
|
|
2516
|
+
if (!session) {
|
|
2517
|
+
return false;
|
|
2518
|
+
}
|
|
2519
|
+
const notification = {
|
|
2520
|
+
jsonrpc: "2.0",
|
|
2521
|
+
method,
|
|
2522
|
+
...params && { params }
|
|
2523
|
+
};
|
|
2524
|
+
try {
|
|
2525
|
+
await session.transport.send(notification);
|
|
2526
|
+
return true;
|
|
2527
|
+
} catch (error) {
|
|
2528
|
+
console.warn(
|
|
2529
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2530
|
+
error
|
|
2531
|
+
);
|
|
2532
|
+
return false;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
// Store the roots changed callback
|
|
2536
|
+
onRootsChangedCallback;
|
|
2537
|
+
/**
|
|
2538
|
+
* Register a callback for when a client's roots change
|
|
2539
|
+
*
|
|
2540
|
+
* When a client sends a `notifications/roots/list_changed` notification,
|
|
2541
|
+
* the server will automatically request the updated roots list and call
|
|
2542
|
+
* this callback with the new roots.
|
|
2543
|
+
*
|
|
2544
|
+
* @param callback - Function called with the updated roots array
|
|
2545
|
+
*
|
|
2546
|
+
* @example
|
|
2547
|
+
* ```typescript
|
|
2548
|
+
* server.onRootsChanged(async (roots) => {
|
|
2549
|
+
* console.log("Client roots updated:", roots);
|
|
2550
|
+
* roots.forEach(root => {
|
|
2551
|
+
* console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
|
|
2552
|
+
* });
|
|
2553
|
+
* });
|
|
2554
|
+
* ```
|
|
2555
|
+
*/
|
|
2556
|
+
onRootsChanged(callback) {
|
|
2557
|
+
this.onRootsChangedCallback = callback;
|
|
2558
|
+
return this;
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Request the current roots list from a specific client session
|
|
2562
|
+
*
|
|
2563
|
+
* This sends a `roots/list` request to the client and returns
|
|
2564
|
+
* the list of roots the client has configured.
|
|
2565
|
+
*
|
|
2566
|
+
* @param sessionId - The session ID of the client to query
|
|
2567
|
+
* @returns Array of roots, or null if the session doesn't exist or request fails
|
|
2568
|
+
*
|
|
2569
|
+
* @example
|
|
2570
|
+
* ```typescript
|
|
2571
|
+
* const sessions = server.getActiveSessions();
|
|
2572
|
+
* if (sessions.length > 0) {
|
|
2573
|
+
* const roots = await server.listRoots(sessions[0]);
|
|
2574
|
+
* if (roots) {
|
|
2575
|
+
* console.log(`Client has ${roots.length} roots:`);
|
|
2576
|
+
* roots.forEach(r => console.log(` - ${r.uri}`));
|
|
2577
|
+
* }
|
|
2578
|
+
* }
|
|
2579
|
+
* ```
|
|
2580
|
+
*/
|
|
2581
|
+
async listRoots(sessionId) {
|
|
2582
|
+
const session = this.sessions.get(sessionId);
|
|
2583
|
+
if (!session) {
|
|
2584
|
+
return null;
|
|
2585
|
+
}
|
|
2586
|
+
try {
|
|
2587
|
+
const request = {
|
|
2588
|
+
jsonrpc: "2.0",
|
|
2589
|
+
id: generateUUID(),
|
|
2590
|
+
method: "roots/list",
|
|
2591
|
+
params: {}
|
|
2592
|
+
};
|
|
2593
|
+
const response = await session.transport.send(request);
|
|
2594
|
+
if (response && typeof response === "object" && "roots" in response) {
|
|
2595
|
+
return response.roots;
|
|
2596
|
+
}
|
|
2597
|
+
return [];
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
console.warn(
|
|
2600
|
+
`[MCP] Failed to list roots from session ${sessionId}:`,
|
|
2601
|
+
error
|
|
2602
|
+
);
|
|
2603
|
+
return null;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2201
2606
|
/**
|
|
2202
2607
|
* Mount MCP Inspector UI at /inspector
|
|
2203
2608
|
*
|