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.
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 +605 -168
  40. package/dist/src/server/index.js +605 -168
  41. package/dist/src/server/mcp-server.d.ts +201 -10
  42. package/dist/src/server/mcp-server.d.ts.map +1 -1
  43. package/dist/src/server/types/common.d.ts +28 -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 || []
@@ -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
- * 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.
@@ -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 endpoint = "/mcp";
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
- this.app.post(endpoint, async (c) => {
1837
- const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
1838
- try {
1839
- expressReq.body = await c.req.json();
1840
- } catch {
1841
- expressReq.body = {};
1842
- }
1843
- const transport = new StreamableHTTPServerTransport({
1844
- sessionIdGenerator: void 0,
1845
- enableJsonResponse: true
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
- if (expressRes._closeHandler) {
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
- await this.server.connect(transport);
1853
- await this.waitForRequestComplete(
1854
- transport,
1855
- expressReq,
1856
- expressRes,
1857
- expressReq.body
1858
- );
1859
- const response = getResponse();
1860
- if (response) {
1861
- return response;
1862
- }
1863
- return c.text("", 200);
1864
- });
1865
- this.app.get(endpoint, async (c) => {
1866
- const transport = new StreamableHTTPServerTransport({
1867
- sessionIdGenerator: void 0,
1868
- enableJsonResponse: true
1869
- });
1870
- c.req.raw.signal?.addEventListener("abort", () => {
1871
- transport.close();
1872
- });
1873
- const { readable, writable } = new globalThis.TransformStream();
1874
- const writer = writable.getWriter();
1875
- const encoder = new TextEncoder();
1876
- let resolveResponse;
1877
- const responsePromise = new Promise((resolve) => {
1878
- resolveResponse = resolve;
1879
- });
1880
- let headersSent = false;
1881
- const headers = {};
1882
- let statusCode = 200;
1883
- const expressRes = {
1884
- statusCode: 200,
1885
- headersSent: false,
1886
- status: /* @__PURE__ */ __name((code) => {
1887
- statusCode = code;
1888
- expressRes.statusCode = code;
1889
- return expressRes;
1890
- }, "status"),
1891
- setHeader: /* @__PURE__ */ __name((name, value) => {
1892
- if (!headersSent) {
1893
- headers[name] = Array.isArray(value) ? value.join(", ") : value;
1894
- }
1895
- }, "setHeader"),
1896
- getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
1897
- write: /* @__PURE__ */ __name((chunk) => {
1898
- if (!headersSent) {
1899
- headersSent = true;
1900
- resolveResponse(
1901
- new Response(readable, {
1902
- status: statusCode,
1903
- headers
1904
- })
1905
- );
1906
- }
1907
- const data = typeof chunk === "string" ? encoder.encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
1908
- writer.write(data);
1909
- return true;
1910
- }, "write"),
1911
- end: /* @__PURE__ */ __name((chunk) => {
1912
- if (chunk) {
1913
- expressRes.write(chunk);
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
- if (!headersSent) {
1916
- headersSent = true;
1917
- resolveResponse(
1918
- new Response(null, {
1919
- status: statusCode,
1920
- headers
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
- writer.close();
1926
- }
1927
- }, "end"),
1928
- on: /* @__PURE__ */ __name((event, handler) => {
1929
- if (event === "close") {
1930
- expressRes._closeHandler = handler;
1931
- }
1932
- }, "on"),
1933
- once: /* @__PURE__ */ __name(() => {
1934
- }, "once"),
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
- return responsePromise;
1974
- });
1975
- this.app.delete(endpoint, async (c) => {
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 ${endpoint}`);
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
- mountInspector(this.app);
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
  }