mcp-use 1.5.0-canary.3 → 1.5.0-canary.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{chunk-WERYJ6PF.js → chunk-2AOGMX4T.js} +1 -1
  3. package/dist/{chunk-DSBKVAWD.js → chunk-2JBWOW4S.js} +152 -0
  4. package/dist/{chunk-UT7O4SIJ.js → chunk-BWOTID2D.js} +209 -75
  5. package/dist/{chunk-GPAOZN2F.js → chunk-QRABML5H.js} +15 -3
  6. package/dist/index.cjs +395 -84
  7. package/dist/index.d.ts +4 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +30 -10
  10. package/dist/src/agents/index.cjs +1 -0
  11. package/dist/src/agents/index.js +2 -2
  12. package/dist/src/browser.cjs +351 -72
  13. package/dist/src/browser.d.ts +2 -0
  14. package/dist/src/browser.d.ts.map +1 -1
  15. package/dist/src/browser.js +2 -2
  16. package/dist/src/client/browser.d.ts.map +1 -1
  17. package/dist/src/client/prompts.cjs +3 -0
  18. package/dist/src/client/prompts.js +2 -2
  19. package/dist/src/client.d.ts +8 -0
  20. package/dist/src/client.d.ts.map +1 -1
  21. package/dist/src/config.d.ts +2 -1
  22. package/dist/src/config.d.ts.map +1 -1
  23. package/dist/src/connectors/base.d.ts +79 -1
  24. package/dist/src/connectors/base.d.ts.map +1 -1
  25. package/dist/src/connectors/http.d.ts +1 -0
  26. package/dist/src/connectors/http.d.ts.map +1 -1
  27. package/dist/src/connectors/stdio.d.ts.map +1 -1
  28. package/dist/src/connectors/websocket.d.ts +6 -0
  29. package/dist/src/connectors/websocket.d.ts.map +1 -1
  30. package/dist/src/react/index.cjs +365 -73
  31. package/dist/src/react/index.d.ts +1 -0
  32. package/dist/src/react/index.d.ts.map +1 -1
  33. package/dist/src/react/index.js +3 -3
  34. package/dist/src/react/types.d.ts +9 -1
  35. package/dist/src/react/types.d.ts.map +1 -1
  36. package/dist/src/react/useMcp.d.ts.map +1 -1
  37. package/dist/src/server/adapters/mcp-ui-adapter.d.ts +1 -0
  38. package/dist/src/server/adapters/mcp-ui-adapter.d.ts.map +1 -1
  39. package/dist/src/server/index.cjs +441 -28
  40. package/dist/src/server/index.js +441 -28
  41. package/dist/src/server/mcp-server.d.ts +179 -2
  42. package/dist/src/server/mcp-server.d.ts.map +1 -1
  43. package/dist/src/server/types/common.d.ts +2 -0
  44. package/dist/src/server/types/common.d.ts.map +1 -1
  45. package/dist/src/session.d.ts +40 -1
  46. package/dist/src/session.d.ts.map +1 -1
  47. package/package.json +10 -4
@@ -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 uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}.html` : `ui://widget/${definition.name}`;
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 = `ui://widget/${definition.widget}`;
863
+ resourceUri = this.generateWidgetUri(definition.widget);
806
864
  mimeType = "text/uri-list";
807
865
  break;
808
866
  case "rawHtml":
809
- resourceUri = `ui://widget/${definition.name}`;
867
+ resourceUri = this.generateWidgetUri(definition.name);
810
868
  mimeType = "text/html";
811
869
  break;
812
870
  case "remoteDom":
813
- resourceUri = `ui://widget/${definition.name}`;
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 = `ui://widget/${definition.name}.html`;
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: `ui://widget/${definition.name}-{id}.html`,
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 = `ui://widget/${definition.name}-${randomId}.html`;
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;
@@ -1633,6 +1723,7 @@ if (container && Component) {
1633
1723
  resource_domains: [
1634
1724
  "https://*.oaistatic.com",
1635
1725
  "https://*.oaiusercontent.com",
1726
+ "https://*.openai.com",
1636
1727
  // always also add the base url of the server
1637
1728
  ...this.getServerBaseUrl() ? [this.getServerBaseUrl()] : [],
1638
1729
  ...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.resource_domains || []
@@ -1669,8 +1760,7 @@ if (container && Component) {
1669
1760
  *
1670
1761
  * Sets up the HTTP transport layer for the MCP server, creating endpoints for
1671
1762
  * Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup.
1672
- * Each request gets its own transport instance to prevent state conflicts between
1673
- * concurrent client connections.
1763
+ * Transports are reused per session ID to maintain state across requests.
1674
1764
  *
1675
1765
  * This method is called automatically when the server starts listening and ensures
1676
1766
  * that MCP clients can communicate with the server over HTTP.
@@ -1688,6 +1778,55 @@ if (container && Component) {
1688
1778
  if (this.mcpMounted) return;
1689
1779
  const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
1690
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
+ }
1691
1830
  const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
1692
1831
  const req = c.req.raw;
1693
1832
  const responseBody = [];
@@ -1797,21 +1936,51 @@ if (container && Component) {
1797
1936
  }, "createExpressLikeObjects");
1798
1937
  this.app.post(endpoint, async (c) => {
1799
1938
  const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
1939
+ let body = {};
1800
1940
  try {
1801
- expressReq.body = await c.req.json();
1941
+ body = await c.req.json();
1942
+ expressReq.body = body;
1802
1943
  } catch {
1803
1944
  expressReq.body = {};
1804
1945
  }
1805
- const transport = new StreamableHTTPServerTransport({
1806
- sessionIdGenerator: void 0,
1807
- enableJsonResponse: true
1808
- });
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
+ }
1809
1979
  if (expressRes._closeHandler) {
1810
1980
  c.req.raw.signal?.addEventListener("abort", () => {
1811
1981
  transport.close();
1812
1982
  });
1813
1983
  }
1814
- await this.server.connect(transport);
1815
1984
  await this.waitForRequestComplete(
1816
1985
  transport,
1817
1986
  expressReq,
@@ -1825,10 +1994,38 @@ if (container && Component) {
1825
1994
  return c.text("", 200);
1826
1995
  });
1827
1996
  this.app.get(endpoint, async (c) => {
1828
- const transport = new StreamableHTTPServerTransport({
1829
- sessionIdGenerator: void 0,
1830
- enableJsonResponse: true
1831
- });
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
+ }
1832
2029
  c.req.raw.signal?.addEventListener("abort", () => {
1833
2030
  transport.close();
1834
2031
  });
@@ -1924,7 +2121,6 @@ if (container && Component) {
1924
2121
  headers: c.req.header(),
1925
2122
  method: c.req.method
1926
2123
  };
1927
- await this.server.connect(transport);
1928
2124
  transport.handleRequest(expressReq, expressRes).catch((err) => {
1929
2125
  console.error("MCP Transport error:", err);
1930
2126
  try {
@@ -1936,14 +2132,38 @@ if (container && Component) {
1936
2132
  });
1937
2133
  this.app.delete(endpoint, async (c) => {
1938
2134
  const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
1939
- const transport = new StreamableHTTPServerTransport({
1940
- sessionIdGenerator: void 0,
1941
- enableJsonResponse: true
1942
- });
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
+ }
1943
2164
  c.req.raw.signal?.addEventListener("abort", () => {
1944
2165
  transport.close();
1945
2166
  });
1946
- await this.server.connect(transport);
1947
2167
  await this.waitForRequestComplete(transport, expressReq, expressRes);
1948
2168
  const response = getResponse();
1949
2169
  if (response) {
@@ -2159,6 +2379,192 @@ if (container && Component) {
2159
2379
  return result;
2160
2380
  };
2161
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
+ }
2162
2568
  /**
2163
2569
  * Mount MCP Inspector UI at /inspector
2164
2570
  *
@@ -2194,7 +2600,14 @@ if (container && Component) {
2194
2600
  }
2195
2601
  try {
2196
2602
  const { mountInspector } = await import("@mcp-use/inspector");
2197
- mountInspector(this.app);
2603
+ const mcpUrl = `http://${this.serverHost}:${this.serverPort}/mcp`;
2604
+ const autoConnectConfig = JSON.stringify({
2605
+ url: mcpUrl,
2606
+ name: "Local MCP Server",
2607
+ transportType: "sse",
2608
+ connectionType: "Direct"
2609
+ });
2610
+ mountInspector(this.app, { autoConnectUrl: autoConnectConfig });
2198
2611
  this.inspectorMounted = true;
2199
2612
  console.log(
2200
2613
  `[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`