@modelcontextprotocol/inspector 2.13.0 → 2.14.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 (32) hide show
  1. package/README.md +1 -1
  2. package/clients/cli/build/index.js +1013 -80
  3. package/clients/mcpdo/build/mcp-bin.js +50 -22
  4. package/clients/mcpdo/build/mcpdod.js +1 -1
  5. package/clients/mcpdo/build/{server-BHuKGkMj.js → server-Dv339Ejx.js} +886 -48
  6. package/clients/tui/build/index.js +781 -46
  7. package/clients/web/build/index.js +223 -124
  8. package/clients/web/dist/assets/fredoka-normal-hebrew-CjvktpUN.woff2 +0 -0
  9. package/clients/web/dist/assets/fredoka-normal-latin-DOQG_Jwn.woff2 +0 -0
  10. package/clients/web/dist/assets/fredoka-normal-latin-ext-tYNUQHO6.woff2 +0 -0
  11. package/clients/web/dist/assets/index-BCud2uMC.js +1594 -0
  12. package/clients/web/dist/assets/{index-dqp8Sd8W.css → index-Dval2a7I.css} +1 -1
  13. package/clients/web/dist/assets/{prism-light-BIhJtJkv.js → prism-light-CoVRM9R2.js} +1 -1
  14. package/clients/web/dist/assets/roboto-mono-italic-cyrillic-C1E63de8.woff2 +0 -0
  15. package/clients/web/dist/assets/roboto-mono-italic-cyrillic-ext-DR9ovEqT.woff2 +0 -0
  16. package/clients/web/dist/assets/roboto-mono-italic-greek-Cs2u2nVh.woff2 +0 -0
  17. package/clients/web/dist/assets/roboto-mono-italic-latin-D_P-kX1t.woff2 +0 -0
  18. package/clients/web/dist/assets/roboto-mono-italic-latin-ext-CErTcBzw.woff2 +0 -0
  19. package/clients/web/dist/assets/roboto-mono-italic-vietnamese-D4UvzJMM.woff2 +0 -0
  20. package/clients/web/dist/assets/roboto-mono-normal-cyrillic-DfzWIUPd.woff2 +0 -0
  21. package/clients/web/dist/assets/roboto-mono-normal-cyrillic-ext-D8QBEkUe.woff2 +0 -0
  22. package/clients/web/dist/assets/roboto-mono-normal-greek-DlC4X1lQ.woff2 +0 -0
  23. package/clients/web/dist/assets/roboto-mono-normal-latin-BAKpfWyj.woff2 +0 -0
  24. package/clients/web/dist/assets/roboto-mono-normal-latin-ext-CSSVNFqV.woff2 +0 -0
  25. package/clients/web/dist/assets/roboto-mono-normal-vietnamese-DFkHeOHB.woff2 +0 -0
  26. package/clients/web/dist/fonts/Fredoka-OFL.txt +93 -0
  27. package/clients/web/dist/fonts/RobotoMono-OFL.txt +93 -0
  28. package/clients/web/dist/index.html +2 -5
  29. package/clients/web/static/sandbox_proxy.html +177 -6
  30. package/package.json +5 -4
  31. package/skills/mcpdo/SKILL.md +14 -0
  32. package/clients/web/dist/assets/index-BqEbhCDX.js +0 -1587
@@ -22,7 +22,7 @@ import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypt
22
22
  import { createSecureContext } from "node:tls";
23
23
  import { createServer as createServer$1 } from "vite";
24
24
  import react from "@vitejs/plugin-react";
25
- import { homedir } from "node:os";
25
+ import { homedir, tmpdir } from "node:os";
26
26
  import { Hono } from "hono";
27
27
  import { streamSSE } from "hono/streaming";
28
28
  import { bodyLimit } from "hono/body-limit";
@@ -556,26 +556,37 @@ function expectedSecretFields(stored) {
556
556
  if (isStdioStored(stored) && stored.env) for (const k of Object.keys(stored.env)) fields.push(envSecretField(k));
557
557
  return fields;
558
558
  }
559
- const DEFAULT_SEED_CONFIG = { mcpServers: {
560
- "filesystem-server-default": {
561
- type: "stdio",
562
- command: "npx",
563
- args: [
564
- "-y",
565
- "@modelcontextprotocol/server-filesystem",
566
- "/tmp"
567
- ]
568
- },
569
- "everything-server-default": {
570
- type: "stdio",
571
- command: "npx",
572
- args: ["-y", "@modelcontextprotocol/server-everything"]
573
- },
574
- "example-server-default": {
575
- type: "streamable-http",
576
- url: "https://example-server.modelcontextprotocol.io/mcp"
559
+ const EXAMPLE_SERVER_URL = "https://example-server.modelcontextprotocol.io/mcp";
560
+ function adHocServerName(config) {
561
+ if (config.type === "sse" || config.type === "streamable-http") try {
562
+ return new URL(config.url).host || "server";
563
+ } catch {
564
+ return "server";
577
565
  }
578
- } };
566
+ return config.command?.split(/[\\/]/).pop() || "server";
567
+ }
568
+ function defaultSeedConfig(tempDir) {
569
+ return { mcpServers: {
570
+ "filesystem-server-default": {
571
+ type: "stdio",
572
+ command: "npx",
573
+ args: [
574
+ "-y",
575
+ "@modelcontextprotocol/server-filesystem",
576
+ tempDir
577
+ ]
578
+ },
579
+ "everything-server-default": {
580
+ type: "stdio",
581
+ command: "npx",
582
+ args: ["-y", "@modelcontextprotocol/server-everything"]
583
+ },
584
+ "example-server-default": {
585
+ type: "streamable-http",
586
+ url: EXAMPLE_SERVER_URL
587
+ }
588
+ } };
589
+ }
579
590
  //#endregion
580
591
  //#region ../../core/storage/own-entry.ts
581
592
  function setOwnEntry(map, key, value) {
@@ -913,8 +924,103 @@ function resolveBindHostname(env = process.env) {
913
924
  return stripBrackets(host);
914
925
  }
915
926
  //#endregion
927
+ //#region server/host-allow-list.ts
928
+ const ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
929
+ const HOSTNAME_RE = /^[a-z0-9._-]+$/;
930
+ function normalizeHostname(hostname) {
931
+ return hostname.trim().toLowerCase().replace(/\.$/, "");
932
+ }
933
+ function parseHostHeader(value) {
934
+ const raw = value.trim().toLowerCase();
935
+ let hostname;
936
+ let port;
937
+ if (raw.startsWith("[")) {
938
+ const end = raw.indexOf("]");
939
+ if (end < 0) return null;
940
+ hostname = raw.slice(0, end + 1);
941
+ if (!isIPv6(hostname.slice(1, -1))) return null;
942
+ const rest = raw.slice(end + 1);
943
+ if (rest !== "") {
944
+ if (!rest.startsWith(":")) return null;
945
+ port = rest.slice(1);
946
+ }
947
+ } else {
948
+ const colon = raw.indexOf(":");
949
+ hostname = colon < 0 ? raw : raw.slice(0, colon);
950
+ port = colon < 0 ? void 0 : raw.slice(colon + 1);
951
+ }
952
+ if (port !== void 0) {
953
+ if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
954
+ }
955
+ if (hostname.startsWith("[")) return hostname;
956
+ const name = normalizeHostname(hostname);
957
+ return HOSTNAME_RE.test(name) ? name : null;
958
+ }
959
+ function parseAllowedHostsEnv(value) {
960
+ if (!value) return [];
961
+ const out = [];
962
+ for (const entry of value.split(",")) {
963
+ const trimmed = entry.trim();
964
+ if (trimmed === "") continue;
965
+ const domain = trimmed.startsWith(".") ? "." : "";
966
+ const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
967
+ if (!HOSTNAME_RE.test(ascii)) {
968
+ console.warn(`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`);
969
+ continue;
970
+ }
971
+ out.push(domain + ascii);
972
+ }
973
+ return out;
974
+ }
975
+ function configuredAllowedHosts(options) {
976
+ const hosts = /* @__PURE__ */ new Set();
977
+ if (!isAllInterfacesHost(options.bindHost)) hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
978
+ for (const origin of options.allowedOrigins) try {
979
+ const { hostname } = new URL(origin);
980
+ if (hostname !== "") hosts.add(normalizeHostname(hostname));
981
+ } catch {}
982
+ for (const extra of options.extraHosts ?? []) hosts.add(normalizeHostname(extra));
983
+ return [...hosts];
984
+ }
985
+ function createHostAllowList(options) {
986
+ const configured = configuredAllowedHosts(options);
987
+ return (hostHeader) => {
988
+ if (hostHeader === void 0) return false;
989
+ const hostname = parseHostHeader(hostHeader);
990
+ if (hostname === null) return false;
991
+ if (hostname.startsWith("[")) return true;
992
+ if (isIPv4(hostname)) return true;
993
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
994
+ for (const allowed of configured) {
995
+ if (allowed === hostname) return true;
996
+ if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
997
+ }
998
+ return false;
999
+ };
1000
+ }
1001
+ function hostAllowListMiddleware(options) {
1002
+ const isAllowed = createHostAllowList(options);
1003
+ return async (c, next) => {
1004
+ const host = c.req.header("host");
1005
+ if (!isAllowed(host)) return c.text(`Forbidden: Host "${host ?? ""}" is not allowed. To serve the Inspector under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`, 403);
1006
+ await next();
1007
+ };
1008
+ }
1009
+ //#endregion
916
1010
  //#region server/sandbox-controller.ts
917
1011
  const __dirname$3 = dirname(fileURLToPath(import.meta.url));
1012
+ function isSandboxPath(url) {
1013
+ const path = (url ?? "").split("?", 1)[0];
1014
+ return path === "/sandbox" || path === "/sandbox/";
1015
+ }
1016
+ function publicUrlHosts(publicUrl) {
1017
+ if (!publicUrl) return [];
1018
+ try {
1019
+ return [new URL(publicUrl).hostname];
1020
+ } catch {
1021
+ return [];
1022
+ }
1023
+ }
918
1024
  const LOOPBACK_FRAME_ANCESTORS = ["http://127.0.0.1:*", "http://localhost:*"];
919
1025
  const CSP_HOST_SOURCE = /^[a-z][a-z0-9+.-]*:\/\/[^\s;,'"[\]*]+$/i;
920
1026
  function sandboxFrameAncestors(allowedOrigins) {
@@ -948,7 +1054,12 @@ function resolveSandboxPort() {
948
1054
  return fromSandbox ?? parseListenPort$1(process.env.SERVER_PORT) ?? 6275;
949
1055
  }
950
1056
  function createSandboxController(options) {
951
- const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
1057
+ const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl, allowedHosts } = options;
1058
+ const isAllowedHost = createHostAllowList({
1059
+ bindHost: host,
1060
+ allowedOrigins: allowedOrigins ?? [],
1061
+ extraHosts: [...allowedHosts ?? [], ...publicUrlHosts(publicUrl)]
1062
+ });
952
1063
  let server = null;
953
1064
  let sandboxUrl = null;
954
1065
  let boundPort = 0;
@@ -977,18 +1088,25 @@ function createSandboxController(options) {
977
1088
  resolve(value);
978
1089
  };
979
1090
  server = createServer((req, res) => {
980
- if (req.method !== "GET" || req.url !== "/sandbox" && req.url !== "/sandbox/") {
1091
+ if (!isAllowedHost(req.headers.host)) {
1092
+ res.writeHead(403, { "Content-Type": "text/plain" });
1093
+ res.end(`Forbidden: Host "${req.headers.host ?? ""}" is not allowed. To serve the sandbox under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`);
1094
+ return;
1095
+ }
1096
+ const isHead = req.method === "HEAD";
1097
+ if (req.method !== "GET" && !isHead || !isSandboxPath(req.url)) {
981
1098
  res.writeHead(404, { "Content-Type": "text/plain" });
982
1099
  res.end("Not Found");
983
1100
  return;
984
1101
  }
985
1102
  res.writeHead(200, {
986
1103
  "Content-Type": "text/html; charset=utf-8",
1104
+ "Content-Length": Buffer.byteLength(sandboxHtml),
987
1105
  "Cache-Control": "no-store, no-cache, must-revalidate",
988
1106
  Pragma: "no-cache",
989
1107
  "Content-Security-Policy": SANDBOX_PROXY_CSP
990
1108
  });
991
- res.end(sandboxHtml);
1109
+ res.end(isHead ? void 0 : sandboxHtml);
992
1110
  });
993
1111
  let retriedDynamic = false;
994
1112
  server.on("error", (err) => {
@@ -1318,89 +1436,6 @@ function resolveTlsConfig(env = process.env) {
1318
1436
  };
1319
1437
  }
1320
1438
  //#endregion
1321
- //#region server/host-allow-list.ts
1322
- const ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
1323
- const HOSTNAME_RE = /^[a-z0-9._-]+$/;
1324
- function normalizeHostname(hostname) {
1325
- return hostname.trim().toLowerCase().replace(/\.$/, "");
1326
- }
1327
- function parseHostHeader(value) {
1328
- const raw = value.trim().toLowerCase();
1329
- let hostname;
1330
- let port;
1331
- if (raw.startsWith("[")) {
1332
- const end = raw.indexOf("]");
1333
- if (end < 0) return null;
1334
- hostname = raw.slice(0, end + 1);
1335
- if (!isIPv6(hostname.slice(1, -1))) return null;
1336
- const rest = raw.slice(end + 1);
1337
- if (rest !== "") {
1338
- if (!rest.startsWith(":")) return null;
1339
- port = rest.slice(1);
1340
- }
1341
- } else {
1342
- const colon = raw.indexOf(":");
1343
- hostname = colon < 0 ? raw : raw.slice(0, colon);
1344
- port = colon < 0 ? void 0 : raw.slice(colon + 1);
1345
- }
1346
- if (port !== void 0) {
1347
- if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
1348
- }
1349
- if (hostname.startsWith("[")) return hostname;
1350
- const name = normalizeHostname(hostname);
1351
- return HOSTNAME_RE.test(name) ? name : null;
1352
- }
1353
- function parseAllowedHostsEnv(value) {
1354
- if (!value) return [];
1355
- const out = [];
1356
- for (const entry of value.split(",")) {
1357
- const trimmed = entry.trim();
1358
- if (trimmed === "") continue;
1359
- const domain = trimmed.startsWith(".") ? "." : "";
1360
- const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
1361
- if (!HOSTNAME_RE.test(ascii)) {
1362
- console.warn(`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`);
1363
- continue;
1364
- }
1365
- out.push(domain + ascii);
1366
- }
1367
- return out;
1368
- }
1369
- function configuredAllowedHosts(options) {
1370
- const hosts = /* @__PURE__ */ new Set();
1371
- if (!isAllInterfacesHost(options.bindHost)) hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
1372
- for (const origin of options.allowedOrigins) try {
1373
- const { hostname } = new URL(origin);
1374
- if (hostname !== "") hosts.add(normalizeHostname(hostname));
1375
- } catch {}
1376
- for (const extra of options.extraHosts ?? []) hosts.add(normalizeHostname(extra));
1377
- return [...hosts];
1378
- }
1379
- function createHostAllowList(options) {
1380
- const configured = configuredAllowedHosts(options);
1381
- return (hostHeader) => {
1382
- if (hostHeader === void 0) return false;
1383
- const hostname = parseHostHeader(hostHeader);
1384
- if (hostname === null) return false;
1385
- if (hostname.startsWith("[")) return true;
1386
- if (isIPv4(hostname)) return true;
1387
- if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
1388
- for (const allowed of configured) {
1389
- if (allowed === hostname) return true;
1390
- if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
1391
- }
1392
- return false;
1393
- };
1394
- }
1395
- function hostAllowListMiddleware(options) {
1396
- const isAllowed = createHostAllowList(options);
1397
- return async (c, next) => {
1398
- const host = c.req.header("host");
1399
- if (!isAllowed(host)) return c.text(`Forbidden: Host "${host ?? ""}" is not allowed. To serve the Inspector under this name, add it to ${ALLOWED_HOSTS_ENV} (or to ALLOWED_ORIGINS).`, 403);
1400
- await next();
1401
- };
1402
- }
1403
- //#endregion
1404
1439
  //#region server/web-server-config.ts
1405
1440
  const inspectorVersion = readInspectorVersionSafe(import.meta.url);
1406
1441
  function defaultEnvironmentFromProcess(extra) {
@@ -1522,6 +1557,10 @@ function defaultAllowedOrigins(hostname, port, scheme = "http") {
1522
1557
  ];
1523
1558
  return [webOrigin(scheme, h, port)];
1524
1559
  }
1560
+ function weakAuthTokenWarning(token) {
1561
+ if (token === "" || token.length >= 32) return null;
1562
+ return `Warning: the custom API token (${API_SERVER_ENV_VARS.AUTH_TOKEN}) is ${token.length} character${token.length === 1 ? "" : "s"} long; use at least 32 random characters (e.g. \`openssl rand -hex 32\`), or unset it to have one generated.`;
1563
+ }
1525
1564
  function buildWebServerConfig(options = {}) {
1526
1565
  const { initialMcpConfig = null, mcpConfigPath, writable = true, initialServers = null } = options;
1527
1566
  const rawPort = process.env.CLIENT_PORT?.trim() || "6274";
@@ -1531,6 +1570,8 @@ function buildWebServerConfig(options = {}) {
1531
1570
  const tls = resolveTlsConfig();
1532
1571
  const dangerouslyOmitAuth = isEnvFlagEnabled(process.env.DANGEROUSLY_OMIT_AUTH);
1533
1572
  const authToken = dangerouslyOmitAuth ? "" : process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] ?? process.env["MCP_PROXY_AUTH_TOKEN"] ?? "";
1573
+ const weakToken = weakAuthTokenWarning(authToken);
1574
+ if (weakToken) console.warn(weakToken);
1534
1575
  const sandboxPort = resolveSandboxPort();
1535
1576
  const requestedAppOriginPort = resolveAppOriginPort();
1536
1577
  const appOriginCollidesWith = requestedAppOriginPort !== 0 && (requestedAppOriginPort === port ? "the web server (CLIENT_PORT)" : requestedAppOriginPort === sandboxPort ? "the MCP Apps sandbox (MCP_SANDBOX_PORT)" : null);
@@ -5013,9 +5054,13 @@ function waitForProgressToken(waits, token) {
5013
5054
  //#endregion
5014
5055
  //#region ../../core/mcp/remote/node/remote-session.ts
5015
5056
  var RemoteSession = class RemoteSession {
5057
+ static DEFAULT_DETACH_GRACE_MS = 6e4;
5058
+ static MAX_QUEUED_EVENTS = 1e3;
5016
5059
  sessionId;
5017
5060
  transport;
5018
5061
  eventQueue = [];
5062
+ droppedEventCount = 0;
5063
+ detachTimer;
5019
5064
  eventConsumer = null;
5020
5065
  transportDead = false;
5021
5066
  transportError = null;
@@ -5046,13 +5091,47 @@ var RemoteSession = class RemoteSession {
5046
5091
  this.transport.setProtocolVersion?.(version);
5047
5092
  }
5048
5093
  setEventConsumer(consumer) {
5094
+ this.cancelDetachGrace();
5049
5095
  this.eventConsumer = consumer;
5096
+ if (this.droppedEventCount > 0) {
5097
+ const dropped = this.droppedEventCount;
5098
+ this.droppedEventCount = 0;
5099
+ consumer({
5100
+ type: "stdio_log",
5101
+ data: {
5102
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5103
+ message: `[inspector] ${dropped} event(s) were dropped while no client was attached (the queue holds at most ${RemoteSession.MAX_QUEUED_EVENTS})`
5104
+ }
5105
+ });
5106
+ }
5050
5107
  while (this.eventQueue.length > 0) consumer(this.eventQueue.shift());
5051
5108
  }
5052
5109
  clearEventConsumer() {
5053
5110
  this.eventConsumer = null;
5054
5111
  return this.transportDead;
5055
5112
  }
5113
+ detachEventConsumer(consumer) {
5114
+ if (this.eventConsumer !== consumer) return false;
5115
+ this.eventConsumer = null;
5116
+ return true;
5117
+ }
5118
+ armDetachGrace(graceMs, onExpire) {
5119
+ this.cancelDetachGrace();
5120
+ this.detachTimer = setTimeout(() => {
5121
+ this.detachTimer = void 0;
5122
+ onExpire();
5123
+ }, graceMs);
5124
+ this.detachTimer.unref();
5125
+ }
5126
+ cancelDetachGrace() {
5127
+ if (this.detachTimer !== void 0) {
5128
+ clearTimeout(this.detachTimer);
5129
+ this.detachTimer = void 0;
5130
+ }
5131
+ }
5132
+ hasPendingDetachGrace() {
5133
+ return this.detachTimer !== void 0;
5134
+ }
5056
5135
  markTransportDead(error) {
5057
5136
  this.transportDead = true;
5058
5137
  this.transportError = error;
@@ -5163,7 +5242,13 @@ var RemoteSession = class RemoteSession {
5163
5242
  }
5164
5243
  pushEvent(event) {
5165
5244
  if (this.eventConsumer) this.eventConsumer(event);
5166
- else this.eventQueue.push(event);
5245
+ else {
5246
+ if (this.eventQueue.length >= RemoteSession.MAX_QUEUED_EVENTS) {
5247
+ this.eventQueue.shift();
5248
+ this.droppedEventCount++;
5249
+ }
5250
+ this.eventQueue.push(event);
5251
+ }
5167
5252
  }
5168
5253
  onMessage(message) {
5169
5254
  if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) this.settleRequestWait(message.id);
@@ -5640,6 +5725,14 @@ function createRemoteApp(options) {
5640
5725
  const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
5641
5726
  const app = new Hono();
5642
5727
  const sessions = /* @__PURE__ */ new Map();
5728
+ const sessionDetachGraceMs = options.sessionDetachGraceMs ?? RemoteSession.DEFAULT_DETACH_GRACE_MS;
5729
+ const armSessionReap = (session) => {
5730
+ session.armDetachGrace(sessionDetachGraceMs, () => {
5731
+ sessions.delete(session.sessionId);
5732
+ session.rejectActiveRequestWaits(/* @__PURE__ */ new Error("Session closed: no client attached"));
5733
+ session.transport.close().catch(() => {});
5734
+ });
5735
+ };
5643
5736
  const { logger: fileLogger, allowedOrigins } = options;
5644
5737
  const storageDir = options.storageDir ?? getDefaultStorageDir();
5645
5738
  const mcpConfigPath = options.mcpConfigPath ?? getDefaultMcpConfigPath();
@@ -5826,6 +5919,7 @@ function createRemoteApp(options) {
5826
5919
  return c.json({ error: `Failed to start transport: ${msg}` }, is401 ? 401 : 500);
5827
5920
  }
5828
5921
  sessions.set(sessionId, session);
5922
+ armSessionReap(session);
5829
5923
  return c.json({ sessionId });
5830
5924
  });
5831
5925
  app.post("/api/mcp/send", async (c) => {
@@ -5916,13 +6010,14 @@ function createRemoteApp(options) {
5916
6010
  const session = sessions.get(sessionId);
5917
6011
  if (!session) return c.json({ error: "Session not found" }, 404);
5918
6012
  return streamSSE(c, async (stream) => {
5919
- session.setEventConsumer((event) => {
6013
+ const consumer = (event) => {
5920
6014
  const data = JSON.stringify(event);
5921
6015
  stream.writeSSE({
5922
6016
  event: event.type,
5923
6017
  data
5924
6018
  });
5925
- });
6019
+ };
6020
+ session.setEventConsumer(consumer);
5926
6021
  if (session.isTransportDead()) {
5927
6022
  await new Promise((resolve) => setTimeout(resolve, 0));
5928
6023
  session.clearEventConsumer();
@@ -5930,7 +6025,12 @@ function createRemoteApp(options) {
5930
6025
  return;
5931
6026
  }
5932
6027
  await primeAndHoldSseStream(stream, () => {
5933
- if (session.clearEventConsumer() || session.isTransportDead()) sessions.delete(sessionId);
6028
+ if (!session.detachEventConsumer(consumer)) return;
6029
+ if (session.isTransportDead()) {
6030
+ sessions.delete(sessionId);
6031
+ return;
6032
+ }
6033
+ armSessionReap(session);
5934
6034
  });
5935
6035
  });
5936
6036
  });
@@ -5945,6 +6045,7 @@ function createRemoteApp(options) {
5945
6045
  if (!sessionId) return c.json({ error: "Missing sessionId" }, 400);
5946
6046
  const session = sessions.get(sessionId);
5947
6047
  if (session) {
6048
+ session.cancelDetachGrace();
5948
6049
  session.clearEventConsumer();
5949
6050
  await session.transport.close();
5950
6051
  sessions.delete(sessionId);
@@ -6608,8 +6709,9 @@ function createRemoteApp(options) {
6608
6709
  const settled = await withWriteLock(async () => {
6609
6710
  const rawInside = await readStoreFile(mcpConfigPath);
6610
6711
  if (rawInside === null) {
6611
- await writeMcpAndTrackMtime(serializeStore(DEFAULT_SEED_CONFIG));
6612
- return DEFAULT_SEED_CONFIG;
6712
+ const seed = defaultSeedConfig(tmpdir());
6713
+ await writeMcpAndTrackMtime(serializeStore(seed));
6714
+ return seed;
6613
6715
  }
6614
6716
  const parsedInside = parseStore(rawInside);
6615
6717
  const inside = { mcpServers: normalizeMcpServers(parsedInside?.mcpServers) };
@@ -6972,7 +7074,8 @@ function honoMiddlewarePlugin(config) {
6972
7074
  port: config.sandboxPort,
6973
7075
  host: config.sandboxHost,
6974
7076
  allowedOrigins: config.allowedOrigins,
6975
- publicUrl: config.sandboxPublicUrl
7077
+ publicUrl: config.sandboxPublicUrl,
7078
+ allowedHosts: config.allowedHosts
6976
7079
  });
6977
7080
  await sandboxController.start();
6978
7081
  const appOriginController = createAppOriginController({
@@ -7277,6 +7380,9 @@ async function startViteDevServer(config) {
7277
7380
  }
7278
7381
  //#endregion
7279
7382
  //#region server/server.ts
7383
+ function isApiPath(path) {
7384
+ return path === "/api" || path.startsWith("/api/");
7385
+ }
7280
7386
  const __filename$1 = fileURLToPath(import.meta.url);
7281
7387
  const __dirname$1 = dirname(__filename$1);
7282
7388
  async function startHonoServer(config) {
@@ -7284,7 +7390,8 @@ async function startHonoServer(config) {
7284
7390
  port: config.sandboxPort,
7285
7391
  host: config.sandboxHost,
7286
7392
  allowedOrigins: config.allowedOrigins,
7287
- publicUrl: config.sandboxPublicUrl
7393
+ publicUrl: config.sandboxPublicUrl,
7394
+ allowedHosts: config.allowedHosts
7288
7395
  });
7289
7396
  await sandboxController.start();
7290
7397
  const appOriginController = createAppOriginController({
@@ -7337,7 +7444,7 @@ async function startHonoServer(config) {
7337
7444
  });
7338
7445
  app.use("/*", serveStatic({ root: rootPath }));
7339
7446
  app.get("*", async (c) => {
7340
- if (c.req.path.startsWith("/api")) return c.notFound();
7447
+ if (isApiPath(c.req.path)) return c.notFound();
7341
7448
  try {
7342
7449
  return serveIndexHtml(c);
7343
7450
  } catch (error) {
@@ -7404,14 +7511,6 @@ function ensureStdioCwd(config) {
7404
7511
  };
7405
7512
  return config;
7406
7513
  }
7407
- function deriveSeedServerId(config) {
7408
- if (config.type === "sse" || config.type === "streamable-http") try {
7409
- return new URL(config.url).host || "server";
7410
- } catch {
7411
- return "server";
7412
- }
7413
- return config.command?.split(/[\\/]/).pop() || "server";
7414
- }
7415
7514
  async function runWeb(argv) {
7416
7515
  const program = new Command();
7417
7516
  const argSeparatorIndex = argv.indexOf("--");
@@ -7487,7 +7586,7 @@ async function runWeb(argv) {
7487
7586
  headers: opts.header
7488
7587
  } : { ...config };
7489
7588
  if (opts.protocolEra !== void 0) entry.protocolEra = opts.protocolEra;
7490
- initialServers = { mcpServers: { [deriveSeedServerId(config)]: entry } };
7589
+ initialServers = { mcpServers: { [adHocServerName(config)]: entry } };
7491
7590
  writable = false;
7492
7591
  } else if (hasHeaders) {
7493
7592
  console.error("Error: --header requires an ad-hoc --server-url or command target.");