@modelcontextprotocol/inspector 2.13.0 → 2.15.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 (34) hide show
  1. package/README.md +1 -1
  2. package/clients/cli/build/index.js +5481 -1847
  3. package/clients/mcpdo/build/mcp-bin.js +925 -186
  4. package/clients/mcpdo/build/mcpdod.js +2 -3
  5. package/clients/mcpdo/build/{server-BHuKGkMj.js → server-B-CUVIBj.js} +3544 -995
  6. package/clients/tui/build/index.js +2126 -570
  7. package/clients/web/build/index.js +459 -280
  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-BZ94R6fh.js +1599 -0
  12. package/clients/web/dist/assets/{index-dqp8Sd8W.css → index-tBehH5iF.css} +1 -1
  13. package/clients/web/dist/assets/{prism-light-BIhJtJkv.js → prism-light-Crps--E8.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/assets/worker-json-DEGsizKa.js +7859 -0
  27. package/clients/web/dist/fonts/Fredoka-OFL.txt +93 -0
  28. package/clients/web/dist/fonts/RobotoMono-OFL.txt +93 -0
  29. package/clients/web/dist/index.html +2 -5
  30. package/clients/web/static/sandbox_proxy.html +177 -6
  31. package/package.json +8 -4
  32. package/skills/mcpdo/SKILL.md +63 -2
  33. package/clients/web/dist/assets/index-BqEbhCDX.js +0 -1587
  34. package/clients/web/dist/assets/worker-json-8CDXFfik.js +0 -1631
@@ -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";
@@ -37,20 +37,18 @@ import { createServer as createServer$2 } from "node:https";
37
37
  import { serve } from "@hono/node-server";
38
38
  import { serveStatic } from "@hono/node-server/serve-static";
39
39
  import { spawnSync } from "node:child_process";
40
- //#region ../../core/storage/store-id.ts
40
+ // ../../core/storage/store-id.ts
41
41
  function validateStoreId(storeId) {
42
42
  return /^[a-zA-Z0-9_-]+$/.test(storeId) && storeId !== "__proto__";
43
43
  }
44
- //#endregion
45
- //#region ../../core/storage/store-serialize.ts
44
+ // ../../core/storage/store-serialize.ts
46
45
  function serializeStore(data) {
47
46
  return JSON.stringify(data, null, 2);
48
47
  }
49
48
  function parseStore(raw) {
50
49
  return JSON.parse(raw);
51
50
  }
52
- //#endregion
53
- //#region ../../core/storage/store-io.ts
51
+ // ../../core/storage/store-io.ts
54
52
  function getDefaultStorageDir() {
55
53
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
56
54
  return path$1.join(homeDir, ".mcp-inspector", "storage");
@@ -96,8 +94,7 @@ async function deleteStoreFile(filePath) {
96
94
  if (error.code !== "ENOENT") throw error;
97
95
  }
98
96
  }
99
- //#endregion
100
- //#region ../../core/mcp/types.ts
97
+ // ../../core/mcp/types.ts
101
98
  const DEFAULT_TASK_TTL_MS = 6e4;
102
99
  const DEFAULT_MAX_FETCH_REQUESTS = 1e3;
103
100
  const MODERN_LOG_LEVELS = [
@@ -114,8 +111,7 @@ const MODERN_LOG_LEVELS = [
114
111
  function isModernLogLevel(value) {
115
112
  return typeof value === "string" && MODERN_LOG_LEVELS.includes(value);
116
113
  }
117
- //#endregion
118
- //#region ../../core/extension/skills/schemas.ts
114
+ // ../../core/extension/skills/schemas.ts
119
115
  const CACHE_FIELDS = {
120
116
  ttlMs: ReadResourceDirectoryResultSchema.shape.ttlMs,
121
117
  cacheScope: SkillCacheScopeSchema
@@ -227,13 +223,11 @@ SKILL_LIMITS.bytes;
227
223
  function isSkillCatalogLimit(value) {
228
224
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
229
225
  }
230
- //#endregion
231
- //#region ../../core/auth/secret-fields.ts
226
+ // ../../core/auth/secret-fields.ts
232
227
  const SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
233
228
  const SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
234
229
  const envSecretField = (envKey) => `env:${envKey}`;
235
- //#endregion
236
- //#region ../../core/json/jsonUtils.ts
230
+ // ../../core/json/jsonUtils.ts
237
231
  function isSerializableJson(value) {
238
232
  if (value === null) return true;
239
233
  switch (typeof value) {
@@ -247,8 +241,7 @@ function isSerializableJson(value) {
247
241
  function toRecord(value) {
248
242
  return value;
249
243
  }
250
- //#endregion
251
- //#region ../../core/mcp/serverList.ts
244
+ // ../../core/mcp/serverList.ts
252
245
  const VALID_SERVER_TYPES = /* @__PURE__ */ new Set([
253
246
  "stdio",
254
247
  "sse",
@@ -556,28 +549,38 @@ function expectedSecretFields(stored) {
556
549
  if (isStdioStored(stored) && stored.env) for (const k of Object.keys(stored.env)) fields.push(envSecretField(k));
557
550
  return fields;
558
551
  }
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"
552
+ const EXAMPLE_SERVER_URL = "https://example-server.modelcontextprotocol.io/mcp";
553
+ function adHocServerName(config) {
554
+ if (config.type === "sse" || config.type === "streamable-http") try {
555
+ return new URL(config.url).host || "server";
556
+ } catch {
557
+ return "server";
577
558
  }
578
- } };
579
- //#endregion
580
- //#region ../../core/storage/own-entry.ts
559
+ return config.command?.split(/[\\/]/).pop() || "server";
560
+ }
561
+ function defaultSeedConfig(tempDir) {
562
+ return { mcpServers: {
563
+ "filesystem-server-default": {
564
+ type: "stdio",
565
+ command: "npx",
566
+ args: [
567
+ "-y",
568
+ "@modelcontextprotocol/server-filesystem",
569
+ tempDir
570
+ ]
571
+ },
572
+ "everything-server-default": {
573
+ type: "stdio",
574
+ command: "npx",
575
+ args: ["-y", "@modelcontextprotocol/server-everything"]
576
+ },
577
+ "example-server-default": {
578
+ type: "streamable-http",
579
+ url: EXAMPLE_SERVER_URL
580
+ }
581
+ } };
582
+ }
583
+ // ../../core/storage/own-entry.ts
581
584
  function setOwnEntry(map, key, value) {
582
585
  Object.defineProperty(map, key, {
583
586
  value,
@@ -589,8 +592,7 @@ function setOwnEntry(map, key, value) {
589
592
  function getOwnEntry(map, key) {
590
593
  return map !== void 0 && Object.hasOwn(map, key) ? map[key] : void 0;
591
594
  }
592
- //#endregion
593
- //#region ../../core/mcp/node/config.ts
595
+ // ../../core/mcp/node/config.ts
594
596
  function parseKeyValuePair(value, previous = {}) {
595
597
  const parts = value.split("=");
596
598
  const key = parts[0] ?? "";
@@ -759,8 +761,7 @@ function resolveServerConfigs(options, mode) {
759
761
  }
760
762
  const API_SERVER_ENV_VARS = { AUTH_TOKEN: "MCP_INSPECTOR_API_TOKEN" };
761
763
  const INSPECTOR_API_TOKEN_GLOBAL = "__INSPECTOR_API_TOKEN__";
762
- //#endregion
763
- //#region ../../core/auth/secret-storage-info.ts
764
+ // ../../core/auth/secret-storage-info.ts
764
765
  function secretStorageLabel(info) {
765
766
  switch (info.kind) {
766
767
  case "keyring": return "OS keychain";
@@ -787,8 +788,7 @@ function secretStorageSummary(info) {
787
788
  if (info.reason === "fallback") parts.push("— fell back from the OS keychain");
788
789
  return parts.join(" ");
789
790
  }
790
- //#endregion
791
- //#region ../../core/node/version.ts
791
+ // ../../core/node/version.ts
792
792
  const ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
793
793
  function readInspectorVersion(callerUrl) {
794
794
  const startDir = dirname(fileURLToPath(callerUrl));
@@ -812,8 +812,7 @@ function readInspectorVersionSafe(callerUrl) {
812
812
  return;
813
813
  }
814
814
  }
815
- //#endregion
816
- //#region ../../core/node/openUrl.ts
815
+ // ../../core/node/openUrl.ts
817
816
  const OPEN_URL_TIMEOUT_MS = 5e3;
818
817
  const OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
819
818
  function toOpenableHref(href) {
@@ -841,8 +840,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
841
840
  clearTimeout(timer);
842
841
  }
843
842
  }
844
- //#endregion
845
- //#region ../../core/node/hostUrl.ts
843
+ // ../../core/node/hostUrl.ts
846
844
  function stripBrackets(host) {
847
845
  return host.replace(/^\[(.*)\]$/, "$1");
848
846
  }
@@ -895,8 +893,7 @@ function isLoopbackHost(host) {
895
893
  const h = canonicalUrlHost(host).replace(/\.$/, "");
896
894
  return h === "localhost" || h === "[::1]" || /^127(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/.test(h);
897
895
  }
898
- //#endregion
899
- //#region server/resolve-bind-host.ts
896
+ // server/resolve-bind-host.ts
900
897
  const BIND_ALL_INTERFACES_ENV = "DANGEROUSLY_BIND_ALL_INTERFACES";
901
898
  function isEnvFlagEnabled(value) {
902
899
  const v = value?.trim().toLowerCase();
@@ -912,9 +909,102 @@ function resolveBindHostname(env = process.env) {
912
909
  }
913
910
  return stripBrackets(host);
914
911
  }
915
- //#endregion
916
- //#region server/sandbox-controller.ts
912
+ // server/host-allow-list.ts
913
+ const ALLOWED_HOSTS_ENV = "MCP_INSPECTOR_ALLOWED_HOSTS";
914
+ const HOSTNAME_RE = /^[a-z0-9._-]+$/;
915
+ function normalizeHostname(hostname) {
916
+ return hostname.trim().toLowerCase().replace(/\.$/, "");
917
+ }
918
+ function parseHostHeader(value) {
919
+ const raw = value.trim().toLowerCase();
920
+ let hostname;
921
+ let port;
922
+ if (raw.startsWith("[")) {
923
+ const end = raw.indexOf("]");
924
+ if (end < 0) return null;
925
+ hostname = raw.slice(0, end + 1);
926
+ if (!isIPv6(hostname.slice(1, -1))) return null;
927
+ const rest = raw.slice(end + 1);
928
+ if (rest !== "") {
929
+ if (!rest.startsWith(":")) return null;
930
+ port = rest.slice(1);
931
+ }
932
+ } else {
933
+ const colon = raw.indexOf(":");
934
+ hostname = colon < 0 ? raw : raw.slice(0, colon);
935
+ port = colon < 0 ? void 0 : raw.slice(colon + 1);
936
+ }
937
+ if (port !== void 0) {
938
+ if (!/^\d{1,5}$/.test(port) || Number(port) > 65535) return null;
939
+ }
940
+ if (hostname.startsWith("[")) return hostname;
941
+ const name = normalizeHostname(hostname);
942
+ return HOSTNAME_RE.test(name) ? name : null;
943
+ }
944
+ function parseAllowedHostsEnv(value) {
945
+ if (!value) return [];
946
+ const out = [];
947
+ for (const entry of value.split(",")) {
948
+ const trimmed = entry.trim();
949
+ if (trimmed === "") continue;
950
+ const domain = trimmed.startsWith(".") ? "." : "";
951
+ const ascii = /[\s/:*@]/.test(trimmed) ? "" : domainToASCII(normalizeHostname(trimmed.slice(domain.length)));
952
+ if (!HOSTNAME_RE.test(ascii)) {
953
+ console.warn(`Ignoring invalid ${ALLOWED_HOSTS_ENV} entry: ${trimmed} (list bare hostnames, e.g. inspector.example.com or .example.com)`);
954
+ continue;
955
+ }
956
+ out.push(domain + ascii);
957
+ }
958
+ return out;
959
+ }
960
+ function configuredAllowedHosts(options) {
961
+ const hosts = /* @__PURE__ */ new Set();
962
+ if (!isAllInterfacesHost(options.bindHost)) hosts.add(normalizeHostname(canonicalUrlHost(options.bindHost)));
963
+ for (const origin of options.allowedOrigins) try {
964
+ const { hostname } = new URL(origin);
965
+ if (hostname !== "") hosts.add(normalizeHostname(hostname));
966
+ } catch {}
967
+ for (const extra of options.extraHosts ?? []) hosts.add(normalizeHostname(extra));
968
+ return [...hosts];
969
+ }
970
+ function createHostAllowList(options) {
971
+ const configured = configuredAllowedHosts(options);
972
+ return (hostHeader) => {
973
+ if (hostHeader === void 0) return false;
974
+ const hostname = parseHostHeader(hostHeader);
975
+ if (hostname === null) return false;
976
+ if (hostname.startsWith("[")) return true;
977
+ if (isIPv4(hostname)) return true;
978
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
979
+ for (const allowed of configured) {
980
+ if (allowed === hostname) return true;
981
+ if (allowed.startsWith(".") && (allowed.slice(1) === hostname || hostname.endsWith(allowed))) return true;
982
+ }
983
+ return false;
984
+ };
985
+ }
986
+ function hostAllowListMiddleware(options) {
987
+ const isAllowed = createHostAllowList(options);
988
+ return async (c, next) => {
989
+ const host = c.req.header("host");
990
+ 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);
991
+ await next();
992
+ };
993
+ }
994
+ // server/sandbox-controller.ts
917
995
  const __dirname$3 = dirname(fileURLToPath(import.meta.url));
996
+ function isSandboxPath(url) {
997
+ const path = (url ?? "").split("?", 1)[0];
998
+ return path === "/sandbox" || path === "/sandbox/";
999
+ }
1000
+ function publicUrlHosts(publicUrl) {
1001
+ if (!publicUrl) return [];
1002
+ try {
1003
+ return [new URL(publicUrl).hostname];
1004
+ } catch {
1005
+ return [];
1006
+ }
1007
+ }
918
1008
  const LOOPBACK_FRAME_ANCESTORS = ["http://127.0.0.1:*", "http://localhost:*"];
919
1009
  const CSP_HOST_SOURCE = /^[a-z][a-z0-9+.-]*:\/\/[^\s;,'"[\]*]+$/i;
920
1010
  function sandboxFrameAncestors(allowedOrigins) {
@@ -948,7 +1038,12 @@ function resolveSandboxPort() {
948
1038
  return fromSandbox ?? parseListenPort$1(process.env.SERVER_PORT) ?? 6275;
949
1039
  }
950
1040
  function createSandboxController(options) {
951
- const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl } = options;
1041
+ const { port, host = DEFAULT_BIND_HOST, allowedOrigins, publicUrl, allowedHosts } = options;
1042
+ const isAllowedHost = createHostAllowList({
1043
+ bindHost: host,
1044
+ allowedOrigins: allowedOrigins ?? [],
1045
+ extraHosts: [...allowedHosts ?? [], ...publicUrlHosts(publicUrl)]
1046
+ });
952
1047
  let server = null;
953
1048
  let sandboxUrl = null;
954
1049
  let boundPort = 0;
@@ -977,18 +1072,25 @@ function createSandboxController(options) {
977
1072
  resolve(value);
978
1073
  };
979
1074
  server = createServer((req, res) => {
980
- if (req.method !== "GET" || req.url !== "/sandbox" && req.url !== "/sandbox/") {
1075
+ if (!isAllowedHost(req.headers.host)) {
1076
+ res.writeHead(403, { "Content-Type": "text/plain" });
1077
+ 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).`);
1078
+ return;
1079
+ }
1080
+ const isHead = req.method === "HEAD";
1081
+ if (req.method !== "GET" && !isHead || !isSandboxPath(req.url)) {
981
1082
  res.writeHead(404, { "Content-Type": "text/plain" });
982
1083
  res.end("Not Found");
983
1084
  return;
984
1085
  }
985
1086
  res.writeHead(200, {
986
1087
  "Content-Type": "text/html; charset=utf-8",
1088
+ "Content-Length": Buffer.byteLength(sandboxHtml),
987
1089
  "Cache-Control": "no-store, no-cache, must-revalidate",
988
1090
  Pragma: "no-cache",
989
1091
  "Content-Security-Policy": SANDBOX_PROXY_CSP
990
1092
  });
991
- res.end(sandboxHtml);
1093
+ res.end(isHead ? void 0 : sandboxHtml);
992
1094
  });
993
1095
  let retriedDynamic = false;
994
1096
  server.on("error", (err) => {
@@ -1210,8 +1312,7 @@ function createAppOriginController(options) {
1210
1312
  }
1211
1313
  };
1212
1314
  }
1213
- //#endregion
1214
- //#region server/public-address.ts
1315
+ // server/public-address.ts
1215
1316
  const SANDBOX_FULL_ADDRESS_ENV = "MCP_SANDBOX_FULL_ADDRESS";
1216
1317
  const APP_ORIGIN_FULL_ADDRESS_ENV = "MCP_APP_ORIGIN_FULL_ADDRESS";
1217
1318
  const SANDBOX_PATH = "/sandbox";
@@ -1280,8 +1381,7 @@ function resolveAppOriginPublicOrigin(raw, allowedOrigins, sandboxUrl) {
1280
1381
  warnMixedContent(APP_ORIGIN_FULL_ADDRESS_ENV, url, allowedOrigins);
1281
1382
  return url.origin;
1282
1383
  }
1283
- //#endregion
1284
- //#region server/tls.ts
1384
+ // server/tls.ts
1285
1385
  const TLS_KEY_PATH_ENV = "INSPECTOR_TLS_KEY_PATH";
1286
1386
  const TLS_CERT_PATH_ENV = "INSPECTOR_TLS_CERT_PATH";
1287
1387
  function webScheme(tls) {
@@ -1317,91 +1417,7 @@ function resolveTlsConfig(env = process.env) {
1317
1417
  cert
1318
1418
  };
1319
1419
  }
1320
- //#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
- //#region server/web-server-config.ts
1420
+ // server/web-server-config.ts
1405
1421
  const inspectorVersion = readInspectorVersionSafe(import.meta.url);
1406
1422
  function defaultEnvironmentFromProcess(extra) {
1407
1423
  const keys = process.platform === "win32" ? [
@@ -1522,6 +1538,10 @@ function defaultAllowedOrigins(hostname, port, scheme = "http") {
1522
1538
  ];
1523
1539
  return [webOrigin(scheme, h, port)];
1524
1540
  }
1541
+ function weakAuthTokenWarning(token) {
1542
+ if (token === "" || token.length >= 32) return null;
1543
+ 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.`;
1544
+ }
1525
1545
  function buildWebServerConfig(options = {}) {
1526
1546
  const { initialMcpConfig = null, mcpConfigPath, writable = true, initialServers = null } = options;
1527
1547
  const rawPort = process.env.CLIENT_PORT?.trim() || "6274";
@@ -1531,6 +1551,8 @@ function buildWebServerConfig(options = {}) {
1531
1551
  const tls = resolveTlsConfig();
1532
1552
  const dangerouslyOmitAuth = isEnvFlagEnabled(process.env.DANGEROUSLY_OMIT_AUTH);
1533
1553
  const authToken = dangerouslyOmitAuth ? "" : process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] ?? process.env["MCP_PROXY_AUTH_TOKEN"] ?? "";
1554
+ const weakToken = weakAuthTokenWarning(authToken);
1555
+ if (weakToken) console.warn(weakToken);
1534
1556
  const sandboxPort = resolveSandboxPort();
1535
1557
  const requestedAppOriginPort = resolveAppOriginPort();
1536
1558
  const appOriginCollidesWith = requestedAppOriginPort !== 0 && (requestedAppOriginPort === port ? "the web server (CLIENT_PORT)" : requestedAppOriginPort === sandboxPort ? "the MCP Apps sandbox (MCP_SANDBOX_PORT)" : null);
@@ -1584,8 +1606,7 @@ function resolveAutoOpen() {
1584
1606
  if (flag === "false") return false;
1585
1607
  return !process.env.VITEST;
1586
1608
  }
1587
- //#endregion
1588
- //#region ../../core/mcp/config.ts
1609
+ // ../../core/mcp/config.ts
1589
1610
  function getServerType(config) {
1590
1611
  if (!("type" in config) || config.type === void 0) return "stdio";
1591
1612
  const type = config.type;
@@ -1594,8 +1615,7 @@ function getServerType(config) {
1594
1615
  if (type === "streamable-http") return "streamable-http";
1595
1616
  throw new Error(`Invalid server type: ${type}. Valid types are: stdio, sse, streamable-http`);
1596
1617
  }
1597
- //#endregion
1598
- //#region ../../core/mcp/fetchTracking.ts
1618
+ // ../../core/mcp/fetchTracking.ts
1599
1619
  const SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
1600
1620
  "authorization",
1601
1621
  "cookie",
@@ -1704,14 +1724,15 @@ function isLongLivedStreamResponse(method, contentType) {
1704
1724
  }
1705
1725
  const CHALLENGE_BODY_MAX_BYTES = 65536;
1706
1726
  const CHALLENGE_BODY_READ_MS = 2e3;
1727
+ const TRACKED_BODY_MAX_BYTES = 1048576;
1707
1728
  const TRUNCATED_BODY_SUFFIX = "\n[truncated]";
1708
1729
  const WITHHELD_TRUNCATED_JSON_BODY = "[truncated JSON body withheld: an incomplete body cannot be redacted]";
1709
- function recordableChallengeBody(read, contentType) {
1730
+ function recordableBody(read, contentType) {
1710
1731
  if (!read.truncated) return redactBody(read.text, contentType);
1711
1732
  if ((contentType ?? "").toLowerCase().includes("json") || /^\s*[{[]/.test(read.text)) return WITHHELD_TRUNCATED_JSON_BODY;
1712
1733
  return redactBody(read.text, contentType) + TRUNCATED_BODY_SUFFIX;
1713
1734
  }
1714
- async function readChallengeBody(response) {
1735
+ async function readCappedBody(response, maxBytes, timeoutMs) {
1715
1736
  let reader;
1716
1737
  try {
1717
1738
  const body = response.clone().body;
@@ -1725,20 +1746,23 @@ async function readChallengeBody(response) {
1725
1746
  let text = "";
1726
1747
  let bytes = 0;
1727
1748
  let timer;
1728
- const deadline = new Promise((resolve) => {
1729
- timer = setTimeout(() => resolve("timeout"), CHALLENGE_BODY_READ_MS);
1749
+ const deadline = timeoutMs === void 0 ? void 0 : new Promise((resolve) => {
1750
+ timer = setTimeout(() => resolve("timeout"), timeoutMs);
1730
1751
  });
1731
1752
  try {
1732
- while (bytes < CHALLENGE_BODY_MAX_BYTES) {
1733
- const next = await Promise.race([reader.read(), deadline]);
1753
+ for (;;) {
1754
+ const next = await (deadline ? Promise.race([reader.read(), deadline]) : reader.read());
1734
1755
  if (next === "timeout") break;
1735
1756
  if (next.done) return {
1736
1757
  text: text + decoder.decode(),
1737
1758
  truncated: false
1738
1759
  };
1739
- const chunk = next.value.subarray(0, CHALLENGE_BODY_MAX_BYTES - bytes);
1760
+ if (next.value.byteLength === 0) continue;
1761
+ if (bytes >= maxBytes) break;
1762
+ const chunk = next.value.subarray(0, maxBytes - bytes);
1740
1763
  bytes += chunk.byteLength;
1741
1764
  text += decoder.decode(chunk, { stream: true });
1765
+ if (chunk.byteLength < next.value.byteLength) break;
1742
1766
  }
1743
1767
  reader.cancel().catch(() => {});
1744
1768
  return {
@@ -1751,6 +1775,9 @@ async function readChallengeBody(response) {
1751
1775
  clearTimeout(timer);
1752
1776
  }
1753
1777
  }
1778
+ function readChallengeBody(response) {
1779
+ return readCappedBody(response, CHALLENGE_BODY_MAX_BYTES, CHALLENGE_BODY_READ_MS);
1780
+ }
1754
1781
  function longLivedStreamFraming(contentType) {
1755
1782
  return contentType?.toLowerCase().includes("application/x-ndjson") ? "ndjson" : "sse";
1756
1783
  }
@@ -1916,7 +1943,7 @@ function createFetchTracker(baseFetch, callbacks) {
1916
1943
  };
1917
1944
  if ((responseStatus === 401 || responseStatus === 403) && response.body && !response.bodyUsed) {
1918
1945
  const read = await readChallengeBody(response);
1919
- entry.responseBody = read === void 0 ? void 0 : recordableChallengeBody(read, response.headers.get("content-type"));
1946
+ entry.responseBody = read === void 0 ? void 0 : recordableBody(read, response.headers.get("content-type"));
1920
1947
  callbacks.trackRequest?.(entry);
1921
1948
  return response;
1922
1949
  }
@@ -1924,11 +1951,12 @@ function createFetchTracker(baseFetch, callbacks) {
1924
1951
  if (isLongLivedStream && response.body && !response.bodyUsed) watchLongLivedStream(response, id, longLivedStreamFraming(response.headers.get("content-type")), callbacks);
1925
1952
  if (!isLongLivedStream && response.body && !response.bodyUsed) {
1926
1953
  const responseContentType = response.headers.get("content-type");
1927
- try {
1928
- response.clone().text().then((body) => {
1929
- callbacks.updateResponseBody?.(id, redactBody(body, responseContentType) ?? body);
1930
- }).catch(() => {});
1931
- } catch {}
1954
+ readCappedBody(response, TRACKED_BODY_MAX_BYTES).then((read) => {
1955
+ if (read === void 0) return;
1956
+ try {
1957
+ callbacks.updateResponseBody?.(id, recordableBody(read, responseContentType));
1958
+ } catch {}
1959
+ });
1932
1960
  }
1933
1961
  return response;
1934
1962
  };
@@ -1994,8 +2022,7 @@ function parseAuthChallengeFromResponse(response, context) {
1994
2022
  }
1995
2023
  };
1996
2024
  }
1997
- //#endregion
1998
- //#region ../../core/mcp/node/authChallengeFetch.ts
2025
+ // ../../core/mcp/node/authChallengeFetch.ts
1999
2026
  function createAuthChallengeInterceptFetch(baseFetch) {
2000
2027
  return async (input, init) => {
2001
2028
  const response = await baseFetch(input, init);
@@ -2016,8 +2043,7 @@ function createAuthChallengeObserverFetch(baseFetch, onChallenge) {
2016
2043
  return response;
2017
2044
  };
2018
2045
  }
2019
- //#endregion
2020
- //#region ../../core/mcp/node/proxyFetch.ts
2046
+ // ../../core/mcp/node/proxyFetch.ts
2021
2047
  function asUndiciInit(init) {
2022
2048
  return init;
2023
2049
  }
@@ -2107,8 +2133,7 @@ function createProxyFetch() {
2107
2133
  return (await loadedProxiedFetch)(input, init);
2108
2134
  };
2109
2135
  }
2110
- //#endregion
2111
- //#region ../../core/auth/requestTimeout.ts
2136
+ // ../../core/auth/requestTimeout.ts
2112
2137
  const OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
2113
2138
  function readNegotiatesEndpoint(hint) {
2114
2139
  if (!hint) return true;
@@ -2155,11 +2180,23 @@ function callerSignalOf(input, init) {
2155
2180
  if (explicit !== void 0) return explicit ?? void 0;
2156
2181
  if (typeof input !== "string" && !(input instanceof URL)) return input.signal;
2157
2182
  }
2158
- //#endregion
2159
- //#region ../../core/mcp/node/fetchGuard.ts
2183
+ // ../../core/auth/fetchRefusal.ts
2160
2184
  var FetchTargetError = class extends Error {
2161
2185
  name = "FetchTargetError";
2186
+ url;
2187
+ constructor(message, url) {
2188
+ super(message);
2189
+ if (url !== void 0) this.url = url;
2190
+ }
2162
2191
  };
2192
+ function findFetchTargetError(err) {
2193
+ let current = err;
2194
+ for (let depth = 0; depth < 8 && current instanceof Error; depth++) {
2195
+ if (current instanceof FetchTargetError) return current;
2196
+ current = current.cause;
2197
+ }
2198
+ }
2199
+ const FETCH_TARGET_REFUSED_WIRE_CODE = "fetch_target_refused";
2163
2200
  const REDIRECT_STATUSES = /* @__PURE__ */ new Set([
2164
2201
  301,
2165
2202
  302,
@@ -2168,11 +2205,12 @@ const REDIRECT_STATUSES = /* @__PURE__ */ new Set([
2168
2205
  308
2169
2206
  ]);
2170
2207
  const resolveWithSystemDns = async (hostname) => (await lookup(hostname, { all: true })).map((r) => r.address);
2208
+ const METADATA_IPV4_ADDRESSES = /* @__PURE__ */ new Set(["100.100.100.200", "192.0.0.192"]);
2171
2209
  function isBlockedAddress(ip) {
2172
2210
  const kind = isIP(ip);
2173
2211
  if (kind === 4) {
2174
2212
  const [a, b] = ip.split(".").map(Number);
2175
- return a === 169 && b === 254;
2213
+ return a === 169 && b === 254 || METADATA_IPV4_ADDRESSES.has(ip);
2176
2214
  }
2177
2215
  if (kind !== 6) return false;
2178
2216
  const addr = ip.toLowerCase();
@@ -2335,27 +2373,35 @@ async function guardedFetch(url, init, options = {}) {
2335
2373
  headers: init.headers
2336
2374
  };
2337
2375
  for (let hop = 0; hop <= 5; hop++) {
2338
- await assertAllowedTarget(request.url, resolveHost, viaProxy, init.signal);
2339
- const res = await fetchFn(request.url.href, {
2340
- method: request.method,
2341
- headers: new Headers(request.headers),
2342
- body: request.body,
2343
- signal: init.signal,
2344
- redirect: "manual"
2345
- });
2376
+ let res;
2377
+ try {
2378
+ await assertAllowedTarget(request.url, resolveHost, viaProxy, init.signal);
2379
+ res = await fetchFn(request.url.href, {
2380
+ method: request.method,
2381
+ headers: new Headers(request.headers),
2382
+ body: request.body,
2383
+ signal: init.signal,
2384
+ redirect: "manual"
2385
+ });
2386
+ } catch (err) {
2387
+ stampRefusedUrl(err, request.url);
2388
+ throw err;
2389
+ }
2346
2390
  const location = res.headers.get("location");
2347
2391
  if (!REDIRECT_STATUSES.has(res.status) || location === null) return res;
2348
2392
  res.body?.cancel().catch(() => {});
2349
- request = nextRedirectRequest(request, res.status, location);
2393
+ try {
2394
+ request = nextRedirectRequest(request, res.status, location);
2395
+ } catch (err) {
2396
+ stampRefusedUrl(err, request.url);
2397
+ throw err;
2398
+ }
2350
2399
  }
2351
- throw new FetchTargetError(`Refusing to follow more than 5 redirects`);
2400
+ throw new FetchTargetError(`Refusing to follow more than 5 redirects`, request.url.href);
2352
2401
  }
2353
- function findFetchTargetError(err) {
2354
- let current = err;
2355
- for (let depth = 0; depth < 8 && current instanceof Error; depth++) {
2356
- if (current instanceof FetchTargetError) return current;
2357
- current = current.cause;
2358
- }
2402
+ function stampRefusedUrl(err, url) {
2403
+ const refused = findFetchTargetError(err);
2404
+ if (refused && refused.url === void 0) refused.url = url.href;
2359
2405
  }
2360
2406
  async function bodyText(input, init) {
2361
2407
  const body = init?.body !== void 0 ? init.body : input instanceof Request && input.body !== null ? await input.clone().text() : void 0;
@@ -2382,8 +2428,7 @@ function createGuardedFetch(options = {}) {
2382
2428
  }
2383
2429
  };
2384
2430
  }
2385
- //#endregion
2386
- //#region ../../core/auth/routeOAuthRequests.ts
2431
+ // ../../core/auth/routeOAuthRequests.ts
2387
2432
  function isJsonRpcMessage(value) {
2388
2433
  return typeof value === "object" && value !== null && value.jsonrpc === "2.0";
2389
2434
  }
@@ -2407,8 +2452,7 @@ function isMcpShapedRequest(input, init) {
2407
2452
  function routeOAuthRequests(mcpFetch, oauthFetch, isMcpEndpoint) {
2408
2453
  return (input, init) => isMcpShapedRequest(input, init) && isMcpEndpoint(requestUrlOf(input), requestHeadersOf(input, init)) ? mcpFetch(input, init) : oauthFetch(input, init);
2409
2454
  }
2410
- //#endregion
2411
- //#region ../../core/mcp/node/notificationHeadersFetch.ts
2455
+ // ../../core/mcp/node/notificationHeadersFetch.ts
2412
2456
  function createNotificationHeadersFetch(baseFetch) {
2413
2457
  return (input, init) => {
2414
2458
  const method = modernNotificationMethod(init);
@@ -2444,8 +2488,7 @@ function modernNotificationMethod(init) {
2444
2488
  function isRecord$2(value) {
2445
2489
  return typeof value === "object" && value !== null && !Array.isArray(value);
2446
2490
  }
2447
- //#endregion
2448
- //#region ../../core/mcp/node/suppressNotificationStreamFetch.ts
2491
+ // ../../core/mcp/node/suppressNotificationStreamFetch.ts
2449
2492
  function createSuppressNotificationStreamFetch(baseFetch, endpoint) {
2450
2493
  return async (input, init) => {
2451
2494
  if (isStandaloneStreamRequest(input, init, endpoint)) return new Response(null, {
@@ -2471,8 +2514,7 @@ function isStandaloneStreamRequest(input, init, endpoint) {
2471
2514
  const headers = new Headers(init?.headers ?? request?.headers);
2472
2515
  return (headers.get("accept")?.toLowerCase() ?? "").includes("text/event-stream") && !headers.has("last-event-id");
2473
2516
  }
2474
- //#endregion
2475
- //#region ../../core/mcp/node/transport.ts
2517
+ // ../../core/mcp/node/transport.ts
2476
2518
  function headersFromSettings(settings) {
2477
2519
  if (!settings || settings.headers.length === 0) return void 0;
2478
2520
  const out = {};
@@ -2554,8 +2596,7 @@ function createTransportNode(config, options = {}) {
2554
2596
  }) };
2555
2597
  }
2556
2598
  }
2557
- //#endregion
2558
- //#region ../../core/mcp/remote/node/cappedBody.ts
2599
+ // ../../core/mcp/remote/node/cappedBody.ts
2559
2600
  async function readCappedText(body, maxBytes) {
2560
2601
  const reader = body.getReader();
2561
2602
  const decoder = new TextDecoder();
@@ -2624,6 +2665,24 @@ var RateLimitLedger = class {
2624
2665
  function isRateLimitError(err) {
2625
2666
  return SdkHttpError.isInstance(err) && err.status === 429;
2626
2667
  }
2668
+ // ../../core/mcp/sessionTermination.ts
2669
+ function canTerminateSession(transport) {
2670
+ return typeof transport === "object" && transport !== null && "terminateSession" in transport && typeof transport.terminateSession === "function";
2671
+ }
2672
+ const SESSION_TERMINATE_TIMEOUT_MS = 3e3;
2673
+ async function terminateSessionBestEffort(transport, timeoutMs = SESSION_TERMINATE_TIMEOUT_MS) {
2674
+ if (!canTerminateSession(transport) || !transport.sessionId) return false;
2675
+ let timer;
2676
+ const timedOut = new Promise((resolve) => {
2677
+ timer = setTimeout(() => resolve(false), timeoutMs);
2678
+ });
2679
+ const terminated = new Promise((resolve) => resolve(transport.terminateSession())).then(() => true, () => false);
2680
+ try {
2681
+ return await Promise.race([terminated, timedOut]);
2682
+ } finally {
2683
+ clearTimeout(timer);
2684
+ }
2685
+ }
2627
2686
  function mergeOAuthSections(disk, snapshot, sections) {
2628
2687
  const merged = {
2629
2688
  servers: { ...disk?.servers },
@@ -2740,8 +2799,7 @@ function parseOAuthStoreWriteBody(body) {
2740
2799
  if (!snapshot || !snapshotHasValidSecretPayloads(snapshot)) return null;
2741
2800
  return { snapshot };
2742
2801
  }
2743
- //#endregion
2744
- //#region ../../core/auth/node/secret-store.ts
2802
+ // ../../core/auth/node/secret-store.ts
2745
2803
  const SERVICE_NAME = "mcp-inspector";
2746
2804
  let keyringLoad;
2747
2805
  var KeyringModuleShapeError = class extends Error {
@@ -2961,8 +3019,7 @@ var SessionSecretStore = class extends InMemorySecretStore {
2961
3019
  return false;
2962
3020
  }
2963
3021
  };
2964
- //#endregion
2965
- //#region ../../core/auth/node/file-lock.ts
3022
+ // ../../core/auth/node/file-lock.ts
2966
3023
  const STALE_MS = 1e4;
2967
3024
  const RETRY = {
2968
3025
  retries: 20,
@@ -3094,8 +3151,7 @@ async function withSecretFileLock(filePath, fn) {
3094
3151
  await release();
3095
3152
  }
3096
3153
  }
3097
- //#endregion
3098
- //#region ../../core/auth/node/file-secret-store.ts
3154
+ // ../../core/auth/node/file-secret-store.ts
3099
3155
  const scrypt = (passphrase, salt, keylen, options) => new Promise((resolve, reject) => {
3100
3156
  crypto$1.scrypt(passphrase, salt, keylen, options, (err, key) => {
3101
3157
  /* v8 ignore next -- @preserve: node validates the cost parameters
@@ -3503,8 +3559,7 @@ async function readSecretFilePermissions(filePath) {
3503
3559
  };
3504
3560
  }
3505
3561
  }
3506
- //#endregion
3507
- //#region ../../core/auth/node/secret-store-selection.ts
3562
+ // ../../core/auth/node/secret-store-selection.ts
3508
3563
  const SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
3509
3564
  const SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
3510
3565
  const STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
@@ -3816,8 +3871,7 @@ function defaultSecretStore() {
3816
3871
  async function resolveConcreteSecretStore(store) {
3817
3872
  return store instanceof DeferredSecretStore ? (await resolveSecretStore()).store : store;
3818
3873
  }
3819
- //#endregion
3820
- //#region ../../core/auth/node/oauth-secrets.ts
3874
+ // ../../core/auth/node/oauth-secrets.ts
3821
3875
  const PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
3822
3876
  const warnedPolicyValues = /* @__PURE__ */ new Set();
3823
3877
  function getPersistTokensPolicy(env = process.env) {
@@ -4087,8 +4141,7 @@ function snapshotHasPlaintextSecrets(snapshot) {
4087
4141
  for (const session of Object.values(snapshot.idpSessions)) if (session.idToken !== void 0 || session.refreshToken !== void 0) return true;
4088
4142
  return false;
4089
4143
  }
4090
- //#endregion
4091
- //#region ../../core/auth/node/oauth-namespace-ledger.ts
4144
+ // ../../core/auth/node/oauth-namespace-ledger.ts
4092
4145
  const namespaceLedgerPath = (stateFilePath) => `${stateFilePath}.namespaces.json`;
4093
4146
  const KEYRING_LOCATION = "keyring";
4094
4147
  const FILE_LOCATION_PREFIX = "file:";
@@ -4346,8 +4399,7 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current, ow
4346
4399
  warnLedgerFailure("update", error);
4347
4400
  }
4348
4401
  }
4349
- //#endregion
4350
- //#region ../../core/auth/node/oauth-persist-file.ts
4402
+ // ../../core/auth/node/oauth-persist-file.ts
4351
4403
  const warnedStoreFailures = /* @__PURE__ */ new Set();
4352
4404
  function warnStoreWriteFailure(error) {
4353
4405
  const reason = error instanceof Error ? error.message : String(error);
@@ -4879,8 +4931,7 @@ async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
4879
4931
  if (locked) await purgeSupersededNamespaces(filePath, secretStore, void 0);
4880
4932
  });
4881
4933
  }
4882
- //#endregion
4883
- //#region ../../core/mcp/import/clientConfig.ts
4934
+ // ../../core/mcp/import/clientConfig.ts
4884
4935
  function parseJsonObject(raw) {
4885
4936
  let data;
4886
4937
  try {
@@ -4910,8 +4961,7 @@ function parseVsCodeConfig(raw) {
4910
4961
  if (!servers || typeof servers !== "object" || Array.isArray(servers)) throw new Error("No 'servers' object found in VS Code config file");
4911
4962
  return { mcpServers: normalizeServerMap(servers) };
4912
4963
  }
4913
- //#endregion
4914
- //#region ../../core/mcp/import/strategies.ts
4964
+ // ../../core/mcp/import/strategies.ts
4915
4965
  function join$1(...segments) {
4916
4966
  return segments.map((s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")).filter((s) => s.length > 0).join("/");
4917
4967
  }
@@ -4952,8 +5002,7 @@ const IMPORT_STRATEGIES = Object.fromEntries([
4952
5002
  function getImportStrategy(id) {
4953
5003
  return IMPORT_STRATEGIES[id];
4954
5004
  }
4955
- //#endregion
4956
- //#region ../../core/mcp/import/resolveSource.ts
5005
+ // ../../core/mcp/import/resolveSource.ts
4957
5006
  function resolveImportSource(type, platform, homeDir, readFile) {
4958
5007
  const strategy = getImportStrategy(type);
4959
5008
  if (!strategy) return null;
@@ -4996,8 +5045,7 @@ function resolveImportSource(type, platform, homeDir, readFile) {
4996
5045
  searched
4997
5046
  };
4998
5047
  }
4999
- //#endregion
5000
- //#region ../../core/mcp/remote/progressToken.ts
5048
+ // ../../core/mcp/remote/progressToken.ts
5001
5049
  function progressTokenOf(message) {
5002
5050
  if ("method" in message && message.method === "notifications/progress" && "params" in message && typeof message.params === "object" && message.params !== null) {
5003
5051
  const parsed = ProgressTokenSchema.safeParse(message.params.progressToken);
@@ -5010,12 +5058,20 @@ function waitForProgressToken(waits, token) {
5010
5058
  const numeric = Number(token);
5011
5059
  return Number.isSafeInteger(numeric) ? waits.get(numeric) : void 0;
5012
5060
  }
5013
- //#endregion
5014
- //#region ../../core/mcp/remote/node/remote-session.ts
5061
+ // ../../core/mcp/remote/node/remote-session.ts
5015
5062
  var RemoteSession = class RemoteSession {
5063
+ static DEFAULT_DETACH_GRACE_MS = 6e4;
5064
+ static MAX_QUEUED_EVENTS = 1e3;
5016
5065
  sessionId;
5066
+ serverName;
5067
+ transportType;
5068
+ createdAt;
5069
+ lastConsumerSeenAt = null;
5017
5070
  transport;
5071
+ terminated = new AbortController();
5018
5072
  eventQueue = [];
5073
+ droppedEventCount = 0;
5074
+ detachTimer;
5019
5075
  eventConsumer = null;
5020
5076
  transportDead = false;
5021
5077
  transportError = null;
@@ -5027,8 +5083,31 @@ var RemoteSession = class RemoteSession {
5027
5083
  appliedProtocolVersion;
5028
5084
  rateLimits = new RateLimitLedger();
5029
5085
  static PROTOCOL_VERSION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
5030
- constructor(sessionId) {
5086
+ constructor(sessionId, descriptor = {}) {
5031
5087
  this.sessionId = sessionId;
5088
+ this.serverName = descriptor.serverName;
5089
+ this.transportType = descriptor.transport ?? "stdio";
5090
+ this.createdAt = /* @__PURE__ */ new Date();
5091
+ }
5092
+ get terminatedSignal() {
5093
+ return this.terminated.signal;
5094
+ }
5095
+ terminate() {
5096
+ this.terminated.abort();
5097
+ }
5098
+ describe() {
5099
+ const attached = this.eventConsumer !== null;
5100
+ const lastSeen = attached ? /* @__PURE__ */ new Date() : this.lastConsumerSeenAt;
5101
+ return {
5102
+ sessionId: this.sessionId,
5103
+ ...this.serverName !== void 0 && { serverName: this.serverName },
5104
+ transport: this.transportType,
5105
+ createdAt: this.createdAt.toISOString(),
5106
+ consumerAttached: attached,
5107
+ lastConsumerSeenAt: lastSeen ? lastSeen.toISOString() : null,
5108
+ queuedEvents: this.eventQueue.length,
5109
+ transportDead: this.transportDead
5110
+ };
5032
5111
  }
5033
5112
  setAuthProviderHandle(handle) {
5034
5113
  this.authProviderHandle = handle;
@@ -5046,13 +5125,50 @@ var RemoteSession = class RemoteSession {
5046
5125
  this.transport.setProtocolVersion?.(version);
5047
5126
  }
5048
5127
  setEventConsumer(consumer) {
5128
+ this.cancelDetachGrace();
5049
5129
  this.eventConsumer = consumer;
5130
+ this.lastConsumerSeenAt = /* @__PURE__ */ new Date();
5131
+ if (this.droppedEventCount > 0) {
5132
+ const dropped = this.droppedEventCount;
5133
+ this.droppedEventCount = 0;
5134
+ consumer({
5135
+ type: "stdio_log",
5136
+ data: {
5137
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5138
+ message: `[inspector] ${dropped} event(s) were dropped while no client was attached (the queue holds at most ${RemoteSession.MAX_QUEUED_EVENTS})`
5139
+ }
5140
+ });
5141
+ }
5050
5142
  while (this.eventQueue.length > 0) consumer(this.eventQueue.shift());
5051
5143
  }
5052
5144
  clearEventConsumer() {
5145
+ if (this.eventConsumer !== null) this.lastConsumerSeenAt = /* @__PURE__ */ new Date();
5053
5146
  this.eventConsumer = null;
5054
5147
  return this.transportDead;
5055
5148
  }
5149
+ detachEventConsumer(consumer) {
5150
+ if (this.eventConsumer !== consumer) return false;
5151
+ this.eventConsumer = null;
5152
+ this.lastConsumerSeenAt = /* @__PURE__ */ new Date();
5153
+ return true;
5154
+ }
5155
+ armDetachGrace(graceMs, onExpire) {
5156
+ this.cancelDetachGrace();
5157
+ this.detachTimer = setTimeout(() => {
5158
+ this.detachTimer = void 0;
5159
+ onExpire();
5160
+ }, graceMs);
5161
+ this.detachTimer.unref();
5162
+ }
5163
+ cancelDetachGrace() {
5164
+ if (this.detachTimer !== void 0) {
5165
+ clearTimeout(this.detachTimer);
5166
+ this.detachTimer = void 0;
5167
+ }
5168
+ }
5169
+ hasPendingDetachGrace() {
5170
+ return this.detachTimer !== void 0;
5171
+ }
5056
5172
  markTransportDead(error) {
5057
5173
  this.transportDead = true;
5058
5174
  this.transportError = error;
@@ -5163,7 +5279,13 @@ var RemoteSession = class RemoteSession {
5163
5279
  }
5164
5280
  pushEvent(event) {
5165
5281
  if (this.eventConsumer) this.eventConsumer(event);
5166
- else this.eventQueue.push(event);
5282
+ else {
5283
+ if (this.eventQueue.length >= RemoteSession.MAX_QUEUED_EVENTS) {
5284
+ this.eventQueue.shift();
5285
+ this.droppedEventCount++;
5286
+ }
5287
+ this.eventQueue.push(event);
5288
+ }
5167
5289
  }
5168
5290
  onMessage(message) {
5169
5291
  if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) this.settleRequestWait(message.id);
@@ -5216,8 +5338,7 @@ var RemoteSession = class RemoteSession {
5216
5338
  });
5217
5339
  }
5218
5340
  };
5219
- //#endregion
5220
- //#region ../../core/mcp/remote/node/tokenAuthProvider.ts
5341
+ // ../../core/mcp/remote/node/tokenAuthProvider.ts
5221
5342
  const REMOTE_OAUTH_STUB_METADATA = {
5222
5343
  redirect_uris: [],
5223
5344
  scope: ""
@@ -5282,8 +5403,7 @@ function createRemoteAuthProvider(initialAuthState) {
5282
5403
  }
5283
5404
  };
5284
5405
  }
5285
- //#endregion
5286
- //#region ../../core/client/config-parse.ts
5406
+ // ../../core/client/config-parse.ts
5287
5407
  function isAbsoluteHttpUrl(value) {
5288
5408
  let url;
5289
5409
  try {
@@ -5375,8 +5495,7 @@ function formatClientConfigLoadError(error) {
5375
5495
  if (error instanceof Error) return error.message;
5376
5496
  return String(error);
5377
5497
  }
5378
- //#endregion
5379
- //#region ../../core/client/secrets.ts
5498
+ // ../../core/client/secrets.ts
5380
5499
  const CLIENT_KEYCHAIN_ID = "client";
5381
5500
  function hasClientPlaintextSecret(config) {
5382
5501
  const secret = config.enterpriseManagedAuth?.idp?.clientSecret;
@@ -5417,8 +5536,7 @@ function mergeSecretsIntoClientConfig(config, secrets) {
5417
5536
  }
5418
5537
  };
5419
5538
  }
5420
- //#endregion
5421
- //#region ../../core/client/node-persistence.ts
5539
+ // ../../core/client/node-persistence.ts
5422
5540
  async function withClientConfigLock(filePath, action, body) {
5423
5541
  let entered = false;
5424
5542
  try {
@@ -5510,25 +5628,44 @@ async function deleteClientConfigStore(filePath, secretStore) {
5510
5628
  }
5511
5629
  });
5512
5630
  }
5513
- //#endregion
5514
- //#region ../../core/mcp/remote/node/server.ts
5631
+ // ../../core/mcp/remote/node/server.ts
5515
5632
  const SSE_PRIMING_COMMENT = ":\n\n";
5516
5633
  function closeAbortedStream(stream) {
5517
5634
  stream.close().catch(() => {});
5518
5635
  }
5519
- async function primeAndHoldSseStream(stream, cleanup) {
5636
+ async function primeAndHoldSseStream(stream, cleanup, end) {
5520
5637
  let done = false;
5521
- const aborted = new Promise((resolve) => {
5522
- stream.onAbort(() => {
5523
- if (done) return;
5524
- done = true;
5525
- cleanup();
5638
+ const finish = () => {
5639
+ if (done) return false;
5640
+ done = true;
5641
+ cleanup();
5642
+ return true;
5643
+ };
5644
+ const released = new Promise((resolve) => {
5645
+ let closed = false;
5646
+ const release = () => {
5647
+ if (closed) return;
5648
+ closed = true;
5526
5649
  closeAbortedStream(stream);
5527
5650
  resolve();
5651
+ };
5652
+ let detachEnd = () => {};
5653
+ stream.onAbort(() => {
5654
+ if (finish()) detachEnd();
5655
+ release();
5528
5656
  });
5657
+ if (!end) return;
5658
+ const { signal, flush } = end;
5659
+ const onEnd = () => {
5660
+ if (!finish()) return;
5661
+ flush().catch(() => {}).then(release);
5662
+ };
5663
+ detachEnd = () => signal.removeEventListener("abort", onEnd);
5664
+ if (signal.aborted) onEnd();
5665
+ else signal.addEventListener("abort", onEnd, { once: true });
5529
5666
  });
5530
5667
  await stream.write(SSE_PRIMING_COMMENT);
5531
- await aborted;
5668
+ await released;
5532
5669
  }
5533
5670
  const MAX_APP_DOCUMENT_CHARS = 8388608;
5534
5671
  const MAX_APP_DOCUMENT_BODY_BYTES = 25165824;
@@ -5635,11 +5772,27 @@ function mcpParamHeadersOnly(headers) {
5635
5772
  for (const [key, value] of Object.entries(headers)) if (typeof value === "string" && key.toLowerCase().startsWith("Mcp-Param-".toLowerCase())) out[key] = value;
5636
5773
  return Object.keys(out).length > 0 ? out : void 0;
5637
5774
  }
5775
+ const SESSION_TERMINATED_MESSAGE = "Session terminated from the Inspector's active session list";
5776
+ const MAX_SESSION_SERVER_NAME_CHARS = 256;
5777
+ function sessionServerName(value) {
5778
+ if (typeof value !== "string") return void 0;
5779
+ const name = value.trim();
5780
+ return name === "" ? void 0 : name.slice(0, MAX_SESSION_SERVER_NAME_CHARS);
5781
+ }
5638
5782
  function createRemoteApp(options) {
5639
5783
  const dangerouslyOmitAuth = !!options.dangerouslyOmitAuth;
5640
5784
  const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
5641
5785
  const app = new Hono();
5642
5786
  const sessions = /* @__PURE__ */ new Map();
5787
+ const sessionDetachGraceMs = options.sessionDetachGraceMs ?? RemoteSession.DEFAULT_DETACH_GRACE_MS;
5788
+ const armSessionReap = (session) => {
5789
+ session.armDetachGrace(sessionDetachGraceMs, () => {
5790
+ sessions.delete(session.sessionId);
5791
+ session.rejectActiveRequestWaits(/* @__PURE__ */ new Error("Session closed: no client attached"));
5792
+ const transport = session.transport;
5793
+ terminateSessionBestEffort(transport).then(() => transport.close().catch(() => {}));
5794
+ });
5795
+ };
5643
5796
  const { logger: fileLogger, allowedOrigins } = options;
5644
5797
  const storageDir = options.storageDir ?? getDefaultStorageDir();
5645
5798
  const mcpConfigPath = options.mcpConfigPath ?? getDefaultMcpConfigPath();
@@ -5753,7 +5906,10 @@ function createRemoteApp(options) {
5753
5906
  const config = body.config;
5754
5907
  if (!config) return c.json({ error: "Missing config" }, 400);
5755
5908
  const sessionId = crypto.randomUUID();
5756
- const session = new RemoteSession(sessionId);
5909
+ const session = new RemoteSession(sessionId, {
5910
+ serverName: sessionServerName(body.serverName),
5911
+ transport: config.type ?? "stdio"
5912
+ });
5757
5913
  let transport;
5758
5914
  let authHandle;
5759
5915
  try {
@@ -5826,6 +5982,7 @@ function createRemoteApp(options) {
5826
5982
  return c.json({ error: `Failed to start transport: ${msg}` }, is401 ? 401 : 500);
5827
5983
  }
5828
5984
  sessions.set(sessionId, session);
5985
+ armSessionReap(session);
5829
5986
  return c.json({ sessionId });
5830
5987
  });
5831
5988
  app.post("/api/mcp/send", async (c) => {
@@ -5916,13 +6073,15 @@ function createRemoteApp(options) {
5916
6073
  const session = sessions.get(sessionId);
5917
6074
  if (!session) return c.json({ error: "Session not found" }, 404);
5918
6075
  return streamSSE(c, async (stream) => {
5919
- session.setEventConsumer((event) => {
6076
+ let lastWrite = Promise.resolve();
6077
+ const consumer = (event) => {
5920
6078
  const data = JSON.stringify(event);
5921
- stream.writeSSE({
6079
+ lastWrite = stream.writeSSE({
5922
6080
  event: event.type,
5923
6081
  data
5924
- });
5925
- });
6082
+ }).catch(() => {});
6083
+ };
6084
+ session.setEventConsumer(consumer);
5926
6085
  if (session.isTransportDead()) {
5927
6086
  await new Promise((resolve) => setTimeout(resolve, 0));
5928
6087
  session.clearEventConsumer();
@@ -5930,7 +6089,15 @@ function createRemoteApp(options) {
5930
6089
  return;
5931
6090
  }
5932
6091
  await primeAndHoldSseStream(stream, () => {
5933
- if (session.clearEventConsumer() || session.isTransportDead()) sessions.delete(sessionId);
6092
+ if (!session.detachEventConsumer(consumer)) return;
6093
+ if (session.isTransportDead()) {
6094
+ sessions.delete(sessionId);
6095
+ return;
6096
+ }
6097
+ armSessionReap(session);
6098
+ }, {
6099
+ signal: session.terminatedSignal,
6100
+ flush: () => lastWrite
5934
6101
  });
5935
6102
  });
5936
6103
  });
@@ -5945,12 +6112,30 @@ function createRemoteApp(options) {
5945
6112
  if (!sessionId) return c.json({ error: "Missing sessionId" }, 400);
5946
6113
  const session = sessions.get(sessionId);
5947
6114
  if (session) {
6115
+ session.cancelDetachGrace();
5948
6116
  session.clearEventConsumer();
6117
+ await terminateSessionBestEffort(session.transport);
5949
6118
  await session.transport.close();
5950
6119
  sessions.delete(sessionId);
5951
6120
  }
5952
6121
  return c.json({ ok: true });
5953
6122
  });
6123
+ app.get("/api/mcp/sessions", (c) => {
6124
+ const list = { sessions: [...sessions.values()].map((session) => session.describe()).sort((a, b) => a.createdAt.localeCompare(b.createdAt)) };
6125
+ return c.json(list);
6126
+ });
6127
+ app.delete("/api/mcp/sessions/:sessionId", async (c) => {
6128
+ const sessionId = c.req.param("sessionId");
6129
+ const session = sessions.get(sessionId);
6130
+ if (!session) return c.json({ error: "Session not found" }, 404);
6131
+ sessions.delete(sessionId);
6132
+ session.cancelDetachGrace();
6133
+ session.markTransportDead(SESSION_TERMINATED_MESSAGE);
6134
+ session.terminate();
6135
+ session.rejectActiveRequestWaits(/* @__PURE__ */ new Error(SESSION_TERMINATED_MESSAGE));
6136
+ await session.transport.close().catch(() => {});
6137
+ return c.json({ ok: true });
6138
+ });
5954
6139
  app.post("/api/mcp/auth-state", async (c) => {
5955
6140
  let body;
5956
6141
  try {
@@ -6042,7 +6227,14 @@ function createRemoteApp(options) {
6042
6227
  timeoutMs: deadlineMs
6043
6228
  }, 504);
6044
6229
  const refused = findFetchTargetError(err);
6045
- if (refused) return c.json({ error: refused.message }, 403);
6230
+ if (refused) {
6231
+ const body = {
6232
+ error: refused.message,
6233
+ code: FETCH_TARGET_REFUSED_WIRE_CODE,
6234
+ url: redactUrlQuery(refused.url ?? url)
6235
+ };
6236
+ return c.json(body, 403);
6237
+ }
6046
6238
  return c.json({ error: msg }, 500);
6047
6239
  } finally {
6048
6240
  if (timer !== void 0) clearTimeout(timer);
@@ -6608,8 +6800,9 @@ function createRemoteApp(options) {
6608
6800
  const settled = await withWriteLock(async () => {
6609
6801
  const rawInside = await readStoreFile(mcpConfigPath);
6610
6802
  if (rawInside === null) {
6611
- await writeMcpAndTrackMtime(serializeStore(DEFAULT_SEED_CONFIG));
6612
- return DEFAULT_SEED_CONFIG;
6803
+ const seed = defaultSeedConfig(tmpdir());
6804
+ await writeMcpAndTrackMtime(serializeStore(seed));
6805
+ return seed;
6613
6806
  }
6614
6807
  const parsedInside = parseStore(rawInside);
6615
6808
  const inside = { mcpServers: normalizeMcpServers(parsedInside?.mcpServers) };
@@ -6887,8 +7080,7 @@ function createRemoteApp(options) {
6887
7080
  }
6888
7081
  };
6889
7082
  }
6890
- //#endregion
6891
- //#region server/inject-auth-token.ts
7083
+ // server/inject-auth-token.ts
6892
7084
  function serializeTokenForScript(token) {
6893
7085
  return JSON.stringify(token).replace(/</g, "\\u003c");
6894
7086
  }
@@ -6901,8 +7093,7 @@ function injectAuthToken(html, token) {
6901
7093
  if (bodyClose !== -1) return html.slice(0, bodyClose) + script + html.slice(bodyClose);
6902
7094
  return script + html;
6903
7095
  }
6904
- //#endregion
6905
- //#region server/health.ts
7096
+ // server/health.ts
6906
7097
  const HEALTH_PATH = "/healthz";
6907
7098
  const HEALTH_BODY = Object.freeze({ status: "ok" });
6908
7099
  const HEALTH_HEADERS = Object.freeze({
@@ -6932,8 +7123,7 @@ function handleNodeHealthRequest(req, res) {
6932
7123
  else res.end(body);
6933
7124
  return true;
6934
7125
  }
6935
- //#endregion
6936
- //#region server/node-request-bridge.ts
7126
+ // server/node-request-bridge.ts
6937
7127
  function fetchUrlForNodeRequest(headers, pathname, scheme) {
6938
7128
  return `${scheme}://${headers.host ?? headers[":authority"]}${pathname}`;
6939
7129
  }
@@ -6955,8 +7145,7 @@ const CONNECTION_SPECIFIC_HEADERS = /* @__PURE__ */ new Set([
6955
7145
  function isResponseHeaderAllowed(name, httpVersionMajor) {
6956
7146
  return httpVersionMajor < 2 || !CONNECTION_SPECIFIC_HEADERS.has(name.toLowerCase());
6957
7147
  }
6958
- //#endregion
6959
- //#region server/vite-hono-plugin.ts
7148
+ // server/vite-hono-plugin.ts
6960
7149
  function honoMiddlewarePlugin(config) {
6961
7150
  let resolvedAuthToken = "";
6962
7151
  return {
@@ -6972,7 +7161,8 @@ function honoMiddlewarePlugin(config) {
6972
7161
  port: config.sandboxPort,
6973
7162
  host: config.sandboxHost,
6974
7163
  allowedOrigins: config.allowedOrigins,
6975
- publicUrl: config.sandboxPublicUrl
7164
+ publicUrl: config.sandboxPublicUrl,
7165
+ allowedHosts: config.allowedHosts
6976
7166
  });
6977
7167
  await sandboxController.start();
6978
7168
  const appOriginController = createAppOriginController({
@@ -7097,8 +7287,7 @@ function honoMiddlewarePlugin(config) {
7097
7287
  }
7098
7288
  };
7099
7289
  }
7100
- //#endregion
7101
- //#region server/vite-base-config.ts
7290
+ // server/vite-base-config.ts
7102
7291
  const NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE = [
7103
7292
  "@modelcontextprotocol/client/stdio",
7104
7293
  "atomically",
@@ -7241,8 +7430,7 @@ function vitestSharedPaths(clientDir) {
7241
7430
  }
7242
7431
  };
7243
7432
  }
7244
- //#endregion
7245
- //#region server/start-vite-dev-server.ts
7433
+ // server/start-vite-dev-server.ts
7246
7434
  const __dirname$2 = dirname(fileURLToPath(import.meta.url));
7247
7435
  async function startViteDevServer(config) {
7248
7436
  const root = resolve(join(__dirname$2, ".."));
@@ -7275,8 +7463,10 @@ async function startViteDevServer(config) {
7275
7463
  await server.close();
7276
7464
  } };
7277
7465
  }
7278
- //#endregion
7279
- //#region server/server.ts
7466
+ // server/server.ts
7467
+ function isApiPath(path) {
7468
+ return path === "/api" || path.startsWith("/api/");
7469
+ }
7280
7470
  const __filename$1 = fileURLToPath(import.meta.url);
7281
7471
  const __dirname$1 = dirname(__filename$1);
7282
7472
  async function startHonoServer(config) {
@@ -7284,7 +7474,8 @@ async function startHonoServer(config) {
7284
7474
  port: config.sandboxPort,
7285
7475
  host: config.sandboxHost,
7286
7476
  allowedOrigins: config.allowedOrigins,
7287
- publicUrl: config.sandboxPublicUrl
7477
+ publicUrl: config.sandboxPublicUrl,
7478
+ allowedHosts: config.allowedHosts
7288
7479
  });
7289
7480
  await sandboxController.start();
7290
7481
  const appOriginController = createAppOriginController({
@@ -7337,7 +7528,7 @@ async function startHonoServer(config) {
7337
7528
  });
7338
7529
  app.use("/*", serveStatic({ root: rootPath }));
7339
7530
  app.get("*", async (c) => {
7340
- if (c.req.path.startsWith("/api")) return c.notFound();
7531
+ if (isApiPath(c.req.path)) return c.notFound();
7341
7532
  try {
7342
7533
  return serveIndexHtml(c);
7343
7534
  } catch (error) {
@@ -7374,8 +7565,7 @@ async function startHonoServer(config) {
7374
7565
  });
7375
7566
  } };
7376
7567
  }
7377
- //#endregion
7378
- //#region server/ensure-web-build.ts
7568
+ // server/ensure-web-build.ts
7379
7569
  function runViteBuild(webRoot, log) {
7380
7570
  const result = spawnSync("npm", ["run", "build:client"], {
7381
7571
  cwd: webRoot,
@@ -7394,8 +7584,7 @@ function ensureWebBuild(webRoot, distRoot, deps = {}) {
7394
7584
  log("No production web build found at clients/web/dist. Building the web UI now (first run only; this can take a minute)...");
7395
7585
  if (build(webRoot, log) !== 0 || !exists(indexHtml)) throw new Error("Could not build the web UI automatically. Build it manually with `npm run build` (run from clients/web), or launch with `--dev` to use the Vite dev server.");
7396
7586
  }
7397
- //#endregion
7398
- //#region server/run-web.ts
7587
+ // server/run-web.ts
7399
7588
  const __dirname = dirname(fileURLToPath(import.meta.url));
7400
7589
  function ensureStdioCwd(config) {
7401
7590
  if ((config.type === "stdio" || config.type === void 0) && config.command && !config.cwd) return {
@@ -7404,14 +7593,6 @@ function ensureStdioCwd(config) {
7404
7593
  };
7405
7594
  return config;
7406
7595
  }
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
7596
  async function runWeb(argv) {
7416
7597
  const program = new Command();
7417
7598
  const argSeparatorIndex = argv.indexOf("--");
@@ -7487,7 +7668,7 @@ async function runWeb(argv) {
7487
7668
  headers: opts.header
7488
7669
  } : { ...config };
7489
7670
  if (opts.protocolEra !== void 0) entry.protocolEra = opts.protocolEra;
7490
- initialServers = { mcpServers: { [deriveSeedServerId(config)]: entry } };
7671
+ initialServers = { mcpServers: { [adHocServerName(config)]: entry } };
7491
7672
  writable = false;
7492
7673
  } else if (hasHeaders) {
7493
7674
  console.error("Error: --header requires an ad-hoc --server-url or command target.");
@@ -7532,12 +7713,10 @@ async function runWeb(argv) {
7532
7713
  process.on("SIGTERM", shutdown);
7533
7714
  return new Promise(() => {});
7534
7715
  }
7535
- //#endregion
7536
- //#region src/index.ts
7716
+ // src/index.ts
7537
7717
  const __filename = fileURLToPath(import.meta.url);
7538
7718
  if (process.argv[1] !== void 0 && resolve(process.argv[1]) === resolve(__filename)) runWeb(process.argv).then((code) => process.exit(code ?? 0)).catch((e) => {
7539
7719
  console.error(e);
7540
7720
  process.exit(1);
7541
7721
  });
7542
- //#endregion
7543
7722
  export { runWeb };