mcp-use 1.5.0 → 1.5.1-canary.1

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 (49) 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 +730 -192
  40. package/dist/src/server/index.js +730 -192
  41. package/dist/src/server/logging.d.ts +6 -0
  42. package/dist/src/server/logging.d.ts.map +1 -1
  43. package/dist/src/server/mcp-server.d.ts +201 -10
  44. package/dist/src/server/mcp-server.d.ts.map +1 -1
  45. package/dist/src/server/types/common.d.ts +59 -0
  46. package/dist/src/server/types/common.d.ts.map +1 -1
  47. package/dist/src/session.d.ts +40 -1
  48. package/dist/src/session.d.ts.map +1 -1
  49. 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": {
@@ -300,15 +301,46 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
300
301
  __name(adaptConnectMiddleware, "adaptConnectMiddleware");
301
302
 
302
303
  // src/server/logging.ts
304
+ var isDeno = typeof globalThis.Deno !== "undefined";
305
+ function getEnv(key) {
306
+ if (isDeno) {
307
+ return globalThis.Deno.env.get(key);
308
+ }
309
+ return typeof process !== "undefined" && process.env ? process.env[key] : void 0;
310
+ }
311
+ __name(getEnv, "getEnv");
312
+ function isDebugMode() {
313
+ const debugEnv = getEnv("DEBUG");
314
+ return debugEnv !== void 0 && debugEnv !== "" && debugEnv !== "0" && debugEnv.toLowerCase() !== "false";
315
+ }
316
+ __name(isDebugMode, "isDebugMode");
317
+ function formatForLogging(obj) {
318
+ try {
319
+ return JSON.stringify(obj, null, 2);
320
+ } catch {
321
+ return String(obj);
322
+ }
323
+ }
324
+ __name(formatForLogging, "formatForLogging");
303
325
  async function requestLogger(c, next) {
304
326
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
305
327
  const method = c.req.method;
306
328
  const url = c.req.url;
307
- let body = null;
308
- if (method === "POST" && url.includes("/mcp")) {
329
+ const debugMode = isDebugMode();
330
+ let requestBody = null;
331
+ let requestHeaders = {};
332
+ if (debugMode) {
333
+ const allHeaders = c.req.header();
334
+ if (allHeaders) {
335
+ requestHeaders = allHeaders;
336
+ }
337
+ }
338
+ if (method !== "GET" && method !== "HEAD") {
309
339
  try {
310
340
  const clonedRequest = c.req.raw.clone();
311
- body = await clonedRequest.json().catch(() => null);
341
+ requestBody = await clonedRequest.json().catch(() => {
342
+ return clonedRequest.text().catch(() => null);
343
+ });
312
344
  } catch {
313
345
  }
314
346
  }
@@ -325,26 +357,89 @@ async function requestLogger(c, next) {
325
357
  statusColor = "\x1B[35m";
326
358
  }
327
359
  let logMessage = `[${timestamp}] ${method} \x1B[1m${new URL(url).pathname}\x1B[0m`;
328
- if (method === "POST" && url.includes("/mcp") && body?.method) {
329
- logMessage += ` \x1B[1m[${body.method}]\x1B[0m`;
360
+ if (method === "POST" && url.includes("/mcp") && requestBody?.method) {
361
+ logMessage += ` \x1B[1m[${requestBody.method}]\x1B[0m`;
330
362
  }
331
363
  logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
332
364
  console.log(logMessage);
365
+ if (debugMode) {
366
+ console.log("\n\x1B[36m" + "=".repeat(80) + "\x1B[0m");
367
+ console.log("\x1B[1m\x1B[36m[DEBUG] Request Details\x1B[0m");
368
+ console.log("\x1B[36m" + "-".repeat(80) + "\x1B[0m");
369
+ if (Object.keys(requestHeaders).length > 0) {
370
+ console.log("\x1B[33mRequest Headers:\x1B[0m");
371
+ console.log(formatForLogging(requestHeaders));
372
+ }
373
+ if (requestBody !== null) {
374
+ console.log("\x1B[33mRequest Body:\x1B[0m");
375
+ if (typeof requestBody === "string") {
376
+ console.log(requestBody);
377
+ } else {
378
+ console.log(formatForLogging(requestBody));
379
+ }
380
+ }
381
+ const responseHeaders = {};
382
+ c.res.headers.forEach((value, key) => {
383
+ responseHeaders[key] = value;
384
+ });
385
+ if (Object.keys(responseHeaders).length > 0) {
386
+ console.log("\x1B[33mResponse Headers:\x1B[0m");
387
+ console.log(formatForLogging(responseHeaders));
388
+ }
389
+ try {
390
+ if (c.res.body !== null && c.res.body !== void 0) {
391
+ try {
392
+ const clonedResponse = c.res.clone();
393
+ const responseBody = await clonedResponse.text().catch(() => null);
394
+ if (responseBody !== null && responseBody.length > 0) {
395
+ console.log("\x1B[33mResponse Body:\x1B[0m");
396
+ try {
397
+ const jsonBody = JSON.parse(responseBody);
398
+ console.log(formatForLogging(jsonBody));
399
+ } catch {
400
+ const maxLength = 1e4;
401
+ if (responseBody.length > maxLength) {
402
+ console.log(
403
+ responseBody.substring(0, maxLength) + `
404
+ ... (truncated, ${responseBody.length - maxLength} more characters)`
405
+ );
406
+ } else {
407
+ console.log(responseBody);
408
+ }
409
+ }
410
+ } else {
411
+ console.log("\x1B[33mResponse Body:\x1B[0m (empty)");
412
+ }
413
+ } catch (cloneError) {
414
+ console.log("\x1B[33mResponse Body:\x1B[0m (unable to clone/read)");
415
+ }
416
+ } else {
417
+ console.log("\x1B[33mResponse Body:\x1B[0m (no body)");
418
+ }
419
+ } catch (error) {
420
+ console.log("\x1B[33mResponse Body:\x1B[0m (unable to read)");
421
+ }
422
+ console.log("\x1B[36m" + "=".repeat(80) + "\x1B[0m\n");
423
+ }
333
424
  }
334
425
  __name(requestLogger, "requestLogger");
335
426
 
336
427
  // src/server/mcp-server.ts
428
+ function generateUUID() {
429
+ return globalThis.crypto.randomUUID();
430
+ }
431
+ __name(generateUUID, "generateUUID");
337
432
  var TMP_MCP_USE_DIR = ".mcp-use";
338
- var isDeno = typeof globalThis.Deno !== "undefined";
339
- function getEnv(key) {
340
- if (isDeno) {
433
+ var isDeno2 = typeof globalThis.Deno !== "undefined";
434
+ function getEnv2(key) {
435
+ if (isDeno2) {
341
436
  return globalThis.Deno.env.get(key);
342
437
  }
343
438
  return process.env[key];
344
439
  }
345
- __name(getEnv, "getEnv");
440
+ __name(getEnv2, "getEnv");
346
441
  function getCwd() {
347
- if (isDeno) {
442
+ if (isDeno2) {
348
443
  return globalThis.Deno.cwd();
349
444
  }
350
445
  return process.cwd();
@@ -352,7 +447,7 @@ function getCwd() {
352
447
  __name(getCwd, "getCwd");
353
448
  var fsHelpers = {
354
449
  async readFileSync(path, encoding = "utf8") {
355
- if (isDeno) {
450
+ if (isDeno2) {
356
451
  return await globalThis.Deno.readTextFile(path);
357
452
  }
358
453
  const { readFileSync } = await import("fs");
@@ -360,7 +455,7 @@ var fsHelpers = {
360
455
  return typeof result === "string" ? result : result.toString(encoding);
361
456
  },
362
457
  async readFile(path) {
363
- if (isDeno) {
458
+ if (isDeno2) {
364
459
  const data = await globalThis.Deno.readFile(path);
365
460
  return data.buffer;
366
461
  }
@@ -372,7 +467,7 @@ var fsHelpers = {
372
467
  );
373
468
  },
374
469
  async existsSync(path) {
375
- if (isDeno) {
470
+ if (isDeno2) {
376
471
  try {
377
472
  await globalThis.Deno.stat(path);
378
473
  return true;
@@ -384,7 +479,7 @@ var fsHelpers = {
384
479
  return existsSync(path);
385
480
  },
386
481
  async readdirSync(path) {
387
- if (isDeno) {
482
+ if (isDeno2) {
388
483
  const entries = [];
389
484
  for await (const entry of globalThis.Deno.readDir(path)) {
390
485
  entries.push(entry.name);
@@ -397,7 +492,7 @@ var fsHelpers = {
397
492
  };
398
493
  var pathHelpers = {
399
494
  join(...paths) {
400
- if (isDeno) {
495
+ if (isDeno2) {
401
496
  return paths.join("/").replace(/\/+/g, "/");
402
497
  }
403
498
  return paths.join("/").replace(/\/+/g, "/");
@@ -429,6 +524,9 @@ var McpServer = class {
429
524
  registeredTools = [];
430
525
  registeredPrompts = [];
431
526
  registeredResources = [];
527
+ buildId;
528
+ sessions = /* @__PURE__ */ new Map();
529
+ idleCleanupInterval;
432
530
  /**
433
531
  * Creates a new MCP server instance with Hono integration
434
532
  *
@@ -461,7 +559,9 @@ var McpServer = class {
461
559
  "mcp-session-id",
462
560
  "X-Proxy-Token",
463
561
  "X-Target-URL"
464
- ]
562
+ ],
563
+ // Expose mcp-session-id so browser clients can read it from responses
564
+ exposeHeaders: ["mcp-session-id"]
465
565
  })
466
566
  );
467
567
  this.app.use("*", requestLogger);
@@ -524,7 +624,7 @@ var McpServer = class {
524
624
  if (this.serverBaseUrl) {
525
625
  return this.serverBaseUrl;
526
626
  }
527
- const mcpUrl = getEnv("MCP_URL");
627
+ const mcpUrl = getEnv2("MCP_URL");
528
628
  if (mcpUrl) {
529
629
  return mcpUrl;
530
630
  }
@@ -701,6 +801,11 @@ var McpServer = class {
701
801
  */
702
802
  tool(toolDefinition) {
703
803
  const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
804
+ const context = {
805
+ sample: /* @__PURE__ */ __name(async (params, options) => {
806
+ return await this.createMessage(params, options);
807
+ }, "sample")
808
+ };
704
809
  this.server.registerTool(
705
810
  toolDefinition.name,
706
811
  {
@@ -711,6 +816,9 @@ var McpServer = class {
711
816
  _meta: toolDefinition._meta
712
817
  },
713
818
  async (params) => {
819
+ if (toolDefinition.cb.length >= 2) {
820
+ return await toolDefinition.cb(params, context);
821
+ }
714
822
  return await toolDefinition.cb(params);
715
823
  }
716
824
  );
@@ -767,6 +875,46 @@ var McpServer = class {
767
875
  this.registeredPrompts.push(promptDefinition.name);
768
876
  return this;
769
877
  }
878
+ /**
879
+ * Request LLM sampling from connected clients.
880
+ *
881
+ * This method allows server tools to request LLM completions from clients
882
+ * that support the sampling capability. The client will handle model selection,
883
+ * user approval (human-in-the-loop), and return the generated response.
884
+ *
885
+ * @param params - Sampling request parameters including messages, model preferences, etc.
886
+ * @param options - Optional request options (timeouts, cancellation, etc.)
887
+ * @returns Promise resolving to the generated message from the client's LLM
888
+ *
889
+ * @example
890
+ * ```typescript
891
+ * // In a tool callback
892
+ * server.tool({
893
+ * name: 'analyze-sentiment',
894
+ * description: 'Analyze sentiment using LLM',
895
+ * inputs: [{ name: 'text', type: 'string', required: true }],
896
+ * cb: async (params, ctx) => {
897
+ * const result = await ctx.sample({
898
+ * messages: [{
899
+ * role: 'user',
900
+ * content: { type: 'text', text: `Analyze sentiment: ${params.text}` }
901
+ * }],
902
+ * modelPreferences: {
903
+ * intelligencePriority: 0.8,
904
+ * speedPriority: 0.5
905
+ * }
906
+ * });
907
+ * return {
908
+ * content: [{ type: 'text', text: result.content.text }]
909
+ * };
910
+ * }
911
+ * })
912
+ * ```
913
+ */
914
+ async createMessage(params, options) {
915
+ console.log("createMessage", params, options);
916
+ return await this.server.server.createMessage(params, options);
917
+ }
770
918
  /**
771
919
  * Register a UI widget as both a tool and a resource
772
920
  *
@@ -840,19 +988,19 @@ var McpServer = class {
840
988
  let mimeType;
841
989
  switch (definition.type) {
842
990
  case "externalUrl":
843
- resourceUri = `ui://widget/${definition.widget}`;
991
+ resourceUri = this.generateWidgetUri(definition.widget);
844
992
  mimeType = "text/uri-list";
845
993
  break;
846
994
  case "rawHtml":
847
- resourceUri = `ui://widget/${definition.name}`;
995
+ resourceUri = this.generateWidgetUri(definition.name);
848
996
  mimeType = "text/html";
849
997
  break;
850
998
  case "remoteDom":
851
- resourceUri = `ui://widget/${definition.name}`;
999
+ resourceUri = this.generateWidgetUri(definition.name);
852
1000
  mimeType = "application/vnd.mcp-ui.remote-dom+javascript";
853
1001
  break;
854
1002
  case "appsSdk":
855
- resourceUri = `ui://widget/${definition.name}.html`;
1003
+ resourceUri = this.generateWidgetUri(definition.name, ".html");
856
1004
  mimeType = "text/html+skybridge";
857
1005
  break;
858
1006
  default:
@@ -871,16 +1019,19 @@ var McpServer = class {
871
1019
  readCallback: /* @__PURE__ */ __name(async () => {
872
1020
  const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
873
1021
  const uiResource = this.createWidgetUIResource(definition, params);
1022
+ uiResource.resource.uri = resourceUri;
874
1023
  return {
875
1024
  contents: [uiResource.resource]
876
1025
  };
877
1026
  }, "readCallback")
878
1027
  });
879
1028
  if (definition.type === "appsSdk") {
1029
+ const buildIdPart = this.buildId ? `-${this.buildId}` : "";
1030
+ const uriTemplate = `ui://widget/${definition.name}${buildIdPart}-{id}.html`;
880
1031
  this.resourceTemplate({
881
1032
  name: `${definition.name}-dynamic`,
882
1033
  resourceTemplate: {
883
- uriTemplate: `ui://widget/${definition.name}-{id}.html`,
1034
+ uriTemplate,
884
1035
  name: definition.title || definition.name,
885
1036
  description: definition.description,
886
1037
  mimeType
@@ -891,6 +1042,7 @@ var McpServer = class {
891
1042
  annotations: definition.annotations,
892
1043
  readCallback: /* @__PURE__ */ __name(async (uri, params) => {
893
1044
  const uiResource = this.createWidgetUIResource(definition, {});
1045
+ uiResource.resource.uri = uri.toString();
894
1046
  return {
895
1047
  contents: [uiResource.resource]
896
1048
  };
@@ -922,7 +1074,11 @@ var McpServer = class {
922
1074
  const uiResource = this.createWidgetUIResource(definition, params);
923
1075
  if (definition.type === "appsSdk") {
924
1076
  const randomId = Math.random().toString(36).substring(2, 15);
925
- const uniqueUri = `ui://widget/${definition.name}-${randomId}.html`;
1077
+ const uniqueUri = this.generateWidgetUri(
1078
+ definition.name,
1079
+ ".html",
1080
+ randomId
1081
+ );
926
1082
  const uniqueToolMetadata = {
927
1083
  ...toolMetadata,
928
1084
  "openai/outputTemplate": uniqueUri
@@ -979,7 +1135,8 @@ var McpServer = class {
979
1135
  }
980
1136
  const urlConfig = {
981
1137
  baseUrl: configBaseUrl,
982
- port: configPort
1138
+ port: configPort,
1139
+ buildId: this.buildId
983
1140
  };
984
1141
  const uiResource = createUIResourceFromDefinition(
985
1142
  definition,
@@ -994,6 +1151,25 @@ var McpServer = class {
994
1151
  }
995
1152
  return uiResource;
996
1153
  }
1154
+ /**
1155
+ * Generate a widget URI with optional build ID for cache busting
1156
+ *
1157
+ * @private
1158
+ * @param widgetName - Widget name/identifier
1159
+ * @param extension - Optional file extension (e.g., '.html')
1160
+ * @param suffix - Optional suffix (e.g., random ID for dynamic URIs)
1161
+ * @returns Widget URI with build ID if available
1162
+ */
1163
+ generateWidgetUri(widgetName, extension = "", suffix = "") {
1164
+ const parts = [widgetName];
1165
+ if (this.buildId) {
1166
+ parts.push(this.buildId);
1167
+ }
1168
+ if (suffix) {
1169
+ parts.push(suffix);
1170
+ }
1171
+ return `ui://widget/${parts.join("-")}${extension}`;
1172
+ }
997
1173
  /**
998
1174
  * Build a complete URL for a widget including query parameters
999
1175
  *
@@ -1070,7 +1246,7 @@ var McpServer = class {
1070
1246
  * @returns true if in production mode, false otherwise
1071
1247
  */
1072
1248
  isProductionMode() {
1073
- return getEnv("NODE_ENV") === "production";
1249
+ return getEnv2("NODE_ENV") === "production";
1074
1250
  }
1075
1251
  /**
1076
1252
  * Read build manifest file
@@ -1081,7 +1257,7 @@ var McpServer = class {
1081
1257
  async readBuildManifest() {
1082
1258
  try {
1083
1259
  const manifestPath = pathHelpers.join(
1084
- isDeno ? "." : getCwd(),
1260
+ isDeno2 ? "." : getCwd(),
1085
1261
  "dist",
1086
1262
  "mcp-use.json"
1087
1263
  );
@@ -1103,7 +1279,7 @@ var McpServer = class {
1103
1279
  * @returns Promise that resolves when all widgets are mounted
1104
1280
  */
1105
1281
  async mountWidgets(options) {
1106
- if (this.isProductionMode() || isDeno) {
1282
+ if (this.isProductionMode() || isDeno2) {
1107
1283
  console.log("[WIDGETS] Mounting widgets in production mode");
1108
1284
  await this.mountWidgetsProduction(options);
1109
1285
  } else {
@@ -1523,7 +1699,7 @@ if (container && Component) {
1523
1699
  async mountWidgetsProduction(options) {
1524
1700
  const baseRoute = options?.baseRoute || "/mcp-use/widgets";
1525
1701
  const widgetsDir = pathHelpers.join(
1526
- isDeno ? "." : getCwd(),
1702
+ isDeno2 ? "." : getCwd(),
1527
1703
  "dist",
1528
1704
  "resources",
1529
1705
  "widgets"
@@ -1539,6 +1715,10 @@ if (container && Component) {
1539
1715
  "utf8"
1540
1716
  );
1541
1717
  const manifest = JSON.parse(manifestContent);
1718
+ if (manifest.buildId && typeof manifest.buildId === "string") {
1719
+ this.buildId = manifest.buildId;
1720
+ console.log(`[WIDGETS] Build ID: ${this.buildId}`);
1721
+ }
1542
1722
  if (manifest.widgets && typeof manifest.widgets === "object" && !Array.isArray(manifest.widgets)) {
1543
1723
  widgets = Object.keys(manifest.widgets);
1544
1724
  widgetsMetadata = manifest.widgets;
@@ -1671,6 +1851,7 @@ if (container && Component) {
1671
1851
  resource_domains: [
1672
1852
  "https://*.oaistatic.com",
1673
1853
  "https://*.oaiusercontent.com",
1854
+ "https://*.openai.com",
1674
1855
  // always also add the base url of the server
1675
1856
  ...this.getServerBaseUrl() ? [this.getServerBaseUrl()] : [],
1676
1857
  ...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.resource_domains || []
@@ -1703,12 +1884,11 @@ if (container && Component) {
1703
1884
  });
1704
1885
  }
1705
1886
  /**
1706
- * Mount MCP server endpoints at /mcp
1887
+ * Mount MCP server endpoints at /mcp and /sse
1707
1888
  *
1708
1889
  * Sets up the HTTP transport layer for the MCP server, creating endpoints for
1709
1890
  * 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.
1891
+ * Transports are reused per session ID to maintain state across requests.
1712
1892
  *
1713
1893
  * This method is called automatically when the server starts listening and ensures
1714
1894
  * that MCP clients can communicate with the server over HTTP.
@@ -1718,14 +1898,91 @@ if (container && Component) {
1718
1898
  *
1719
1899
  * @example
1720
1900
  * 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
1901
+ * - GET /mcp, GET /sse - SSE streaming endpoint for real-time communication
1902
+ * - POST /mcp, POST /sse - Message handling endpoint for MCP protocol messages
1903
+ * - DELETE /mcp, DELETE /sse - Session cleanup endpoint
1724
1904
  */
1725
1905
  async mountMcp() {
1726
1906
  if (this.mcpMounted) return;
1727
1907
  const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
1728
- const endpoint = "/mcp";
1908
+ const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
1909
+ const createNewTransport = /* @__PURE__ */ __name(async (closeOldSessionId) => {
1910
+ if (closeOldSessionId && this.sessions.has(closeOldSessionId)) {
1911
+ try {
1912
+ this.sessions.get(closeOldSessionId).transport.close();
1913
+ } catch (error) {
1914
+ }
1915
+ this.sessions.delete(closeOldSessionId);
1916
+ }
1917
+ const isProduction = this.isProductionMode();
1918
+ let allowedOrigins = this.config.allowedOrigins;
1919
+ let enableDnsRebindingProtection = false;
1920
+ if (isProduction) {
1921
+ if (allowedOrigins !== void 0) {
1922
+ enableDnsRebindingProtection = allowedOrigins.length > 0;
1923
+ }
1924
+ } else {
1925
+ allowedOrigins = void 0;
1926
+ enableDnsRebindingProtection = false;
1927
+ }
1928
+ const transport = new StreamableHTTPServerTransport({
1929
+ sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
1930
+ enableJsonResponse: true,
1931
+ allowedOrigins,
1932
+ enableDnsRebindingProtection,
1933
+ onsessioninitialized: /* @__PURE__ */ __name((id) => {
1934
+ if (id) {
1935
+ this.sessions.set(id, {
1936
+ transport,
1937
+ lastAccessedAt: Date.now()
1938
+ });
1939
+ }
1940
+ }, "onsessioninitialized"),
1941
+ onsessionclosed: /* @__PURE__ */ __name((id) => {
1942
+ if (id) {
1943
+ this.sessions.delete(id);
1944
+ }
1945
+ }, "onsessionclosed")
1946
+ });
1947
+ await this.server.connect(transport);
1948
+ return transport;
1949
+ }, "createNewTransport");
1950
+ const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
1951
+ if (isInit) {
1952
+ return await createNewTransport(sessionId);
1953
+ }
1954
+ if (sessionId && this.sessions.has(sessionId)) {
1955
+ const session = this.sessions.get(sessionId);
1956
+ session.lastAccessedAt = Date.now();
1957
+ return session.transport;
1958
+ }
1959
+ if (sessionId) {
1960
+ const autoCreate = this.config.autoCreateSessionOnInvalidId ?? true;
1961
+ if (autoCreate) {
1962
+ console.warn(
1963
+ `[MCP] Session ${sessionId} not found (expired or invalid), auto-creating new session for seamless reconnection`
1964
+ );
1965
+ return await createNewTransport(sessionId);
1966
+ } else {
1967
+ return null;
1968
+ }
1969
+ }
1970
+ return null;
1971
+ }, "getOrCreateTransport");
1972
+ if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
1973
+ this.idleCleanupInterval = setInterval(() => {
1974
+ const now = Date.now();
1975
+ for (const [sessionId, session] of this.sessions.entries()) {
1976
+ if (now - session.lastAccessedAt > idleTimeoutMs) {
1977
+ try {
1978
+ session.transport.close();
1979
+ } catch (error) {
1980
+ }
1981
+ this.sessions.delete(sessionId);
1982
+ }
1983
+ }
1984
+ }, 6e4);
1985
+ }
1729
1986
  const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
1730
1987
  const req = c.req.raw;
1731
1988
  const responseBody = [];
@@ -1817,7 +2074,7 @@ if (container && Component) {
1817
2074
  getResponse: /* @__PURE__ */ __name(() => {
1818
2075
  if (ended) {
1819
2076
  if (responseBody.length > 0) {
1820
- const body = isDeno ? Buffer.concat(responseBody) : Buffer.concat(responseBody);
2077
+ const body = isDeno2 ? Buffer.concat(responseBody) : Buffer.concat(responseBody);
1821
2078
  return new Response(body, {
1822
2079
  status: statusCode,
1823
2080
  headers
@@ -1833,164 +2090,250 @@ if (container && Component) {
1833
2090
  }, "getResponse")
1834
2091
  };
1835
2092
  }, "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
2093
+ const mountEndpoint = /* @__PURE__ */ __name((endpoint) => {
2094
+ this.app.post(endpoint, async (c) => {
2095
+ const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
2096
+ let body = {};
2097
+ try {
2098
+ body = await c.req.json();
2099
+ expressReq.body = body;
2100
+ } catch {
2101
+ expressReq.body = {};
2102
+ }
2103
+ const isInit = body?.method === "initialize";
2104
+ const sessionId = c.req.header("mcp-session-id");
2105
+ const transport = await getOrCreateTransport(sessionId, isInit);
2106
+ if (!transport) {
2107
+ if (sessionId) {
2108
+ return c.json(
2109
+ {
2110
+ jsonrpc: "2.0",
2111
+ error: {
2112
+ code: -32e3,
2113
+ message: "Session not found or expired"
2114
+ },
2115
+ // Notifications don't have an id, but we include null for consistency
2116
+ id: body?.id ?? null
2117
+ },
2118
+ 404
2119
+ );
2120
+ } else {
2121
+ return c.json(
2122
+ {
2123
+ jsonrpc: "2.0",
2124
+ error: {
2125
+ code: -32e3,
2126
+ message: "Bad Request: Mcp-Session-Id header is required"
2127
+ },
2128
+ id: body?.id ?? null
2129
+ },
2130
+ 400
2131
+ );
2132
+ }
2133
+ }
2134
+ if (sessionId && this.sessions.has(sessionId)) {
2135
+ this.sessions.get(sessionId).lastAccessedAt = Date.now();
2136
+ }
2137
+ if (expressRes._closeHandler) {
2138
+ c.req.raw.signal?.addEventListener("abort", () => {
2139
+ transport.close();
2140
+ });
2141
+ }
2142
+ await this.waitForRequestComplete(
2143
+ transport,
2144
+ expressReq,
2145
+ expressRes,
2146
+ expressReq.body
2147
+ );
2148
+ const response = getResponse();
2149
+ if (response) {
2150
+ return response;
2151
+ }
2152
+ return c.text("", 200);
1846
2153
  });
1847
- if (expressRes._closeHandler) {
2154
+ this.app.get(endpoint, async (c) => {
2155
+ const sessionId = c.req.header("mcp-session-id");
2156
+ const transport = await getOrCreateTransport(sessionId, false);
2157
+ if (!transport) {
2158
+ if (sessionId) {
2159
+ return c.json(
2160
+ {
2161
+ jsonrpc: "2.0",
2162
+ error: {
2163
+ code: -32e3,
2164
+ message: "Session not found or expired"
2165
+ },
2166
+ id: null
2167
+ },
2168
+ 404
2169
+ );
2170
+ } else {
2171
+ return c.json(
2172
+ {
2173
+ jsonrpc: "2.0",
2174
+ error: {
2175
+ code: -32e3,
2176
+ message: "Bad Request: Mcp-Session-Id header is required"
2177
+ },
2178
+ id: null
2179
+ },
2180
+ 400
2181
+ );
2182
+ }
2183
+ }
2184
+ if (sessionId && this.sessions.has(sessionId)) {
2185
+ this.sessions.get(sessionId).lastAccessedAt = Date.now();
2186
+ }
1848
2187
  c.req.raw.signal?.addEventListener("abort", () => {
1849
2188
  transport.close();
1850
2189
  });
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);
2190
+ const { readable, writable } = new globalThis.TransformStream();
2191
+ const writer = writable.getWriter();
2192
+ const encoder = new TextEncoder();
2193
+ let resolveResponse;
2194
+ const responsePromise = new Promise((resolve) => {
2195
+ resolveResponse = resolve;
2196
+ });
2197
+ let headersSent = false;
2198
+ const headers = {};
2199
+ let statusCode = 200;
2200
+ const expressRes = {
2201
+ statusCode: 200,
2202
+ headersSent: false,
2203
+ status: /* @__PURE__ */ __name((code) => {
2204
+ statusCode = code;
2205
+ expressRes.statusCode = code;
2206
+ return expressRes;
2207
+ }, "status"),
2208
+ setHeader: /* @__PURE__ */ __name((name, value) => {
2209
+ if (!headersSent) {
2210
+ headers[name] = Array.isArray(value) ? value.join(", ") : value;
2211
+ }
2212
+ }, "setHeader"),
2213
+ getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
2214
+ write: /* @__PURE__ */ __name((chunk) => {
2215
+ if (!headersSent) {
2216
+ headersSent = true;
2217
+ resolveResponse(
2218
+ new Response(readable, {
2219
+ status: statusCode,
2220
+ headers
2221
+ })
2222
+ );
2223
+ }
2224
+ const data = typeof chunk === "string" ? encoder.encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
2225
+ writer.write(data);
2226
+ return true;
2227
+ }, "write"),
2228
+ end: /* @__PURE__ */ __name((chunk) => {
2229
+ if (chunk) {
2230
+ expressRes.write(chunk);
2231
+ }
2232
+ if (!headersSent) {
2233
+ headersSent = true;
2234
+ resolveResponse(
2235
+ new Response(null, {
2236
+ status: statusCode,
2237
+ headers
2238
+ })
2239
+ );
2240
+ writer.close();
2241
+ } else {
2242
+ writer.close();
2243
+ }
2244
+ }, "end"),
2245
+ on: /* @__PURE__ */ __name((event, handler) => {
2246
+ if (event === "close") {
2247
+ expressRes._closeHandler = handler;
2248
+ }
2249
+ }, "on"),
2250
+ once: /* @__PURE__ */ __name(() => {
2251
+ }, "once"),
2252
+ removeListener: /* @__PURE__ */ __name(() => {
2253
+ }, "removeListener"),
2254
+ writeHead: /* @__PURE__ */ __name((code, _headers) => {
2255
+ statusCode = code;
2256
+ expressRes.statusCode = code;
2257
+ if (_headers) {
2258
+ Object.assign(headers, _headers);
2259
+ }
2260
+ if (!headersSent) {
2261
+ headersSent = true;
2262
+ resolveResponse(
2263
+ new Response(readable, {
2264
+ status: statusCode,
2265
+ headers
2266
+ })
2267
+ );
2268
+ }
2269
+ return expressRes;
2270
+ }, "writeHead"),
2271
+ flushHeaders: /* @__PURE__ */ __name(() => {
2272
+ }, "flushHeaders")
2273
+ };
2274
+ const expressReq = {
2275
+ ...c.req.raw,
2276
+ url: new URL(c.req.url).pathname + new URL(c.req.url).search,
2277
+ path: new URL(c.req.url).pathname,
2278
+ query: Object.fromEntries(new URL(c.req.url).searchParams),
2279
+ headers: c.req.header(),
2280
+ method: c.req.method
2281
+ };
2282
+ transport.handleRequest(expressReq, expressRes).catch((err) => {
2283
+ console.error("MCP Transport error:", err);
2284
+ try {
2285
+ writer.close();
2286
+ } catch {
1914
2287
  }
1915
- if (!headersSent) {
1916
- headersSent = true;
1917
- resolveResponse(
1918
- new Response(null, {
1919
- status: statusCode,
1920
- headers
1921
- })
2288
+ });
2289
+ return responsePromise;
2290
+ });
2291
+ this.app.delete(endpoint, async (c) => {
2292
+ const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
2293
+ const sessionId = c.req.header("mcp-session-id");
2294
+ const transport = await getOrCreateTransport(sessionId, false);
2295
+ if (!transport) {
2296
+ if (sessionId) {
2297
+ return c.json(
2298
+ {
2299
+ jsonrpc: "2.0",
2300
+ error: {
2301
+ code: -32e3,
2302
+ message: "Session not found or expired"
2303
+ },
2304
+ id: null
2305
+ },
2306
+ 404
1922
2307
  );
1923
- writer.close();
1924
2308
  } 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
- })
2309
+ return c.json(
2310
+ {
2311
+ jsonrpc: "2.0",
2312
+ error: {
2313
+ code: -32e3,
2314
+ message: "Bad Request: Mcp-Session-Id header is required"
2315
+ },
2316
+ id: null
2317
+ },
2318
+ 400
1950
2319
  );
1951
2320
  }
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
2321
  }
2322
+ c.req.raw.signal?.addEventListener("abort", () => {
2323
+ transport.close();
2324
+ });
2325
+ await this.waitForRequestComplete(transport, expressReq, expressRes);
2326
+ const response = getResponse();
2327
+ if (response) {
2328
+ return response;
2329
+ }
2330
+ return c.text("", 200);
1972
2331
  });
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
- });
2332
+ }, "mountEndpoint");
2333
+ mountEndpoint("/mcp");
2334
+ mountEndpoint("/sse");
1992
2335
  this.mcpMounted = true;
1993
- console.log(`[MCP] Server mounted at ${endpoint}`);
2336
+ console.log(`[MCP] Server mounted at /mcp and /sse`);
1994
2337
  }
1995
2338
  /**
1996
2339
  * Start the Hono server with MCP endpoints
@@ -1999,7 +2342,7 @@ if (container && Component) {
1999
2342
  * the inspector UI (if available), and starting the server to listen
2000
2343
  * for incoming connections. This is the main entry point for running the server.
2001
2344
  *
2002
- * The server will be accessible at the specified port with MCP endpoints at /mcp
2345
+ * The server will be accessible at the specified port with MCP endpoints at /mcp and /sse
2003
2346
  * and inspector UI at /inspector (if the inspector package is installed).
2004
2347
  *
2005
2348
  * @param port - Port number to listen on (defaults to 3001 if not specified)
@@ -2009,7 +2352,7 @@ if (container && Component) {
2009
2352
  * ```typescript
2010
2353
  * await server.listen(8080)
2011
2354
  * // Server now running at http://localhost:8080 (or configured host)
2012
- * // MCP endpoints: http://localhost:8080/mcp
2355
+ * // MCP endpoints: http://localhost:8080/mcp and http://localhost:8080/sse
2013
2356
  * // Inspector UI: http://localhost:8080/inspector
2014
2357
  * ```
2015
2358
  */
@@ -2039,9 +2382,9 @@ if (container && Component) {
2039
2382
  console.log("");
2040
2383
  }
2041
2384
  async listen(port) {
2042
- const portEnv = getEnv("PORT");
2385
+ const portEnv = getEnv2("PORT");
2043
2386
  this.serverPort = port || (portEnv ? parseInt(portEnv, 10) : 3001);
2044
- const hostEnv = getEnv("HOST");
2387
+ const hostEnv = getEnv2("HOST");
2045
2388
  if (hostEnv) {
2046
2389
  this.serverHost = hostEnv;
2047
2390
  }
@@ -2052,7 +2395,7 @@ if (container && Component) {
2052
2395
  await this.mountMcp();
2053
2396
  await this.mountInspector();
2054
2397
  this.logRegisteredItems();
2055
- if (isDeno) {
2398
+ if (isDeno2) {
2056
2399
  const corsHeaders = {
2057
2400
  "Access-Control-Allow-Origin": "*",
2058
2401
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type"
@@ -2113,7 +2456,7 @@ if (container && Component) {
2113
2456
  `[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
2114
2457
  );
2115
2458
  console.log(
2116
- `[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
2459
+ `[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp and http://${this.serverHost}:${this.serverPort}/sse`
2117
2460
  );
2118
2461
  }
2119
2462
  );
@@ -2197,6 +2540,192 @@ if (container && Component) {
2197
2540
  return result;
2198
2541
  };
2199
2542
  }
2543
+ /**
2544
+ * Get array of active session IDs
2545
+ *
2546
+ * Returns an array of all currently active session IDs. This is useful for
2547
+ * sending targeted notifications to specific clients or iterating over
2548
+ * connected clients.
2549
+ *
2550
+ * Note: This only works in stateful mode. In stateless mode (edge environments),
2551
+ * this will return an empty array.
2552
+ *
2553
+ * @returns Array of active session ID strings
2554
+ *
2555
+ * @example
2556
+ * ```typescript
2557
+ * const sessions = server.getActiveSessions();
2558
+ * console.log(`${sessions.length} clients connected`);
2559
+ *
2560
+ * // Send notification to first connected client
2561
+ * if (sessions.length > 0) {
2562
+ * server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
2563
+ * }
2564
+ * ```
2565
+ */
2566
+ getActiveSessions() {
2567
+ return Array.from(this.sessions.keys());
2568
+ }
2569
+ /**
2570
+ * Send a notification to all connected clients
2571
+ *
2572
+ * Broadcasts a JSON-RPC notification to all active sessions. Notifications are
2573
+ * one-way messages that do not expect a response from the client.
2574
+ *
2575
+ * Note: This only works in stateful mode with active sessions. If no sessions
2576
+ * are connected, the notification is silently discarded (per MCP spec: "server MAY send").
2577
+ *
2578
+ * @param method - The notification method name (e.g., "custom/my-notification")
2579
+ * @param params - Optional parameters to include in the notification
2580
+ *
2581
+ * @example
2582
+ * ```typescript
2583
+ * // Send a simple notification to all clients
2584
+ * server.sendNotification("custom/server-status", {
2585
+ * status: "ready",
2586
+ * timestamp: new Date().toISOString()
2587
+ * });
2588
+ *
2589
+ * // Notify all clients that resources have changed
2590
+ * server.sendNotification("notifications/resources/list_changed");
2591
+ * ```
2592
+ */
2593
+ async sendNotification(method, params) {
2594
+ const notification = {
2595
+ jsonrpc: "2.0",
2596
+ method,
2597
+ ...params && { params }
2598
+ };
2599
+ for (const [sessionId, session] of this.sessions.entries()) {
2600
+ try {
2601
+ await session.transport.send(notification);
2602
+ } catch (error) {
2603
+ console.warn(
2604
+ `[MCP] Failed to send notification to session ${sessionId}:`,
2605
+ error
2606
+ );
2607
+ }
2608
+ }
2609
+ }
2610
+ /**
2611
+ * Send a notification to a specific client session
2612
+ *
2613
+ * Sends a JSON-RPC notification to a single client identified by their session ID.
2614
+ * This allows sending customized notifications to individual clients.
2615
+ *
2616
+ * Note: This only works in stateful mode. If the session ID doesn't exist,
2617
+ * the notification is silently discarded.
2618
+ *
2619
+ * @param sessionId - The target session ID (from getActiveSessions())
2620
+ * @param method - The notification method name (e.g., "custom/my-notification")
2621
+ * @param params - Optional parameters to include in the notification
2622
+ * @returns true if the notification was sent, false if session not found
2623
+ *
2624
+ * @example
2625
+ * ```typescript
2626
+ * const sessions = server.getActiveSessions();
2627
+ *
2628
+ * // Send different messages to different clients
2629
+ * sessions.forEach((sessionId, index) => {
2630
+ * server.sendNotificationToSession(sessionId, "custom/welcome", {
2631
+ * message: `Hello client #${index + 1}!`,
2632
+ * clientNumber: index + 1
2633
+ * });
2634
+ * });
2635
+ * ```
2636
+ */
2637
+ async sendNotificationToSession(sessionId, method, params) {
2638
+ const session = this.sessions.get(sessionId);
2639
+ if (!session) {
2640
+ return false;
2641
+ }
2642
+ const notification = {
2643
+ jsonrpc: "2.0",
2644
+ method,
2645
+ ...params && { params }
2646
+ };
2647
+ try {
2648
+ await session.transport.send(notification);
2649
+ return true;
2650
+ } catch (error) {
2651
+ console.warn(
2652
+ `[MCP] Failed to send notification to session ${sessionId}:`,
2653
+ error
2654
+ );
2655
+ return false;
2656
+ }
2657
+ }
2658
+ // Store the roots changed callback
2659
+ onRootsChangedCallback;
2660
+ /**
2661
+ * Register a callback for when a client's roots change
2662
+ *
2663
+ * When a client sends a `notifications/roots/list_changed` notification,
2664
+ * the server will automatically request the updated roots list and call
2665
+ * this callback with the new roots.
2666
+ *
2667
+ * @param callback - Function called with the updated roots array
2668
+ *
2669
+ * @example
2670
+ * ```typescript
2671
+ * server.onRootsChanged(async (roots) => {
2672
+ * console.log("Client roots updated:", roots);
2673
+ * roots.forEach(root => {
2674
+ * console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
2675
+ * });
2676
+ * });
2677
+ * ```
2678
+ */
2679
+ onRootsChanged(callback) {
2680
+ this.onRootsChangedCallback = callback;
2681
+ return this;
2682
+ }
2683
+ /**
2684
+ * Request the current roots list from a specific client session
2685
+ *
2686
+ * This sends a `roots/list` request to the client and returns
2687
+ * the list of roots the client has configured.
2688
+ *
2689
+ * @param sessionId - The session ID of the client to query
2690
+ * @returns Array of roots, or null if the session doesn't exist or request fails
2691
+ *
2692
+ * @example
2693
+ * ```typescript
2694
+ * const sessions = server.getActiveSessions();
2695
+ * if (sessions.length > 0) {
2696
+ * const roots = await server.listRoots(sessions[0]);
2697
+ * if (roots) {
2698
+ * console.log(`Client has ${roots.length} roots:`);
2699
+ * roots.forEach(r => console.log(` - ${r.uri}`));
2700
+ * }
2701
+ * }
2702
+ * ```
2703
+ */
2704
+ async listRoots(sessionId) {
2705
+ const session = this.sessions.get(sessionId);
2706
+ if (!session) {
2707
+ return null;
2708
+ }
2709
+ try {
2710
+ const request = {
2711
+ jsonrpc: "2.0",
2712
+ id: generateUUID(),
2713
+ method: "roots/list",
2714
+ params: {}
2715
+ };
2716
+ const response = await session.transport.send(request);
2717
+ if (response && typeof response === "object" && "roots" in response) {
2718
+ return response.roots;
2719
+ }
2720
+ return [];
2721
+ } catch (error) {
2722
+ console.warn(
2723
+ `[MCP] Failed to list roots from session ${sessionId}:`,
2724
+ error
2725
+ );
2726
+ return null;
2727
+ }
2728
+ }
2200
2729
  /**
2201
2730
  * Mount MCP Inspector UI at /inspector
2202
2731
  *
@@ -2213,7 +2742,7 @@ if (container && Component) {
2213
2742
  * @example
2214
2743
  * If @mcp-use/inspector is installed:
2215
2744
  * - Inspector UI available at http://localhost:PORT/inspector
2216
- * - Automatically connects to http://localhost:PORT/mcp
2745
+ * - Automatically connects to http://localhost:PORT/mcp (or /sse)
2217
2746
  *
2218
2747
  * If not installed:
2219
2748
  * - Server continues to function normally
@@ -2232,7 +2761,14 @@ if (container && Component) {
2232
2761
  }
2233
2762
  try {
2234
2763
  const { mountInspector } = await import("@mcp-use/inspector");
2235
- mountInspector(this.app);
2764
+ const mcpUrl = `http://${this.serverHost}:${this.serverPort}/mcp`;
2765
+ const autoConnectConfig = JSON.stringify({
2766
+ url: mcpUrl,
2767
+ name: "Local MCP Server",
2768
+ transportType: "sse",
2769
+ connectionType: "Direct"
2770
+ });
2771
+ mountInspector(this.app, { autoConnectUrl: autoConnectConfig });
2236
2772
  this.inspectorMounted = true;
2237
2773
  console.log(
2238
2774
  `[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`
@@ -2556,7 +3092,9 @@ function createMCPServer(name, config = {}) {
2556
3092
  version: config.version || "1.0.0",
2557
3093
  description: config.description,
2558
3094
  host: config.host,
2559
- baseUrl: config.baseUrl
3095
+ baseUrl: config.baseUrl,
3096
+ allowedOrigins: config.allowedOrigins,
3097
+ sessionIdleTimeoutMs: config.sessionIdleTimeoutMs
2560
3098
  });
2561
3099
  return instance;
2562
3100
  }