mcp-use 1.5.0 → 1.5.1-canary.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-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.d.ts +1 -0
- package/dist/src/react/index.d.ts.map +1 -1
- 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 +605 -168
- package/dist/src/server/index.js +605 -168
- package/dist/src/server/mcp-server.d.ts +201 -10
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/types/common.d.ts +28 -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;
|
|
@@ -1671,6 +1761,7 @@ if (container && Component) {
|
|
|
1671
1761
|
resource_domains: [
|
|
1672
1762
|
"https://*.oaistatic.com",
|
|
1673
1763
|
"https://*.oaiusercontent.com",
|
|
1764
|
+
"https://*.openai.com",
|
|
1674
1765
|
// always also add the base url of the server
|
|
1675
1766
|
...this.getServerBaseUrl() ? [this.getServerBaseUrl()] : [],
|
|
1676
1767
|
...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.resource_domains || []
|
|
@@ -1703,12 +1794,11 @@ if (container && Component) {
|
|
|
1703
1794
|
});
|
|
1704
1795
|
}
|
|
1705
1796
|
/**
|
|
1706
|
-
* Mount MCP server endpoints at /mcp
|
|
1797
|
+
* Mount MCP server endpoints at /mcp and /sse
|
|
1707
1798
|
*
|
|
1708
1799
|
* Sets up the HTTP transport layer for the MCP server, creating endpoints for
|
|
1709
1800
|
* Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup.
|
|
1710
|
-
*
|
|
1711
|
-
* concurrent client connections.
|
|
1801
|
+
* Transports are reused per session ID to maintain state across requests.
|
|
1712
1802
|
*
|
|
1713
1803
|
* This method is called automatically when the server starts listening and ensures
|
|
1714
1804
|
* that MCP clients can communicate with the server over HTTP.
|
|
@@ -1718,14 +1808,80 @@ if (container && Component) {
|
|
|
1718
1808
|
*
|
|
1719
1809
|
* @example
|
|
1720
1810
|
* Endpoints created:
|
|
1721
|
-
* - GET /mcp - SSE streaming endpoint for real-time communication
|
|
1722
|
-
* - POST /mcp - Message handling endpoint for MCP protocol messages
|
|
1723
|
-
* - DELETE /mcp - Session cleanup endpoint
|
|
1811
|
+
* - GET /mcp, GET /sse - SSE streaming endpoint for real-time communication
|
|
1812
|
+
* - POST /mcp, POST /sse - Message handling endpoint for MCP protocol messages
|
|
1813
|
+
* - DELETE /mcp, DELETE /sse - Session cleanup endpoint
|
|
1724
1814
|
*/
|
|
1725
1815
|
async mountMcp() {
|
|
1726
1816
|
if (this.mcpMounted) return;
|
|
1727
1817
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1728
|
-
const
|
|
1818
|
+
const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
|
|
1819
|
+
const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
|
|
1820
|
+
if (isInit) {
|
|
1821
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1822
|
+
try {
|
|
1823
|
+
this.sessions.get(sessionId).transport.close();
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
}
|
|
1826
|
+
this.sessions.delete(sessionId);
|
|
1827
|
+
}
|
|
1828
|
+
const isProduction = this.isProductionMode();
|
|
1829
|
+
let allowedOrigins = this.config.allowedOrigins;
|
|
1830
|
+
let enableDnsRebindingProtection = false;
|
|
1831
|
+
if (isProduction) {
|
|
1832
|
+
if (allowedOrigins !== void 0) {
|
|
1833
|
+
enableDnsRebindingProtection = allowedOrigins.length > 0;
|
|
1834
|
+
}
|
|
1835
|
+
} else {
|
|
1836
|
+
allowedOrigins = void 0;
|
|
1837
|
+
enableDnsRebindingProtection = false;
|
|
1838
|
+
}
|
|
1839
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1840
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
1841
|
+
enableJsonResponse: true,
|
|
1842
|
+
allowedOrigins,
|
|
1843
|
+
enableDnsRebindingProtection,
|
|
1844
|
+
onsessioninitialized: /* @__PURE__ */ __name((id) => {
|
|
1845
|
+
if (id) {
|
|
1846
|
+
this.sessions.set(id, {
|
|
1847
|
+
transport,
|
|
1848
|
+
lastAccessedAt: Date.now()
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
}, "onsessioninitialized"),
|
|
1852
|
+
onsessionclosed: /* @__PURE__ */ __name((id) => {
|
|
1853
|
+
if (id) {
|
|
1854
|
+
this.sessions.delete(id);
|
|
1855
|
+
}
|
|
1856
|
+
}, "onsessionclosed")
|
|
1857
|
+
});
|
|
1858
|
+
await this.server.connect(transport);
|
|
1859
|
+
return transport;
|
|
1860
|
+
}
|
|
1861
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1862
|
+
const session = this.sessions.get(sessionId);
|
|
1863
|
+
session.lastAccessedAt = Date.now();
|
|
1864
|
+
return session.transport;
|
|
1865
|
+
}
|
|
1866
|
+
if (sessionId) {
|
|
1867
|
+
return null;
|
|
1868
|
+
}
|
|
1869
|
+
return null;
|
|
1870
|
+
}, "getOrCreateTransport");
|
|
1871
|
+
if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
|
|
1872
|
+
this.idleCleanupInterval = setInterval(() => {
|
|
1873
|
+
const now = Date.now();
|
|
1874
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1875
|
+
if (now - session.lastAccessedAt > idleTimeoutMs) {
|
|
1876
|
+
try {
|
|
1877
|
+
session.transport.close();
|
|
1878
|
+
} catch (error) {
|
|
1879
|
+
}
|
|
1880
|
+
this.sessions.delete(sessionId);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}, 6e4);
|
|
1884
|
+
}
|
|
1729
1885
|
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1730
1886
|
const req = c.req.raw;
|
|
1731
1887
|
const responseBody = [];
|
|
@@ -1833,164 +1989,250 @@ if (container && Component) {
|
|
|
1833
1989
|
}, "getResponse")
|
|
1834
1990
|
};
|
|
1835
1991
|
}, "createExpressLikeObjects");
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1992
|
+
const mountEndpoint = /* @__PURE__ */ __name((endpoint) => {
|
|
1993
|
+
this.app.post(endpoint, async (c) => {
|
|
1994
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1995
|
+
let body = {};
|
|
1996
|
+
try {
|
|
1997
|
+
body = await c.req.json();
|
|
1998
|
+
expressReq.body = body;
|
|
1999
|
+
} catch {
|
|
2000
|
+
expressReq.body = {};
|
|
2001
|
+
}
|
|
2002
|
+
const isInit = body?.method === "initialize";
|
|
2003
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2004
|
+
const transport = await getOrCreateTransport(sessionId, isInit);
|
|
2005
|
+
if (!transport) {
|
|
2006
|
+
if (sessionId) {
|
|
2007
|
+
return c.json(
|
|
2008
|
+
{
|
|
2009
|
+
jsonrpc: "2.0",
|
|
2010
|
+
error: {
|
|
2011
|
+
code: -32e3,
|
|
2012
|
+
message: "Session not found or expired"
|
|
2013
|
+
},
|
|
2014
|
+
// Notifications don't have an id, but we include null for consistency
|
|
2015
|
+
id: body?.id ?? null
|
|
2016
|
+
},
|
|
2017
|
+
404
|
|
2018
|
+
);
|
|
2019
|
+
} else {
|
|
2020
|
+
return c.json(
|
|
2021
|
+
{
|
|
2022
|
+
jsonrpc: "2.0",
|
|
2023
|
+
error: {
|
|
2024
|
+
code: -32e3,
|
|
2025
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2026
|
+
},
|
|
2027
|
+
id: body?.id ?? null
|
|
2028
|
+
},
|
|
2029
|
+
400
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2034
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2035
|
+
}
|
|
2036
|
+
if (expressRes._closeHandler) {
|
|
2037
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2038
|
+
transport.close();
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
await this.waitForRequestComplete(
|
|
2042
|
+
transport,
|
|
2043
|
+
expressReq,
|
|
2044
|
+
expressRes,
|
|
2045
|
+
expressReq.body
|
|
2046
|
+
);
|
|
2047
|
+
const response = getResponse();
|
|
2048
|
+
if (response) {
|
|
2049
|
+
return response;
|
|
2050
|
+
}
|
|
2051
|
+
return c.text("", 200);
|
|
1846
2052
|
});
|
|
1847
|
-
|
|
2053
|
+
this.app.get(endpoint, async (c) => {
|
|
2054
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2055
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2056
|
+
if (!transport) {
|
|
2057
|
+
if (sessionId) {
|
|
2058
|
+
return c.json(
|
|
2059
|
+
{
|
|
2060
|
+
jsonrpc: "2.0",
|
|
2061
|
+
error: {
|
|
2062
|
+
code: -32e3,
|
|
2063
|
+
message: "Session not found or expired"
|
|
2064
|
+
},
|
|
2065
|
+
id: null
|
|
2066
|
+
},
|
|
2067
|
+
404
|
|
2068
|
+
);
|
|
2069
|
+
} else {
|
|
2070
|
+
return c.json(
|
|
2071
|
+
{
|
|
2072
|
+
jsonrpc: "2.0",
|
|
2073
|
+
error: {
|
|
2074
|
+
code: -32e3,
|
|
2075
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2076
|
+
},
|
|
2077
|
+
id: null
|
|
2078
|
+
},
|
|
2079
|
+
400
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2084
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2085
|
+
}
|
|
1848
2086
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1849
2087
|
transport.close();
|
|
1850
2088
|
});
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2089
|
+
const { readable, writable } = new globalThis.TransformStream();
|
|
2090
|
+
const writer = writable.getWriter();
|
|
2091
|
+
const encoder = new TextEncoder();
|
|
2092
|
+
let resolveResponse;
|
|
2093
|
+
const responsePromise = new Promise((resolve) => {
|
|
2094
|
+
resolveResponse = resolve;
|
|
2095
|
+
});
|
|
2096
|
+
let headersSent = false;
|
|
2097
|
+
const headers = {};
|
|
2098
|
+
let statusCode = 200;
|
|
2099
|
+
const expressRes = {
|
|
2100
|
+
statusCode: 200,
|
|
2101
|
+
headersSent: false,
|
|
2102
|
+
status: /* @__PURE__ */ __name((code) => {
|
|
2103
|
+
statusCode = code;
|
|
2104
|
+
expressRes.statusCode = code;
|
|
2105
|
+
return expressRes;
|
|
2106
|
+
}, "status"),
|
|
2107
|
+
setHeader: /* @__PURE__ */ __name((name, value) => {
|
|
2108
|
+
if (!headersSent) {
|
|
2109
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
2110
|
+
}
|
|
2111
|
+
}, "setHeader"),
|
|
2112
|
+
getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
|
|
2113
|
+
write: /* @__PURE__ */ __name((chunk) => {
|
|
2114
|
+
if (!headersSent) {
|
|
2115
|
+
headersSent = true;
|
|
2116
|
+
resolveResponse(
|
|
2117
|
+
new Response(readable, {
|
|
2118
|
+
status: statusCode,
|
|
2119
|
+
headers
|
|
2120
|
+
})
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
const data = typeof chunk === "string" ? encoder.encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
2124
|
+
writer.write(data);
|
|
2125
|
+
return true;
|
|
2126
|
+
}, "write"),
|
|
2127
|
+
end: /* @__PURE__ */ __name((chunk) => {
|
|
2128
|
+
if (chunk) {
|
|
2129
|
+
expressRes.write(chunk);
|
|
2130
|
+
}
|
|
2131
|
+
if (!headersSent) {
|
|
2132
|
+
headersSent = true;
|
|
2133
|
+
resolveResponse(
|
|
2134
|
+
new Response(null, {
|
|
2135
|
+
status: statusCode,
|
|
2136
|
+
headers
|
|
2137
|
+
})
|
|
2138
|
+
);
|
|
2139
|
+
writer.close();
|
|
2140
|
+
} else {
|
|
2141
|
+
writer.close();
|
|
2142
|
+
}
|
|
2143
|
+
}, "end"),
|
|
2144
|
+
on: /* @__PURE__ */ __name((event, handler) => {
|
|
2145
|
+
if (event === "close") {
|
|
2146
|
+
expressRes._closeHandler = handler;
|
|
2147
|
+
}
|
|
2148
|
+
}, "on"),
|
|
2149
|
+
once: /* @__PURE__ */ __name(() => {
|
|
2150
|
+
}, "once"),
|
|
2151
|
+
removeListener: /* @__PURE__ */ __name(() => {
|
|
2152
|
+
}, "removeListener"),
|
|
2153
|
+
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
2154
|
+
statusCode = code;
|
|
2155
|
+
expressRes.statusCode = code;
|
|
2156
|
+
if (_headers) {
|
|
2157
|
+
Object.assign(headers, _headers);
|
|
2158
|
+
}
|
|
2159
|
+
if (!headersSent) {
|
|
2160
|
+
headersSent = true;
|
|
2161
|
+
resolveResponse(
|
|
2162
|
+
new Response(readable, {
|
|
2163
|
+
status: statusCode,
|
|
2164
|
+
headers
|
|
2165
|
+
})
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
return expressRes;
|
|
2169
|
+
}, "writeHead"),
|
|
2170
|
+
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
2171
|
+
}, "flushHeaders")
|
|
2172
|
+
};
|
|
2173
|
+
const expressReq = {
|
|
2174
|
+
...c.req.raw,
|
|
2175
|
+
url: new URL(c.req.url).pathname + new URL(c.req.url).search,
|
|
2176
|
+
path: new URL(c.req.url).pathname,
|
|
2177
|
+
query: Object.fromEntries(new URL(c.req.url).searchParams),
|
|
2178
|
+
headers: c.req.header(),
|
|
2179
|
+
method: c.req.method
|
|
2180
|
+
};
|
|
2181
|
+
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
2182
|
+
console.error("MCP Transport error:", err);
|
|
2183
|
+
try {
|
|
2184
|
+
writer.close();
|
|
2185
|
+
} catch {
|
|
1914
2186
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2187
|
+
});
|
|
2188
|
+
return responsePromise;
|
|
2189
|
+
});
|
|
2190
|
+
this.app.delete(endpoint, async (c) => {
|
|
2191
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
2192
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2193
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2194
|
+
if (!transport) {
|
|
2195
|
+
if (sessionId) {
|
|
2196
|
+
return c.json(
|
|
2197
|
+
{
|
|
2198
|
+
jsonrpc: "2.0",
|
|
2199
|
+
error: {
|
|
2200
|
+
code: -32e3,
|
|
2201
|
+
message: "Session not found or expired"
|
|
2202
|
+
},
|
|
2203
|
+
id: null
|
|
2204
|
+
},
|
|
2205
|
+
404
|
|
1922
2206
|
);
|
|
1923
|
-
writer.close();
|
|
1924
2207
|
} else {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
removeListener: /* @__PURE__ */ __name(() => {
|
|
1936
|
-
}, "removeListener"),
|
|
1937
|
-
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
1938
|
-
statusCode = code;
|
|
1939
|
-
expressRes.statusCode = code;
|
|
1940
|
-
if (_headers) {
|
|
1941
|
-
Object.assign(headers, _headers);
|
|
1942
|
-
}
|
|
1943
|
-
if (!headersSent) {
|
|
1944
|
-
headersSent = true;
|
|
1945
|
-
resolveResponse(
|
|
1946
|
-
new Response(readable, {
|
|
1947
|
-
status: statusCode,
|
|
1948
|
-
headers
|
|
1949
|
-
})
|
|
2208
|
+
return c.json(
|
|
2209
|
+
{
|
|
2210
|
+
jsonrpc: "2.0",
|
|
2211
|
+
error: {
|
|
2212
|
+
code: -32e3,
|
|
2213
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2214
|
+
},
|
|
2215
|
+
id: null
|
|
2216
|
+
},
|
|
2217
|
+
400
|
|
1950
2218
|
);
|
|
1951
2219
|
}
|
|
1952
|
-
return expressRes;
|
|
1953
|
-
}, "writeHead"),
|
|
1954
|
-
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
1955
|
-
}, "flushHeaders")
|
|
1956
|
-
};
|
|
1957
|
-
const expressReq = {
|
|
1958
|
-
...c.req.raw,
|
|
1959
|
-
url: new URL(c.req.url).pathname + new URL(c.req.url).search,
|
|
1960
|
-
path: new URL(c.req.url).pathname,
|
|
1961
|
-
query: Object.fromEntries(new URL(c.req.url).searchParams),
|
|
1962
|
-
headers: c.req.header(),
|
|
1963
|
-
method: c.req.method
|
|
1964
|
-
};
|
|
1965
|
-
await this.server.connect(transport);
|
|
1966
|
-
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
1967
|
-
console.error("MCP Transport error:", err);
|
|
1968
|
-
try {
|
|
1969
|
-
writer.close();
|
|
1970
|
-
} catch {
|
|
1971
2220
|
}
|
|
2221
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2222
|
+
transport.close();
|
|
2223
|
+
});
|
|
2224
|
+
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
2225
|
+
const response = getResponse();
|
|
2226
|
+
if (response) {
|
|
2227
|
+
return response;
|
|
2228
|
+
}
|
|
2229
|
+
return c.text("", 200);
|
|
1972
2230
|
});
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1977
|
-
const transport = new StreamableHTTPServerTransport({
|
|
1978
|
-
sessionIdGenerator: void 0,
|
|
1979
|
-
enableJsonResponse: true
|
|
1980
|
-
});
|
|
1981
|
-
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1982
|
-
transport.close();
|
|
1983
|
-
});
|
|
1984
|
-
await this.server.connect(transport);
|
|
1985
|
-
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
1986
|
-
const response = getResponse();
|
|
1987
|
-
if (response) {
|
|
1988
|
-
return response;
|
|
1989
|
-
}
|
|
1990
|
-
return c.text("", 200);
|
|
1991
|
-
});
|
|
2231
|
+
}, "mountEndpoint");
|
|
2232
|
+
mountEndpoint("/mcp");
|
|
2233
|
+
mountEndpoint("/sse");
|
|
1992
2234
|
this.mcpMounted = true;
|
|
1993
|
-
console.log(`[MCP] Server mounted at
|
|
2235
|
+
console.log(`[MCP] Server mounted at /mcp and /sse`);
|
|
1994
2236
|
}
|
|
1995
2237
|
/**
|
|
1996
2238
|
* Start the Hono server with MCP endpoints
|
|
@@ -1999,7 +2241,7 @@ if (container && Component) {
|
|
|
1999
2241
|
* the inspector UI (if available), and starting the server to listen
|
|
2000
2242
|
* for incoming connections. This is the main entry point for running the server.
|
|
2001
2243
|
*
|
|
2002
|
-
* The server will be accessible at the specified port with MCP endpoints at /mcp
|
|
2244
|
+
* The server will be accessible at the specified port with MCP endpoints at /mcp and /sse
|
|
2003
2245
|
* and inspector UI at /inspector (if the inspector package is installed).
|
|
2004
2246
|
*
|
|
2005
2247
|
* @param port - Port number to listen on (defaults to 3001 if not specified)
|
|
@@ -2009,7 +2251,7 @@ if (container && Component) {
|
|
|
2009
2251
|
* ```typescript
|
|
2010
2252
|
* await server.listen(8080)
|
|
2011
2253
|
* // Server now running at http://localhost:8080 (or configured host)
|
|
2012
|
-
* // MCP endpoints: http://localhost:8080/mcp
|
|
2254
|
+
* // MCP endpoints: http://localhost:8080/mcp and http://localhost:8080/sse
|
|
2013
2255
|
* // Inspector UI: http://localhost:8080/inspector
|
|
2014
2256
|
* ```
|
|
2015
2257
|
*/
|
|
@@ -2113,7 +2355,7 @@ if (container && Component) {
|
|
|
2113
2355
|
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
2114
2356
|
);
|
|
2115
2357
|
console.log(
|
|
2116
|
-
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
2358
|
+
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp and http://${this.serverHost}:${this.serverPort}/sse`
|
|
2117
2359
|
);
|
|
2118
2360
|
}
|
|
2119
2361
|
);
|
|
@@ -2197,6 +2439,192 @@ if (container && Component) {
|
|
|
2197
2439
|
return result;
|
|
2198
2440
|
};
|
|
2199
2441
|
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Get array of active session IDs
|
|
2444
|
+
*
|
|
2445
|
+
* Returns an array of all currently active session IDs. This is useful for
|
|
2446
|
+
* sending targeted notifications to specific clients or iterating over
|
|
2447
|
+
* connected clients.
|
|
2448
|
+
*
|
|
2449
|
+
* Note: This only works in stateful mode. In stateless mode (edge environments),
|
|
2450
|
+
* this will return an empty array.
|
|
2451
|
+
*
|
|
2452
|
+
* @returns Array of active session ID strings
|
|
2453
|
+
*
|
|
2454
|
+
* @example
|
|
2455
|
+
* ```typescript
|
|
2456
|
+
* const sessions = server.getActiveSessions();
|
|
2457
|
+
* console.log(`${sessions.length} clients connected`);
|
|
2458
|
+
*
|
|
2459
|
+
* // Send notification to first connected client
|
|
2460
|
+
* if (sessions.length > 0) {
|
|
2461
|
+
* server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
|
|
2462
|
+
* }
|
|
2463
|
+
* ```
|
|
2464
|
+
*/
|
|
2465
|
+
getActiveSessions() {
|
|
2466
|
+
return Array.from(this.sessions.keys());
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Send a notification to all connected clients
|
|
2470
|
+
*
|
|
2471
|
+
* Broadcasts a JSON-RPC notification to all active sessions. Notifications are
|
|
2472
|
+
* one-way messages that do not expect a response from the client.
|
|
2473
|
+
*
|
|
2474
|
+
* Note: This only works in stateful mode with active sessions. If no sessions
|
|
2475
|
+
* are connected, the notification is silently discarded (per MCP spec: "server MAY send").
|
|
2476
|
+
*
|
|
2477
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2478
|
+
* @param params - Optional parameters to include in the notification
|
|
2479
|
+
*
|
|
2480
|
+
* @example
|
|
2481
|
+
* ```typescript
|
|
2482
|
+
* // Send a simple notification to all clients
|
|
2483
|
+
* server.sendNotification("custom/server-status", {
|
|
2484
|
+
* status: "ready",
|
|
2485
|
+
* timestamp: new Date().toISOString()
|
|
2486
|
+
* });
|
|
2487
|
+
*
|
|
2488
|
+
* // Notify all clients that resources have changed
|
|
2489
|
+
* server.sendNotification("notifications/resources/list_changed");
|
|
2490
|
+
* ```
|
|
2491
|
+
*/
|
|
2492
|
+
async sendNotification(method, params) {
|
|
2493
|
+
const notification = {
|
|
2494
|
+
jsonrpc: "2.0",
|
|
2495
|
+
method,
|
|
2496
|
+
...params && { params }
|
|
2497
|
+
};
|
|
2498
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2499
|
+
try {
|
|
2500
|
+
await session.transport.send(notification);
|
|
2501
|
+
} catch (error) {
|
|
2502
|
+
console.warn(
|
|
2503
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2504
|
+
error
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Send a notification to a specific client session
|
|
2511
|
+
*
|
|
2512
|
+
* Sends a JSON-RPC notification to a single client identified by their session ID.
|
|
2513
|
+
* This allows sending customized notifications to individual clients.
|
|
2514
|
+
*
|
|
2515
|
+
* Note: This only works in stateful mode. If the session ID doesn't exist,
|
|
2516
|
+
* the notification is silently discarded.
|
|
2517
|
+
*
|
|
2518
|
+
* @param sessionId - The target session ID (from getActiveSessions())
|
|
2519
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2520
|
+
* @param params - Optional parameters to include in the notification
|
|
2521
|
+
* @returns true if the notification was sent, false if session not found
|
|
2522
|
+
*
|
|
2523
|
+
* @example
|
|
2524
|
+
* ```typescript
|
|
2525
|
+
* const sessions = server.getActiveSessions();
|
|
2526
|
+
*
|
|
2527
|
+
* // Send different messages to different clients
|
|
2528
|
+
* sessions.forEach((sessionId, index) => {
|
|
2529
|
+
* server.sendNotificationToSession(sessionId, "custom/welcome", {
|
|
2530
|
+
* message: `Hello client #${index + 1}!`,
|
|
2531
|
+
* clientNumber: index + 1
|
|
2532
|
+
* });
|
|
2533
|
+
* });
|
|
2534
|
+
* ```
|
|
2535
|
+
*/
|
|
2536
|
+
async sendNotificationToSession(sessionId, method, params) {
|
|
2537
|
+
const session = this.sessions.get(sessionId);
|
|
2538
|
+
if (!session) {
|
|
2539
|
+
return false;
|
|
2540
|
+
}
|
|
2541
|
+
const notification = {
|
|
2542
|
+
jsonrpc: "2.0",
|
|
2543
|
+
method,
|
|
2544
|
+
...params && { params }
|
|
2545
|
+
};
|
|
2546
|
+
try {
|
|
2547
|
+
await session.transport.send(notification);
|
|
2548
|
+
return true;
|
|
2549
|
+
} catch (error) {
|
|
2550
|
+
console.warn(
|
|
2551
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2552
|
+
error
|
|
2553
|
+
);
|
|
2554
|
+
return false;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
// Store the roots changed callback
|
|
2558
|
+
onRootsChangedCallback;
|
|
2559
|
+
/**
|
|
2560
|
+
* Register a callback for when a client's roots change
|
|
2561
|
+
*
|
|
2562
|
+
* When a client sends a `notifications/roots/list_changed` notification,
|
|
2563
|
+
* the server will automatically request the updated roots list and call
|
|
2564
|
+
* this callback with the new roots.
|
|
2565
|
+
*
|
|
2566
|
+
* @param callback - Function called with the updated roots array
|
|
2567
|
+
*
|
|
2568
|
+
* @example
|
|
2569
|
+
* ```typescript
|
|
2570
|
+
* server.onRootsChanged(async (roots) => {
|
|
2571
|
+
* console.log("Client roots updated:", roots);
|
|
2572
|
+
* roots.forEach(root => {
|
|
2573
|
+
* console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
|
|
2574
|
+
* });
|
|
2575
|
+
* });
|
|
2576
|
+
* ```
|
|
2577
|
+
*/
|
|
2578
|
+
onRootsChanged(callback) {
|
|
2579
|
+
this.onRootsChangedCallback = callback;
|
|
2580
|
+
return this;
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Request the current roots list from a specific client session
|
|
2584
|
+
*
|
|
2585
|
+
* This sends a `roots/list` request to the client and returns
|
|
2586
|
+
* the list of roots the client has configured.
|
|
2587
|
+
*
|
|
2588
|
+
* @param sessionId - The session ID of the client to query
|
|
2589
|
+
* @returns Array of roots, or null if the session doesn't exist or request fails
|
|
2590
|
+
*
|
|
2591
|
+
* @example
|
|
2592
|
+
* ```typescript
|
|
2593
|
+
* const sessions = server.getActiveSessions();
|
|
2594
|
+
* if (sessions.length > 0) {
|
|
2595
|
+
* const roots = await server.listRoots(sessions[0]);
|
|
2596
|
+
* if (roots) {
|
|
2597
|
+
* console.log(`Client has ${roots.length} roots:`);
|
|
2598
|
+
* roots.forEach(r => console.log(` - ${r.uri}`));
|
|
2599
|
+
* }
|
|
2600
|
+
* }
|
|
2601
|
+
* ```
|
|
2602
|
+
*/
|
|
2603
|
+
async listRoots(sessionId) {
|
|
2604
|
+
const session = this.sessions.get(sessionId);
|
|
2605
|
+
if (!session) {
|
|
2606
|
+
return null;
|
|
2607
|
+
}
|
|
2608
|
+
try {
|
|
2609
|
+
const request = {
|
|
2610
|
+
jsonrpc: "2.0",
|
|
2611
|
+
id: generateUUID(),
|
|
2612
|
+
method: "roots/list",
|
|
2613
|
+
params: {}
|
|
2614
|
+
};
|
|
2615
|
+
const response = await session.transport.send(request);
|
|
2616
|
+
if (response && typeof response === "object" && "roots" in response) {
|
|
2617
|
+
return response.roots;
|
|
2618
|
+
}
|
|
2619
|
+
return [];
|
|
2620
|
+
} catch (error) {
|
|
2621
|
+
console.warn(
|
|
2622
|
+
`[MCP] Failed to list roots from session ${sessionId}:`,
|
|
2623
|
+
error
|
|
2624
|
+
);
|
|
2625
|
+
return null;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2200
2628
|
/**
|
|
2201
2629
|
* Mount MCP Inspector UI at /inspector
|
|
2202
2630
|
*
|
|
@@ -2213,7 +2641,7 @@ if (container && Component) {
|
|
|
2213
2641
|
* @example
|
|
2214
2642
|
* If @mcp-use/inspector is installed:
|
|
2215
2643
|
* - Inspector UI available at http://localhost:PORT/inspector
|
|
2216
|
-
* - Automatically connects to http://localhost:PORT/mcp
|
|
2644
|
+
* - Automatically connects to http://localhost:PORT/mcp (or /sse)
|
|
2217
2645
|
*
|
|
2218
2646
|
* If not installed:
|
|
2219
2647
|
* - Server continues to function normally
|
|
@@ -2232,7 +2660,14 @@ if (container && Component) {
|
|
|
2232
2660
|
}
|
|
2233
2661
|
try {
|
|
2234
2662
|
const { mountInspector } = await import("@mcp-use/inspector");
|
|
2235
|
-
|
|
2663
|
+
const mcpUrl = `http://${this.serverHost}:${this.serverPort}/mcp`;
|
|
2664
|
+
const autoConnectConfig = JSON.stringify({
|
|
2665
|
+
url: mcpUrl,
|
|
2666
|
+
name: "Local MCP Server",
|
|
2667
|
+
transportType: "sse",
|
|
2668
|
+
connectionType: "Direct"
|
|
2669
|
+
});
|
|
2670
|
+
mountInspector(this.app, { autoConnectUrl: autoConnectConfig });
|
|
2236
2671
|
this.inspectorMounted = true;
|
|
2237
2672
|
console.log(
|
|
2238
2673
|
`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`
|
|
@@ -2556,7 +2991,9 @@ function createMCPServer(name, config = {}) {
|
|
|
2556
2991
|
version: config.version || "1.0.0",
|
|
2557
2992
|
description: config.description,
|
|
2558
2993
|
host: config.host,
|
|
2559
|
-
baseUrl: config.baseUrl
|
|
2994
|
+
baseUrl: config.baseUrl,
|
|
2995
|
+
allowedOrigins: config.allowedOrigins,
|
|
2996
|
+
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs
|
|
2560
2997
|
});
|
|
2561
2998
|
return instance;
|
|
2562
2999
|
}
|