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
package/dist/src/server/index.js
CHANGED
|
@@ -70,7 +70,8 @@ function createAppsSdkResource(uri, htmlTemplate, metadata) {
|
|
|
70
70
|
}
|
|
71
71
|
__name(createAppsSdkResource, "createAppsSdkResource");
|
|
72
72
|
function createUIResourceFromDefinition(definition, params, config) {
|
|
73
|
-
const
|
|
73
|
+
const buildIdPart = config.buildId ? `-${config.buildId}` : "";
|
|
74
|
+
const uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}${buildIdPart}.html` : `ui://widget/${definition.name}${buildIdPart}`;
|
|
74
75
|
const encoding = definition.encoding || "text";
|
|
75
76
|
switch (definition.type) {
|
|
76
77
|
case "externalUrl": {
|
|
@@ -296,6 +297,10 @@ async function requestLogger(c, next) {
|
|
|
296
297
|
__name(requestLogger, "requestLogger");
|
|
297
298
|
|
|
298
299
|
// src/server/mcp-server.ts
|
|
300
|
+
function generateUUID() {
|
|
301
|
+
return globalThis.crypto.randomUUID();
|
|
302
|
+
}
|
|
303
|
+
__name(generateUUID, "generateUUID");
|
|
299
304
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
300
305
|
var isDeno = typeof globalThis.Deno !== "undefined";
|
|
301
306
|
function getEnv(key) {
|
|
@@ -391,6 +396,9 @@ var McpServer = class {
|
|
|
391
396
|
registeredTools = [];
|
|
392
397
|
registeredPrompts = [];
|
|
393
398
|
registeredResources = [];
|
|
399
|
+
buildId;
|
|
400
|
+
sessions = /* @__PURE__ */ new Map();
|
|
401
|
+
idleCleanupInterval;
|
|
394
402
|
/**
|
|
395
403
|
* Creates a new MCP server instance with Hono integration
|
|
396
404
|
*
|
|
@@ -423,7 +431,9 @@ var McpServer = class {
|
|
|
423
431
|
"mcp-session-id",
|
|
424
432
|
"X-Proxy-Token",
|
|
425
433
|
"X-Target-URL"
|
|
426
|
-
]
|
|
434
|
+
],
|
|
435
|
+
// Expose mcp-session-id so browser clients can read it from responses
|
|
436
|
+
exposeHeaders: ["mcp-session-id"]
|
|
427
437
|
})
|
|
428
438
|
);
|
|
429
439
|
this.app.use("*", requestLogger);
|
|
@@ -663,6 +673,11 @@ var McpServer = class {
|
|
|
663
673
|
*/
|
|
664
674
|
tool(toolDefinition) {
|
|
665
675
|
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
676
|
+
const context = {
|
|
677
|
+
sample: /* @__PURE__ */ __name(async (params, options) => {
|
|
678
|
+
return await this.createMessage(params, options);
|
|
679
|
+
}, "sample")
|
|
680
|
+
};
|
|
666
681
|
this.server.registerTool(
|
|
667
682
|
toolDefinition.name,
|
|
668
683
|
{
|
|
@@ -673,6 +688,9 @@ var McpServer = class {
|
|
|
673
688
|
_meta: toolDefinition._meta
|
|
674
689
|
},
|
|
675
690
|
async (params) => {
|
|
691
|
+
if (toolDefinition.cb.length >= 2) {
|
|
692
|
+
return await toolDefinition.cb(params, context);
|
|
693
|
+
}
|
|
676
694
|
return await toolDefinition.cb(params);
|
|
677
695
|
}
|
|
678
696
|
);
|
|
@@ -729,6 +747,46 @@ var McpServer = class {
|
|
|
729
747
|
this.registeredPrompts.push(promptDefinition.name);
|
|
730
748
|
return this;
|
|
731
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Request LLM sampling from connected clients.
|
|
752
|
+
*
|
|
753
|
+
* This method allows server tools to request LLM completions from clients
|
|
754
|
+
* that support the sampling capability. The client will handle model selection,
|
|
755
|
+
* user approval (human-in-the-loop), and return the generated response.
|
|
756
|
+
*
|
|
757
|
+
* @param params - Sampling request parameters including messages, model preferences, etc.
|
|
758
|
+
* @param options - Optional request options (timeouts, cancellation, etc.)
|
|
759
|
+
* @returns Promise resolving to the generated message from the client's LLM
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* ```typescript
|
|
763
|
+
* // In a tool callback
|
|
764
|
+
* server.tool({
|
|
765
|
+
* name: 'analyze-sentiment',
|
|
766
|
+
* description: 'Analyze sentiment using LLM',
|
|
767
|
+
* inputs: [{ name: 'text', type: 'string', required: true }],
|
|
768
|
+
* cb: async (params, ctx) => {
|
|
769
|
+
* const result = await ctx.sample({
|
|
770
|
+
* messages: [{
|
|
771
|
+
* role: 'user',
|
|
772
|
+
* content: { type: 'text', text: `Analyze sentiment: ${params.text}` }
|
|
773
|
+
* }],
|
|
774
|
+
* modelPreferences: {
|
|
775
|
+
* intelligencePriority: 0.8,
|
|
776
|
+
* speedPriority: 0.5
|
|
777
|
+
* }
|
|
778
|
+
* });
|
|
779
|
+
* return {
|
|
780
|
+
* content: [{ type: 'text', text: result.content.text }]
|
|
781
|
+
* };
|
|
782
|
+
* }
|
|
783
|
+
* })
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
async createMessage(params, options) {
|
|
787
|
+
console.log("createMessage", params, options);
|
|
788
|
+
return await this.server.server.createMessage(params, options);
|
|
789
|
+
}
|
|
732
790
|
/**
|
|
733
791
|
* Register a UI widget as both a tool and a resource
|
|
734
792
|
*
|
|
@@ -802,19 +860,19 @@ var McpServer = class {
|
|
|
802
860
|
let mimeType;
|
|
803
861
|
switch (definition.type) {
|
|
804
862
|
case "externalUrl":
|
|
805
|
-
resourceUri =
|
|
863
|
+
resourceUri = this.generateWidgetUri(definition.widget);
|
|
806
864
|
mimeType = "text/uri-list";
|
|
807
865
|
break;
|
|
808
866
|
case "rawHtml":
|
|
809
|
-
resourceUri =
|
|
867
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
810
868
|
mimeType = "text/html";
|
|
811
869
|
break;
|
|
812
870
|
case "remoteDom":
|
|
813
|
-
resourceUri =
|
|
871
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
814
872
|
mimeType = "application/vnd.mcp-ui.remote-dom+javascript";
|
|
815
873
|
break;
|
|
816
874
|
case "appsSdk":
|
|
817
|
-
resourceUri =
|
|
875
|
+
resourceUri = this.generateWidgetUri(definition.name, ".html");
|
|
818
876
|
mimeType = "text/html+skybridge";
|
|
819
877
|
break;
|
|
820
878
|
default:
|
|
@@ -833,16 +891,19 @@ var McpServer = class {
|
|
|
833
891
|
readCallback: /* @__PURE__ */ __name(async () => {
|
|
834
892
|
const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
|
|
835
893
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
894
|
+
uiResource.resource.uri = resourceUri;
|
|
836
895
|
return {
|
|
837
896
|
contents: [uiResource.resource]
|
|
838
897
|
};
|
|
839
898
|
}, "readCallback")
|
|
840
899
|
});
|
|
841
900
|
if (definition.type === "appsSdk") {
|
|
901
|
+
const buildIdPart = this.buildId ? `-${this.buildId}` : "";
|
|
902
|
+
const uriTemplate = `ui://widget/${definition.name}${buildIdPart}-{id}.html`;
|
|
842
903
|
this.resourceTemplate({
|
|
843
904
|
name: `${definition.name}-dynamic`,
|
|
844
905
|
resourceTemplate: {
|
|
845
|
-
uriTemplate
|
|
906
|
+
uriTemplate,
|
|
846
907
|
name: definition.title || definition.name,
|
|
847
908
|
description: definition.description,
|
|
848
909
|
mimeType
|
|
@@ -853,6 +914,7 @@ var McpServer = class {
|
|
|
853
914
|
annotations: definition.annotations,
|
|
854
915
|
readCallback: /* @__PURE__ */ __name(async (uri, params) => {
|
|
855
916
|
const uiResource = this.createWidgetUIResource(definition, {});
|
|
917
|
+
uiResource.resource.uri = uri.toString();
|
|
856
918
|
return {
|
|
857
919
|
contents: [uiResource.resource]
|
|
858
920
|
};
|
|
@@ -884,7 +946,11 @@ var McpServer = class {
|
|
|
884
946
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
885
947
|
if (definition.type === "appsSdk") {
|
|
886
948
|
const randomId = Math.random().toString(36).substring(2, 15);
|
|
887
|
-
const uniqueUri =
|
|
949
|
+
const uniqueUri = this.generateWidgetUri(
|
|
950
|
+
definition.name,
|
|
951
|
+
".html",
|
|
952
|
+
randomId
|
|
953
|
+
);
|
|
888
954
|
const uniqueToolMetadata = {
|
|
889
955
|
...toolMetadata,
|
|
890
956
|
"openai/outputTemplate": uniqueUri
|
|
@@ -941,7 +1007,8 @@ var McpServer = class {
|
|
|
941
1007
|
}
|
|
942
1008
|
const urlConfig = {
|
|
943
1009
|
baseUrl: configBaseUrl,
|
|
944
|
-
port: configPort
|
|
1010
|
+
port: configPort,
|
|
1011
|
+
buildId: this.buildId
|
|
945
1012
|
};
|
|
946
1013
|
const uiResource = createUIResourceFromDefinition(
|
|
947
1014
|
definition,
|
|
@@ -956,6 +1023,25 @@ var McpServer = class {
|
|
|
956
1023
|
}
|
|
957
1024
|
return uiResource;
|
|
958
1025
|
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Generate a widget URI with optional build ID for cache busting
|
|
1028
|
+
*
|
|
1029
|
+
* @private
|
|
1030
|
+
* @param widgetName - Widget name/identifier
|
|
1031
|
+
* @param extension - Optional file extension (e.g., '.html')
|
|
1032
|
+
* @param suffix - Optional suffix (e.g., random ID for dynamic URIs)
|
|
1033
|
+
* @returns Widget URI with build ID if available
|
|
1034
|
+
*/
|
|
1035
|
+
generateWidgetUri(widgetName, extension = "", suffix = "") {
|
|
1036
|
+
const parts = [widgetName];
|
|
1037
|
+
if (this.buildId) {
|
|
1038
|
+
parts.push(this.buildId);
|
|
1039
|
+
}
|
|
1040
|
+
if (suffix) {
|
|
1041
|
+
parts.push(suffix);
|
|
1042
|
+
}
|
|
1043
|
+
return `ui://widget/${parts.join("-")}${extension}`;
|
|
1044
|
+
}
|
|
959
1045
|
/**
|
|
960
1046
|
* Build a complete URL for a widget including query parameters
|
|
961
1047
|
*
|
|
@@ -1501,6 +1587,10 @@ if (container && Component) {
|
|
|
1501
1587
|
"utf8"
|
|
1502
1588
|
);
|
|
1503
1589
|
const manifest = JSON.parse(manifestContent);
|
|
1590
|
+
if (manifest.buildId && typeof manifest.buildId === "string") {
|
|
1591
|
+
this.buildId = manifest.buildId;
|
|
1592
|
+
console.log(`[WIDGETS] Build ID: ${this.buildId}`);
|
|
1593
|
+
}
|
|
1504
1594
|
if (manifest.widgets && typeof manifest.widgets === "object" && !Array.isArray(manifest.widgets)) {
|
|
1505
1595
|
widgets = Object.keys(manifest.widgets);
|
|
1506
1596
|
widgetsMetadata = manifest.widgets;
|
|
@@ -1670,8 +1760,7 @@ if (container && Component) {
|
|
|
1670
1760
|
*
|
|
1671
1761
|
* Sets up the HTTP transport layer for the MCP server, creating endpoints for
|
|
1672
1762
|
* Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup.
|
|
1673
|
-
*
|
|
1674
|
-
* concurrent client connections.
|
|
1763
|
+
* Transports are reused per session ID to maintain state across requests.
|
|
1675
1764
|
*
|
|
1676
1765
|
* This method is called automatically when the server starts listening and ensures
|
|
1677
1766
|
* that MCP clients can communicate with the server over HTTP.
|
|
@@ -1689,6 +1778,55 @@ if (container && Component) {
|
|
|
1689
1778
|
if (this.mcpMounted) return;
|
|
1690
1779
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1691
1780
|
const endpoint = "/mcp";
|
|
1781
|
+
const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
|
|
1782
|
+
const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
|
|
1783
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1784
|
+
const session = this.sessions.get(sessionId);
|
|
1785
|
+
session.lastAccessedAt = Date.now();
|
|
1786
|
+
return session.transport;
|
|
1787
|
+
}
|
|
1788
|
+
if (!isInit && sessionId) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
if (!isInit && !sessionId) {
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1795
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
1796
|
+
enableJsonResponse: true,
|
|
1797
|
+
allowedOrigins: this.config.allowedOrigins,
|
|
1798
|
+
enableDnsRebindingProtection: this.config.allowedOrigins !== void 0 && this.config.allowedOrigins.length > 0,
|
|
1799
|
+
onsessioninitialized: /* @__PURE__ */ __name((id) => {
|
|
1800
|
+
if (id) {
|
|
1801
|
+
this.sessions.set(id, {
|
|
1802
|
+
transport,
|
|
1803
|
+
lastAccessedAt: Date.now()
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
}, "onsessioninitialized"),
|
|
1807
|
+
onsessionclosed: /* @__PURE__ */ __name((id) => {
|
|
1808
|
+
if (id) {
|
|
1809
|
+
this.sessions.delete(id);
|
|
1810
|
+
}
|
|
1811
|
+
}, "onsessionclosed")
|
|
1812
|
+
});
|
|
1813
|
+
await this.server.connect(transport);
|
|
1814
|
+
return transport;
|
|
1815
|
+
}, "getOrCreateTransport");
|
|
1816
|
+
if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
|
|
1817
|
+
this.idleCleanupInterval = setInterval(() => {
|
|
1818
|
+
const now = Date.now();
|
|
1819
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1820
|
+
if (now - session.lastAccessedAt > idleTimeoutMs) {
|
|
1821
|
+
try {
|
|
1822
|
+
session.transport.close();
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
}
|
|
1825
|
+
this.sessions.delete(sessionId);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}, 6e4);
|
|
1829
|
+
}
|
|
1692
1830
|
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1693
1831
|
const req = c.req.raw;
|
|
1694
1832
|
const responseBody = [];
|
|
@@ -1798,21 +1936,51 @@ if (container && Component) {
|
|
|
1798
1936
|
}, "createExpressLikeObjects");
|
|
1799
1937
|
this.app.post(endpoint, async (c) => {
|
|
1800
1938
|
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1939
|
+
let body = {};
|
|
1801
1940
|
try {
|
|
1802
|
-
|
|
1941
|
+
body = await c.req.json();
|
|
1942
|
+
expressReq.body = body;
|
|
1803
1943
|
} catch {
|
|
1804
1944
|
expressReq.body = {};
|
|
1805
1945
|
}
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1946
|
+
const isInit = body?.method === "initialize";
|
|
1947
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
1948
|
+
const transport = await getOrCreateTransport(sessionId, isInit);
|
|
1949
|
+
if (!transport) {
|
|
1950
|
+
if (sessionId) {
|
|
1951
|
+
return c.json(
|
|
1952
|
+
{
|
|
1953
|
+
jsonrpc: "2.0",
|
|
1954
|
+
error: {
|
|
1955
|
+
code: -32e3,
|
|
1956
|
+
message: "Session not found or expired"
|
|
1957
|
+
},
|
|
1958
|
+
id: null
|
|
1959
|
+
},
|
|
1960
|
+
404
|
|
1961
|
+
);
|
|
1962
|
+
} else {
|
|
1963
|
+
return c.json(
|
|
1964
|
+
{
|
|
1965
|
+
jsonrpc: "2.0",
|
|
1966
|
+
error: {
|
|
1967
|
+
code: -32e3,
|
|
1968
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
1969
|
+
},
|
|
1970
|
+
id: null
|
|
1971
|
+
},
|
|
1972
|
+
400
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1977
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
1978
|
+
}
|
|
1810
1979
|
if (expressRes._closeHandler) {
|
|
1811
1980
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1812
1981
|
transport.close();
|
|
1813
1982
|
});
|
|
1814
1983
|
}
|
|
1815
|
-
await this.server.connect(transport);
|
|
1816
1984
|
await this.waitForRequestComplete(
|
|
1817
1985
|
transport,
|
|
1818
1986
|
expressReq,
|
|
@@ -1826,10 +1994,38 @@ if (container && Component) {
|
|
|
1826
1994
|
return c.text("", 200);
|
|
1827
1995
|
});
|
|
1828
1996
|
this.app.get(endpoint, async (c) => {
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1997
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
1998
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
1999
|
+
if (!transport) {
|
|
2000
|
+
if (sessionId) {
|
|
2001
|
+
return c.json(
|
|
2002
|
+
{
|
|
2003
|
+
jsonrpc: "2.0",
|
|
2004
|
+
error: {
|
|
2005
|
+
code: -32e3,
|
|
2006
|
+
message: "Session not found or expired"
|
|
2007
|
+
},
|
|
2008
|
+
id: null
|
|
2009
|
+
},
|
|
2010
|
+
404
|
|
2011
|
+
);
|
|
2012
|
+
} else {
|
|
2013
|
+
return c.json(
|
|
2014
|
+
{
|
|
2015
|
+
jsonrpc: "2.0",
|
|
2016
|
+
error: {
|
|
2017
|
+
code: -32e3,
|
|
2018
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2019
|
+
},
|
|
2020
|
+
id: null
|
|
2021
|
+
},
|
|
2022
|
+
400
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2027
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2028
|
+
}
|
|
1833
2029
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1834
2030
|
transport.close();
|
|
1835
2031
|
});
|
|
@@ -1925,7 +2121,6 @@ if (container && Component) {
|
|
|
1925
2121
|
headers: c.req.header(),
|
|
1926
2122
|
method: c.req.method
|
|
1927
2123
|
};
|
|
1928
|
-
await this.server.connect(transport);
|
|
1929
2124
|
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
1930
2125
|
console.error("MCP Transport error:", err);
|
|
1931
2126
|
try {
|
|
@@ -1937,14 +2132,38 @@ if (container && Component) {
|
|
|
1937
2132
|
});
|
|
1938
2133
|
this.app.delete(endpoint, async (c) => {
|
|
1939
2134
|
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2135
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2136
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2137
|
+
if (!transport) {
|
|
2138
|
+
if (sessionId) {
|
|
2139
|
+
return c.json(
|
|
2140
|
+
{
|
|
2141
|
+
jsonrpc: "2.0",
|
|
2142
|
+
error: {
|
|
2143
|
+
code: -32e3,
|
|
2144
|
+
message: "Session not found or expired"
|
|
2145
|
+
},
|
|
2146
|
+
id: null
|
|
2147
|
+
},
|
|
2148
|
+
404
|
|
2149
|
+
);
|
|
2150
|
+
} else {
|
|
2151
|
+
return c.json(
|
|
2152
|
+
{
|
|
2153
|
+
jsonrpc: "2.0",
|
|
2154
|
+
error: {
|
|
2155
|
+
code: -32e3,
|
|
2156
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2157
|
+
},
|
|
2158
|
+
id: null
|
|
2159
|
+
},
|
|
2160
|
+
400
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
1944
2164
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1945
2165
|
transport.close();
|
|
1946
2166
|
});
|
|
1947
|
-
await this.server.connect(transport);
|
|
1948
2167
|
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
1949
2168
|
const response = getResponse();
|
|
1950
2169
|
if (response) {
|
|
@@ -2160,6 +2379,192 @@ if (container && Component) {
|
|
|
2160
2379
|
return result;
|
|
2161
2380
|
};
|
|
2162
2381
|
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Get array of active session IDs
|
|
2384
|
+
*
|
|
2385
|
+
* Returns an array of all currently active session IDs. This is useful for
|
|
2386
|
+
* sending targeted notifications to specific clients or iterating over
|
|
2387
|
+
* connected clients.
|
|
2388
|
+
*
|
|
2389
|
+
* Note: This only works in stateful mode. In stateless mode (edge environments),
|
|
2390
|
+
* this will return an empty array.
|
|
2391
|
+
*
|
|
2392
|
+
* @returns Array of active session ID strings
|
|
2393
|
+
*
|
|
2394
|
+
* @example
|
|
2395
|
+
* ```typescript
|
|
2396
|
+
* const sessions = server.getActiveSessions();
|
|
2397
|
+
* console.log(`${sessions.length} clients connected`);
|
|
2398
|
+
*
|
|
2399
|
+
* // Send notification to first connected client
|
|
2400
|
+
* if (sessions.length > 0) {
|
|
2401
|
+
* server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
|
|
2402
|
+
* }
|
|
2403
|
+
* ```
|
|
2404
|
+
*/
|
|
2405
|
+
getActiveSessions() {
|
|
2406
|
+
return Array.from(this.sessions.keys());
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Send a notification to all connected clients
|
|
2410
|
+
*
|
|
2411
|
+
* Broadcasts a JSON-RPC notification to all active sessions. Notifications are
|
|
2412
|
+
* one-way messages that do not expect a response from the client.
|
|
2413
|
+
*
|
|
2414
|
+
* Note: This only works in stateful mode with active sessions. If no sessions
|
|
2415
|
+
* are connected, the notification is silently discarded (per MCP spec: "server MAY send").
|
|
2416
|
+
*
|
|
2417
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2418
|
+
* @param params - Optional parameters to include in the notification
|
|
2419
|
+
*
|
|
2420
|
+
* @example
|
|
2421
|
+
* ```typescript
|
|
2422
|
+
* // Send a simple notification to all clients
|
|
2423
|
+
* server.sendNotification("custom/server-status", {
|
|
2424
|
+
* status: "ready",
|
|
2425
|
+
* timestamp: new Date().toISOString()
|
|
2426
|
+
* });
|
|
2427
|
+
*
|
|
2428
|
+
* // Notify all clients that resources have changed
|
|
2429
|
+
* server.sendNotification("notifications/resources/list_changed");
|
|
2430
|
+
* ```
|
|
2431
|
+
*/
|
|
2432
|
+
async sendNotification(method, params) {
|
|
2433
|
+
const notification = {
|
|
2434
|
+
jsonrpc: "2.0",
|
|
2435
|
+
method,
|
|
2436
|
+
...params && { params }
|
|
2437
|
+
};
|
|
2438
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2439
|
+
try {
|
|
2440
|
+
await session.transport.send(notification);
|
|
2441
|
+
} catch (error) {
|
|
2442
|
+
console.warn(
|
|
2443
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2444
|
+
error
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Send a notification to a specific client session
|
|
2451
|
+
*
|
|
2452
|
+
* Sends a JSON-RPC notification to a single client identified by their session ID.
|
|
2453
|
+
* This allows sending customized notifications to individual clients.
|
|
2454
|
+
*
|
|
2455
|
+
* Note: This only works in stateful mode. If the session ID doesn't exist,
|
|
2456
|
+
* the notification is silently discarded.
|
|
2457
|
+
*
|
|
2458
|
+
* @param sessionId - The target session ID (from getActiveSessions())
|
|
2459
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2460
|
+
* @param params - Optional parameters to include in the notification
|
|
2461
|
+
* @returns true if the notification was sent, false if session not found
|
|
2462
|
+
*
|
|
2463
|
+
* @example
|
|
2464
|
+
* ```typescript
|
|
2465
|
+
* const sessions = server.getActiveSessions();
|
|
2466
|
+
*
|
|
2467
|
+
* // Send different messages to different clients
|
|
2468
|
+
* sessions.forEach((sessionId, index) => {
|
|
2469
|
+
* server.sendNotificationToSession(sessionId, "custom/welcome", {
|
|
2470
|
+
* message: `Hello client #${index + 1}!`,
|
|
2471
|
+
* clientNumber: index + 1
|
|
2472
|
+
* });
|
|
2473
|
+
* });
|
|
2474
|
+
* ```
|
|
2475
|
+
*/
|
|
2476
|
+
async sendNotificationToSession(sessionId, method, params) {
|
|
2477
|
+
const session = this.sessions.get(sessionId);
|
|
2478
|
+
if (!session) {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
const notification = {
|
|
2482
|
+
jsonrpc: "2.0",
|
|
2483
|
+
method,
|
|
2484
|
+
...params && { params }
|
|
2485
|
+
};
|
|
2486
|
+
try {
|
|
2487
|
+
await session.transport.send(notification);
|
|
2488
|
+
return true;
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
console.warn(
|
|
2491
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2492
|
+
error
|
|
2493
|
+
);
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
// Store the roots changed callback
|
|
2498
|
+
onRootsChangedCallback;
|
|
2499
|
+
/**
|
|
2500
|
+
* Register a callback for when a client's roots change
|
|
2501
|
+
*
|
|
2502
|
+
* When a client sends a `notifications/roots/list_changed` notification,
|
|
2503
|
+
* the server will automatically request the updated roots list and call
|
|
2504
|
+
* this callback with the new roots.
|
|
2505
|
+
*
|
|
2506
|
+
* @param callback - Function called with the updated roots array
|
|
2507
|
+
*
|
|
2508
|
+
* @example
|
|
2509
|
+
* ```typescript
|
|
2510
|
+
* server.onRootsChanged(async (roots) => {
|
|
2511
|
+
* console.log("Client roots updated:", roots);
|
|
2512
|
+
* roots.forEach(root => {
|
|
2513
|
+
* console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
|
|
2514
|
+
* });
|
|
2515
|
+
* });
|
|
2516
|
+
* ```
|
|
2517
|
+
*/
|
|
2518
|
+
onRootsChanged(callback) {
|
|
2519
|
+
this.onRootsChangedCallback = callback;
|
|
2520
|
+
return this;
|
|
2521
|
+
}
|
|
2522
|
+
/**
|
|
2523
|
+
* Request the current roots list from a specific client session
|
|
2524
|
+
*
|
|
2525
|
+
* This sends a `roots/list` request to the client and returns
|
|
2526
|
+
* the list of roots the client has configured.
|
|
2527
|
+
*
|
|
2528
|
+
* @param sessionId - The session ID of the client to query
|
|
2529
|
+
* @returns Array of roots, or null if the session doesn't exist or request fails
|
|
2530
|
+
*
|
|
2531
|
+
* @example
|
|
2532
|
+
* ```typescript
|
|
2533
|
+
* const sessions = server.getActiveSessions();
|
|
2534
|
+
* if (sessions.length > 0) {
|
|
2535
|
+
* const roots = await server.listRoots(sessions[0]);
|
|
2536
|
+
* if (roots) {
|
|
2537
|
+
* console.log(`Client has ${roots.length} roots:`);
|
|
2538
|
+
* roots.forEach(r => console.log(` - ${r.uri}`));
|
|
2539
|
+
* }
|
|
2540
|
+
* }
|
|
2541
|
+
* ```
|
|
2542
|
+
*/
|
|
2543
|
+
async listRoots(sessionId) {
|
|
2544
|
+
const session = this.sessions.get(sessionId);
|
|
2545
|
+
if (!session) {
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2548
|
+
try {
|
|
2549
|
+
const request = {
|
|
2550
|
+
jsonrpc: "2.0",
|
|
2551
|
+
id: generateUUID(),
|
|
2552
|
+
method: "roots/list",
|
|
2553
|
+
params: {}
|
|
2554
|
+
};
|
|
2555
|
+
const response = await session.transport.send(request);
|
|
2556
|
+
if (response && typeof response === "object" && "roots" in response) {
|
|
2557
|
+
return response.roots;
|
|
2558
|
+
}
|
|
2559
|
+
return [];
|
|
2560
|
+
} catch (error) {
|
|
2561
|
+
console.warn(
|
|
2562
|
+
`[MCP] Failed to list roots from session ${sessionId}:`,
|
|
2563
|
+
error
|
|
2564
|
+
);
|
|
2565
|
+
return null;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2163
2568
|
/**
|
|
2164
2569
|
* Mount MCP Inspector UI at /inspector
|
|
2165
2570
|
*
|