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
@@ -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 uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}.html` : `ui://widget/${definition.name}`;
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 = `ui://widget/${definition.widget}`;
901
+ resourceUri = this.generateWidgetUri(definition.widget);
844
902
  mimeType = "text/uri-list";
845
903
  break;
846
904
  case "rawHtml":
847
- resourceUri = `ui://widget/${definition.name}`;
905
+ resourceUri = this.generateWidgetUri(definition.name);
848
906
  mimeType = "text/html";
849
907
  break;
850
908
  case "remoteDom":
851
- resourceUri = `ui://widget/${definition.name}`;
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 = `ui://widget/${definition.name}.html`;
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: `ui://widget/${definition.name}-{id}.html`,
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 = `ui://widget/${definition.name}-${randomId}.html`;
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 || []
@@ -1707,8 +1798,7 @@ if (container && Component) {
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
- * Each request gets its own transport instance to prevent state conflicts between
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.
@@ -1726,6 +1816,55 @@ if (container && Component) {
1726
1816
  if (this.mcpMounted) return;
1727
1817
  const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
1728
1818
  const endpoint = "/mcp";
1819
+ const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
1820
+ const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
1821
+ if (sessionId && this.sessions.has(sessionId)) {
1822
+ const session = this.sessions.get(sessionId);
1823
+ session.lastAccessedAt = Date.now();
1824
+ return session.transport;
1825
+ }
1826
+ if (!isInit && sessionId) {
1827
+ return null;
1828
+ }
1829
+ if (!isInit && !sessionId) {
1830
+ return null;
1831
+ }
1832
+ const transport = new StreamableHTTPServerTransport({
1833
+ sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
1834
+ enableJsonResponse: true,
1835
+ allowedOrigins: this.config.allowedOrigins,
1836
+ enableDnsRebindingProtection: this.config.allowedOrigins !== void 0 && this.config.allowedOrigins.length > 0,
1837
+ onsessioninitialized: /* @__PURE__ */ __name((id) => {
1838
+ if (id) {
1839
+ this.sessions.set(id, {
1840
+ transport,
1841
+ lastAccessedAt: Date.now()
1842
+ });
1843
+ }
1844
+ }, "onsessioninitialized"),
1845
+ onsessionclosed: /* @__PURE__ */ __name((id) => {
1846
+ if (id) {
1847
+ this.sessions.delete(id);
1848
+ }
1849
+ }, "onsessionclosed")
1850
+ });
1851
+ await this.server.connect(transport);
1852
+ return transport;
1853
+ }, "getOrCreateTransport");
1854
+ if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
1855
+ this.idleCleanupInterval = setInterval(() => {
1856
+ const now = Date.now();
1857
+ for (const [sessionId, session] of this.sessions.entries()) {
1858
+ if (now - session.lastAccessedAt > idleTimeoutMs) {
1859
+ try {
1860
+ session.transport.close();
1861
+ } catch (error) {
1862
+ }
1863
+ this.sessions.delete(sessionId);
1864
+ }
1865
+ }
1866
+ }, 6e4);
1867
+ }
1729
1868
  const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
1730
1869
  const req = c.req.raw;
1731
1870
  const responseBody = [];
@@ -1835,21 +1974,51 @@ if (container && Component) {
1835
1974
  }, "createExpressLikeObjects");
1836
1975
  this.app.post(endpoint, async (c) => {
1837
1976
  const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
1977
+ let body = {};
1838
1978
  try {
1839
- expressReq.body = await c.req.json();
1979
+ body = await c.req.json();
1980
+ expressReq.body = body;
1840
1981
  } catch {
1841
1982
  expressReq.body = {};
1842
1983
  }
1843
- const transport = new StreamableHTTPServerTransport({
1844
- sessionIdGenerator: void 0,
1845
- enableJsonResponse: true
1846
- });
1984
+ const isInit = body?.method === "initialize";
1985
+ const sessionId = c.req.header("mcp-session-id");
1986
+ const transport = await getOrCreateTransport(sessionId, isInit);
1987
+ if (!transport) {
1988
+ if (sessionId) {
1989
+ return c.json(
1990
+ {
1991
+ jsonrpc: "2.0",
1992
+ error: {
1993
+ code: -32e3,
1994
+ message: "Session not found or expired"
1995
+ },
1996
+ id: null
1997
+ },
1998
+ 404
1999
+ );
2000
+ } else {
2001
+ return c.json(
2002
+ {
2003
+ jsonrpc: "2.0",
2004
+ error: {
2005
+ code: -32e3,
2006
+ message: "Bad Request: Mcp-Session-Id header is required"
2007
+ },
2008
+ id: null
2009
+ },
2010
+ 400
2011
+ );
2012
+ }
2013
+ }
2014
+ if (sessionId && this.sessions.has(sessionId)) {
2015
+ this.sessions.get(sessionId).lastAccessedAt = Date.now();
2016
+ }
1847
2017
  if (expressRes._closeHandler) {
1848
2018
  c.req.raw.signal?.addEventListener("abort", () => {
1849
2019
  transport.close();
1850
2020
  });
1851
2021
  }
1852
- await this.server.connect(transport);
1853
2022
  await this.waitForRequestComplete(
1854
2023
  transport,
1855
2024
  expressReq,
@@ -1863,10 +2032,38 @@ if (container && Component) {
1863
2032
  return c.text("", 200);
1864
2033
  });
1865
2034
  this.app.get(endpoint, async (c) => {
1866
- const transport = new StreamableHTTPServerTransport({
1867
- sessionIdGenerator: void 0,
1868
- enableJsonResponse: true
1869
- });
2035
+ const sessionId = c.req.header("mcp-session-id");
2036
+ const transport = await getOrCreateTransport(sessionId, false);
2037
+ if (!transport) {
2038
+ if (sessionId) {
2039
+ return c.json(
2040
+ {
2041
+ jsonrpc: "2.0",
2042
+ error: {
2043
+ code: -32e3,
2044
+ message: "Session not found or expired"
2045
+ },
2046
+ id: null
2047
+ },
2048
+ 404
2049
+ );
2050
+ } else {
2051
+ return c.json(
2052
+ {
2053
+ jsonrpc: "2.0",
2054
+ error: {
2055
+ code: -32e3,
2056
+ message: "Bad Request: Mcp-Session-Id header is required"
2057
+ },
2058
+ id: null
2059
+ },
2060
+ 400
2061
+ );
2062
+ }
2063
+ }
2064
+ if (sessionId && this.sessions.has(sessionId)) {
2065
+ this.sessions.get(sessionId).lastAccessedAt = Date.now();
2066
+ }
1870
2067
  c.req.raw.signal?.addEventListener("abort", () => {
1871
2068
  transport.close();
1872
2069
  });
@@ -1962,7 +2159,6 @@ if (container && Component) {
1962
2159
  headers: c.req.header(),
1963
2160
  method: c.req.method
1964
2161
  };
1965
- await this.server.connect(transport);
1966
2162
  transport.handleRequest(expressReq, expressRes).catch((err) => {
1967
2163
  console.error("MCP Transport error:", err);
1968
2164
  try {
@@ -1974,14 +2170,38 @@ if (container && Component) {
1974
2170
  });
1975
2171
  this.app.delete(endpoint, async (c) => {
1976
2172
  const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
1977
- const transport = new StreamableHTTPServerTransport({
1978
- sessionIdGenerator: void 0,
1979
- enableJsonResponse: true
1980
- });
2173
+ const sessionId = c.req.header("mcp-session-id");
2174
+ const transport = await getOrCreateTransport(sessionId, false);
2175
+ if (!transport) {
2176
+ if (sessionId) {
2177
+ return c.json(
2178
+ {
2179
+ jsonrpc: "2.0",
2180
+ error: {
2181
+ code: -32e3,
2182
+ message: "Session not found or expired"
2183
+ },
2184
+ id: null
2185
+ },
2186
+ 404
2187
+ );
2188
+ } else {
2189
+ return c.json(
2190
+ {
2191
+ jsonrpc: "2.0",
2192
+ error: {
2193
+ code: -32e3,
2194
+ message: "Bad Request: Mcp-Session-Id header is required"
2195
+ },
2196
+ id: null
2197
+ },
2198
+ 400
2199
+ );
2200
+ }
2201
+ }
1981
2202
  c.req.raw.signal?.addEventListener("abort", () => {
1982
2203
  transport.close();
1983
2204
  });
1984
- await this.server.connect(transport);
1985
2205
  await this.waitForRequestComplete(transport, expressReq, expressRes);
1986
2206
  const response = getResponse();
1987
2207
  if (response) {
@@ -2197,6 +2417,192 @@ if (container && Component) {
2197
2417
  return result;
2198
2418
  };
2199
2419
  }
2420
+ /**
2421
+ * Get array of active session IDs
2422
+ *
2423
+ * Returns an array of all currently active session IDs. This is useful for
2424
+ * sending targeted notifications to specific clients or iterating over
2425
+ * connected clients.
2426
+ *
2427
+ * Note: This only works in stateful mode. In stateless mode (edge environments),
2428
+ * this will return an empty array.
2429
+ *
2430
+ * @returns Array of active session ID strings
2431
+ *
2432
+ * @example
2433
+ * ```typescript
2434
+ * const sessions = server.getActiveSessions();
2435
+ * console.log(`${sessions.length} clients connected`);
2436
+ *
2437
+ * // Send notification to first connected client
2438
+ * if (sessions.length > 0) {
2439
+ * server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
2440
+ * }
2441
+ * ```
2442
+ */
2443
+ getActiveSessions() {
2444
+ return Array.from(this.sessions.keys());
2445
+ }
2446
+ /**
2447
+ * Send a notification to all connected clients
2448
+ *
2449
+ * Broadcasts a JSON-RPC notification to all active sessions. Notifications are
2450
+ * one-way messages that do not expect a response from the client.
2451
+ *
2452
+ * Note: This only works in stateful mode with active sessions. If no sessions
2453
+ * are connected, the notification is silently discarded (per MCP spec: "server MAY send").
2454
+ *
2455
+ * @param method - The notification method name (e.g., "custom/my-notification")
2456
+ * @param params - Optional parameters to include in the notification
2457
+ *
2458
+ * @example
2459
+ * ```typescript
2460
+ * // Send a simple notification to all clients
2461
+ * server.sendNotification("custom/server-status", {
2462
+ * status: "ready",
2463
+ * timestamp: new Date().toISOString()
2464
+ * });
2465
+ *
2466
+ * // Notify all clients that resources have changed
2467
+ * server.sendNotification("notifications/resources/list_changed");
2468
+ * ```
2469
+ */
2470
+ async sendNotification(method, params) {
2471
+ const notification = {
2472
+ jsonrpc: "2.0",
2473
+ method,
2474
+ ...params && { params }
2475
+ };
2476
+ for (const [sessionId, session] of this.sessions.entries()) {
2477
+ try {
2478
+ await session.transport.send(notification);
2479
+ } catch (error) {
2480
+ console.warn(
2481
+ `[MCP] Failed to send notification to session ${sessionId}:`,
2482
+ error
2483
+ );
2484
+ }
2485
+ }
2486
+ }
2487
+ /**
2488
+ * Send a notification to a specific client session
2489
+ *
2490
+ * Sends a JSON-RPC notification to a single client identified by their session ID.
2491
+ * This allows sending customized notifications to individual clients.
2492
+ *
2493
+ * Note: This only works in stateful mode. If the session ID doesn't exist,
2494
+ * the notification is silently discarded.
2495
+ *
2496
+ * @param sessionId - The target session ID (from getActiveSessions())
2497
+ * @param method - The notification method name (e.g., "custom/my-notification")
2498
+ * @param params - Optional parameters to include in the notification
2499
+ * @returns true if the notification was sent, false if session not found
2500
+ *
2501
+ * @example
2502
+ * ```typescript
2503
+ * const sessions = server.getActiveSessions();
2504
+ *
2505
+ * // Send different messages to different clients
2506
+ * sessions.forEach((sessionId, index) => {
2507
+ * server.sendNotificationToSession(sessionId, "custom/welcome", {
2508
+ * message: `Hello client #${index + 1}!`,
2509
+ * clientNumber: index + 1
2510
+ * });
2511
+ * });
2512
+ * ```
2513
+ */
2514
+ async sendNotificationToSession(sessionId, method, params) {
2515
+ const session = this.sessions.get(sessionId);
2516
+ if (!session) {
2517
+ return false;
2518
+ }
2519
+ const notification = {
2520
+ jsonrpc: "2.0",
2521
+ method,
2522
+ ...params && { params }
2523
+ };
2524
+ try {
2525
+ await session.transport.send(notification);
2526
+ return true;
2527
+ } catch (error) {
2528
+ console.warn(
2529
+ `[MCP] Failed to send notification to session ${sessionId}:`,
2530
+ error
2531
+ );
2532
+ return false;
2533
+ }
2534
+ }
2535
+ // Store the roots changed callback
2536
+ onRootsChangedCallback;
2537
+ /**
2538
+ * Register a callback for when a client's roots change
2539
+ *
2540
+ * When a client sends a `notifications/roots/list_changed` notification,
2541
+ * the server will automatically request the updated roots list and call
2542
+ * this callback with the new roots.
2543
+ *
2544
+ * @param callback - Function called with the updated roots array
2545
+ *
2546
+ * @example
2547
+ * ```typescript
2548
+ * server.onRootsChanged(async (roots) => {
2549
+ * console.log("Client roots updated:", roots);
2550
+ * roots.forEach(root => {
2551
+ * console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
2552
+ * });
2553
+ * });
2554
+ * ```
2555
+ */
2556
+ onRootsChanged(callback) {
2557
+ this.onRootsChangedCallback = callback;
2558
+ return this;
2559
+ }
2560
+ /**
2561
+ * Request the current roots list from a specific client session
2562
+ *
2563
+ * This sends a `roots/list` request to the client and returns
2564
+ * the list of roots the client has configured.
2565
+ *
2566
+ * @param sessionId - The session ID of the client to query
2567
+ * @returns Array of roots, or null if the session doesn't exist or request fails
2568
+ *
2569
+ * @example
2570
+ * ```typescript
2571
+ * const sessions = server.getActiveSessions();
2572
+ * if (sessions.length > 0) {
2573
+ * const roots = await server.listRoots(sessions[0]);
2574
+ * if (roots) {
2575
+ * console.log(`Client has ${roots.length} roots:`);
2576
+ * roots.forEach(r => console.log(` - ${r.uri}`));
2577
+ * }
2578
+ * }
2579
+ * ```
2580
+ */
2581
+ async listRoots(sessionId) {
2582
+ const session = this.sessions.get(sessionId);
2583
+ if (!session) {
2584
+ return null;
2585
+ }
2586
+ try {
2587
+ const request = {
2588
+ jsonrpc: "2.0",
2589
+ id: generateUUID(),
2590
+ method: "roots/list",
2591
+ params: {}
2592
+ };
2593
+ const response = await session.transport.send(request);
2594
+ if (response && typeof response === "object" && "roots" in response) {
2595
+ return response.roots;
2596
+ }
2597
+ return [];
2598
+ } catch (error) {
2599
+ console.warn(
2600
+ `[MCP] Failed to list roots from session ${sessionId}:`,
2601
+ error
2602
+ );
2603
+ return null;
2604
+ }
2605
+ }
2200
2606
  /**
2201
2607
  * Mount MCP Inspector UI at /inspector
2202
2608
  *
@@ -2232,7 +2638,14 @@ if (container && Component) {
2232
2638
  }
2233
2639
  try {
2234
2640
  const { mountInspector } = await import("@mcp-use/inspector");
2235
- mountInspector(this.app);
2641
+ const mcpUrl = `http://${this.serverHost}:${this.serverPort}/mcp`;
2642
+ const autoConnectConfig = JSON.stringify({
2643
+ url: mcpUrl,
2644
+ name: "Local MCP Server",
2645
+ transportType: "sse",
2646
+ connectionType: "Direct"
2647
+ });
2648
+ mountInspector(this.app, { autoConnectUrl: autoConnectConfig });
2236
2649
  this.inspectorMounted = true;
2237
2650
  console.log(
2238
2651
  `[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`