#!/usr/bin/env node
import "./chunk-MCKGQKYU.js";

// src/index.ts
import { resolve as resolve5 } from "path";
import { fileURLToPath as fileURLToPath7 } from "url";

// server/run-web.ts
import { resolve as resolve4, join as join9, dirname as dirname7 } from "path";
import { fileURLToPath as fileURLToPath6 } from "url";
import { Command } from "commander";

// ../../core/mcp/node/config.ts
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname as dirname2, resolve as resolve2 } from "path";

// ../../core/storage/store-io.ts
import * as path from "path";
import * as fs from "fs/promises";
import { readFile, writeFile } from "atomically";

// ../../core/storage/store-id.ts
function validateStoreId(storeId) {
  return /^[a-zA-Z0-9_-]+$/.test(storeId) && storeId.length > 0;
}

// ../../core/storage/store-serialize.ts
function serializeStore(data) {
  return JSON.stringify(data, null, 2);
}
function parseStore(raw) {
  return JSON.parse(raw);
}

// ../../core/storage/store-io.ts
function getDefaultStorageDir() {
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
  return path.join(homeDir, ".mcp-inspector", "storage");
}
function getDefaultMcpConfigPath() {
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
  return path.join(homeDir, ".mcp-inspector", "mcp.json");
}
function getStoreFilePath(storageDir, storeId) {
  return path.join(storageDir, `${storeId}.json`);
}
async function readStoreFile(filePath) {
  try {
    const data = await readFile(filePath, { encoding: "utf-8" });
    return data;
  } catch (error) {
    const err = error;
    if (err.code === "ENOENT") {
      return null;
    }
    throw error;
  }
}
var pendingWrites = /* @__PURE__ */ new Map();
async function writeStoreFile(filePath, data) {
  const key = path.resolve(filePath);
  const run = async () => {
    const dir = path.dirname(filePath);
    await fs.mkdir(dir, { recursive: true });
    await writeFile(filePath, data, {
      encoding: "utf-8",
      mode: 384
    });
  };
  const prior = pendingWrites.get(key);
  const tracked = (prior ?? Promise.resolve()).catch(() => {
  }).then(run);
  pendingWrites.set(key, tracked);
  try {
    await tracked;
  } finally {
    if (pendingWrites.get(key) === tracked) {
      pendingWrites.delete(key);
    }
  }
}
async function deleteStoreFile(filePath) {
  try {
    await fs.unlink(filePath);
  } catch (error) {
    const err = error;
    if (err.code !== "ENOENT") {
      throw error;
    }
  }
}

// ../../core/mcp/types.ts
var DEFAULT_TASK_TTL_MS = 6e4;
var DEFAULT_MAX_FETCH_REQUESTS = 1e3;
var DEFAULT_PROTOCOL_ERA = "legacy";
var DEFAULT_MODERN_LOG_LEVEL = "debug";
var MODERN_LOG_LEVELS = [
  "off",
  "debug",
  "info",
  "notice",
  "warning",
  "error",
  "critical",
  "alert",
  "emergency"
];
function isModernLogLevel(value) {
  return typeof value === "string" && MODERN_LOG_LEVELS.includes(value);
}

// ../../core/auth/secret-fields.ts
var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
var SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
var envSecretField = (envKey) => `env:${envKey}`;

// ../../core/json/jsonUtils.ts
function toRecord(value) {
  return value;
}

// ../../core/mcp/serverList.ts
var VALID_SERVER_TYPES = /* @__PURE__ */ new Set([
  "stdio",
  "sse",
  "streamable-http"
]);
var VALID_PROTOCOL_ERAS = /* @__PURE__ */ new Set([
  "legacy",
  "auto",
  "modern"
]);
function isProtocolEra(value) {
  return typeof value === "string" && VALID_PROTOCOL_ERAS.has(value);
}
function normalizeServerType(config) {
  const type = config.type;
  let normalizedType;
  if (typeof type !== "string") {
    normalizedType = "stdio";
  } else if (type === "http") {
    normalizedType = "streamable-http";
  } else if (VALID_SERVER_TYPES.has(type)) {
    normalizedType = type;
  } else {
    normalizedType = "stdio";
  }
  return { ...config, type: normalizedType };
}
function cleanRoots(roots) {
  if (!Array.isArray(roots)) {
    console.warn("Ignoring `roots`: expected an array, got", typeof roots);
    return [];
  }
  return roots.filter((r) => {
    if (typeof r?.uri !== "string") {
      console.warn("Dropping root without a string `uri`:", r);
      return false;
    }
    return r.uri.trim() !== "";
  }).map((r) => {
    const rawName = r.name;
    if (rawName !== void 0 && typeof rawName !== "string") {
      console.warn("Dropping non-string `name` on root:", r);
    }
    const trimmedName = typeof rawName === "string" ? rawName.trim() : void 0;
    const { name: _name, ...rest } = r;
    return trimmedName ? { ...rest, name: trimmedName } : rest;
  });
}
function envRecordToPairs(env) {
  return env ? Object.entries(env).map(([key, value]) => ({ key, value })) : [];
}
function envPairsToRecord(pairs) {
  const out = {};
  for (const { key, value } of pairs) {
    if (key.trim() === "") continue;
    out[key] = value;
  }
  return out;
}
function storedFieldsToInspectorSettings(stored) {
  const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
  if (!hasAny) return void 0;
  const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
  const settings = {
    headers: headersPairs,
    env: envRecordToPairs(stored.env),
    metadata: stored.metadata ?? [],
    connectionTimeout: stored.connectionTimeout ?? 0,
    requestTimeout: stored.requestTimeout ?? 0,
    // Unlike the timeouts (0 = "SDK default"), task TTL has a concrete product
    // default so the form shows it and "Run as task" has a value to send.
    taskTtl: stored.taskTtl ?? DEFAULT_TASK_TTL_MS,
    autoRefreshOnListChanged: stored.autoRefreshOnListChanged ?? false,
    paginatedLists: stored.paginatedLists ?? false,
    // Concrete default like taskTtl (not a 0-sentinel): the form needs a value
    // to render and the log state needs one to size its buffer. An absent
    // on-disk field reads back as the default, which the write side then omits.
    maxFetchRequests: stored.maxFetchRequests ?? DEFAULT_MAX_FETCH_REQUESTS,
    // Defaults to an empty list so the form always has a concrete array to
    // render controlled rows from. An absent on-disk `roots` reads back as
    // `[]`, which `inspectorSettingsToStoredFields` then omits on write.
    roots: stored.roots ?? []
  };
  if (isProtocolEra(stored.protocolEra)) {
    settings.protocolEra = stored.protocolEra;
  }
  if (isModernLogLevel(stored.modernLogLevel)) {
    settings.modernLogLevel = stored.modernLogLevel;
  }
  if (stored.advertisedExtensions && Object.keys(stored.advertisedExtensions).length > 0) {
    settings.advertisedExtensions = { ...stored.advertisedExtensions };
  }
  if (stored.oauth?.clientId) settings.oauthClientId = stored.oauth.clientId;
  if (stored.oauth?.clientSecret)
    settings.oauthClientSecret = stored.oauth.clientSecret;
  if (stored.oauth?.scopes) settings.oauthScopes = stored.oauth.scopes;
  if (stored.oauth?.onInsufficientScope) {
    settings.oauthOnInsufficientScope = stored.oauth.onInsufficientScope;
  }
  if (stored.oauth?.enterpriseManaged === true) {
    settings.enterpriseManaged = true;
  }
  if (stored.cwd) settings.cwd = stored.cwd;
  return settings;
}
function inspectorSettingsToStoredFields(settings) {
  const out = {};
  const headersRecord = {};
  for (const { key, value } of settings.headers) {
    if (key.trim() === "") continue;
    headersRecord[key] = value;
  }
  if (Object.keys(headersRecord).length > 0) {
    out.headers = headersRecord;
  }
  const metadataFiltered = settings.metadata.filter((m) => m.key.trim() !== "");
  if (metadataFiltered.length > 0) {
    out.metadata = metadataFiltered;
  }
  if (settings.connectionTimeout > 0) {
    out.connectionTimeout = settings.connectionTimeout;
  }
  if (settings.requestTimeout > 0) {
    out.requestTimeout = settings.requestTimeout;
  }
  if (settings.taskTtl > 0 && settings.taskTtl !== DEFAULT_TASK_TTL_MS) {
    out.taskTtl = settings.taskTtl;
  }
  if (settings.autoRefreshOnListChanged) {
    out.autoRefreshOnListChanged = true;
  }
  if (settings.paginatedLists) {
    out.paginatedLists = true;
  }
  if (settings.advertisedExtensions && Object.keys(settings.advertisedExtensions).length > 0) {
    out.advertisedExtensions = { ...settings.advertisedExtensions };
  }
  if (settings.protocolEra !== void 0 && settings.protocolEra !== DEFAULT_PROTOCOL_ERA) {
    out.protocolEra = settings.protocolEra;
  }
  if (settings.modernLogLevel !== void 0 && settings.modernLogLevel !== DEFAULT_MODERN_LOG_LEVEL) {
    out.modernLogLevel = settings.modernLogLevel;
  }
  if (settings.maxFetchRequests !== DEFAULT_MAX_FETCH_REQUESTS) {
    out.maxFetchRequests = settings.maxFetchRequests;
  }
  const oauthFields = {};
  if (settings.oauthClientId) oauthFields.clientId = settings.oauthClientId;
  if (settings.oauthClientSecret)
    oauthFields.clientSecret = settings.oauthClientSecret;
  if (settings.oauthScopes) oauthFields.scopes = settings.oauthScopes;
  if (settings.oauthOnInsufficientScope) {
    oauthFields.onInsufficientScope = settings.oauthOnInsufficientScope;
  }
  if (settings.enterpriseManaged === true) {
    oauthFields.enterpriseManaged = true;
  }
  if (Object.keys(oauthFields).length > 0) {
    out.oauth = oauthFields;
  }
  const rootsFiltered = cleanRoots(settings.roots);
  if (rootsFiltered.length > 0) {
    out.roots = rootsFiltered;
  }
  return out;
}
var INSPECTOR_FIELD_KEY_MAP = {
  headers: true,
  metadata: true,
  protocolEra: true,
  modernLogLevel: true,
  connectionTimeout: true,
  requestTimeout: true,
  taskTtl: true,
  autoRefreshOnListChanged: true,
  paginatedLists: true,
  advertisedExtensions: true,
  maxFetchRequests: true,
  oauth: true,
  roots: true
};
var INSPECTOR_FIELD_KEYS = new Set(
  Object.keys(INSPECTOR_FIELD_KEY_MAP)
);
function stripInspectorFields(stored) {
  const out = { ...stored };
  for (const key of INSPECTOR_FIELD_KEYS) {
    delete out[key];
  }
  return out;
}
var isStdioStored = (stored) => stored.type === "stdio" || stored.type === void 0;
function extractSecretsFromStored(stored) {
  const secrets = {};
  const stripped = { ...stored };
  if (stored.oauth?.clientSecret) {
    secrets[SECRET_FIELD_OAUTH_CLIENT_SECRET] = stored.oauth.clientSecret;
    const restOauth = {};
    if (stored.oauth.clientId !== void 0)
      restOauth.clientId = stored.oauth.clientId;
    if (stored.oauth.scopes !== void 0)
      restOauth.scopes = stored.oauth.scopes;
    if (stored.oauth.enterpriseManaged === true) {
      restOauth.enterpriseManaged = true;
    }
    if (Object.keys(restOauth).length > 0) {
      stripped.oauth = restOauth;
    } else {
      delete stripped.oauth;
    }
  }
  if (isStdioStored(stripped)) {
    const env = stripped.env;
    if (env) {
      const newEnv = {};
      for (const [k, v] of Object.entries(env)) {
        if (typeof v === "string" && v.length > 0) {
          secrets[envSecretField(k)] = v;
        }
        newEnv[k] = "";
      }
      stripped.env = newEnv;
    }
  }
  return { stripped, secrets };
}
function mergeSecretsIntoStored(stored, secrets) {
  const out = { ...stored };
  const oauthSecret = secrets[SECRET_FIELD_OAUTH_CLIENT_SECRET];
  if (oauthSecret) {
    out.oauth = { ...out.oauth ?? {}, clientSecret: oauthSecret };
  }
  if (isStdioStored(out) && out.env) {
    const newEnv = { ...out.env };
    let mutated = false;
    for (const k of Object.keys(out.env)) {
      const val = secrets[envSecretField(k)];
      if (val !== void 0) {
        newEnv[k] = val;
        mutated = true;
      }
    }
    if (mutated) {
      out.env = newEnv;
    }
  }
  return out;
}
function expectedSecretFields(stored) {
  const fields = [];
  fields.push(SECRET_FIELD_OAUTH_CLIENT_SECRET);
  if (isStdioStored(stored) && stored.env) {
    for (const k of Object.keys(stored.env)) {
      fields.push(envSecretField(k));
    }
  }
  return fields;
}
var DEFAULT_SEED_CONFIG = {
  mcpServers: {
    "filesystem-server-default": {
      type: "stdio",
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    },
    "everything-server-default": {
      type: "stdio",
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-everything"]
    }
  }
};

// ../../core/mcp/node/config.ts
function parseKeyValuePair(value, previous = {}) {
  const parts = value.split("=");
  const key = parts[0] ?? "";
  const val = parts.slice(1).join("=");
  if (!key || val === void 0 || val === "") {
    throw new Error(
      `Invalid parameter format: ${value}. Use key=value format.`
    );
  }
  return { ...previous, [key]: val };
}
function parseHeaderPair(value, previous = {}) {
  const colonIndex = value.indexOf(":");
  if (colonIndex === -1) {
    throw new Error(
      `Invalid header format: ${value}. Use "HeaderName: Value" format.`
    );
  }
  const key = value.slice(0, colonIndex).trim();
  const val = value.slice(colonIndex + 1).trim();
  if (key === "" || val === "") {
    throw new Error(
      `Invalid header format: ${value}. Use "HeaderName: Value" format.`
    );
  }
  return { ...previous, [key]: val };
}
var EMPTY_CATALOG_CONTENT = `${JSON.stringify({ mcpServers: {} }, null, 2)}
`;
function seedEmptyCatalog(resolvedPath) {
  mkdirSync(dirname2(resolvedPath), { recursive: true });
  writeFileSync(resolvedPath, EMPTY_CATALOG_CONTENT, { mode: 384 });
}
function readServerListContent(configPath, writable) {
  const resolvedPath = resolve2(process.cwd(), configPath);
  if (!existsSync(resolvedPath)) {
    if (writable) {
      seedEmptyCatalog(resolvedPath);
      return EMPTY_CATALOG_CONTENT;
    }
    throw new Error(`Config file not found: ${resolvedPath}`);
  }
  return readFileSync(resolvedPath, "utf-8");
}
function loadMcpServersConfig(configPath, writable) {
  try {
    const configContent = readServerListContent(configPath, writable);
    const config = JSON.parse(configContent);
    if (!config.mcpServers) {
      throw new Error("Configuration file must contain an mcpServers element");
    }
    const normalizedServers = {};
    for (const [name, raw] of Object.entries(config.mcpServers)) {
      normalizedServers[name] = normalizeServerType(toRecord(raw));
    }
    return { ...config, mcpServers: normalizedServers };
  } catch (error) {
    if (error instanceof Error) {
      throw new Error(`Error loading configuration: ${error.message}`, {
        cause: error
      });
    }
    throw new Error("Error loading configuration: Unknown error", {
      cause: error
    });
  }
}
function loadServerFromConfig(configPath, serverName, writable) {
  const config = loadMcpServersConfig(configPath, writable);
  if (!config.mcpServers[serverName]) {
    const available = Object.keys(config.mcpServers).join(", ");
    throw new Error(
      `Server '${serverName}' not found in config file. Available servers: ${available}`
    );
  }
  return config.mcpServers[serverName];
}
function buildConfigFromOptions(options) {
  const target = options.target ?? [];
  const first = target[0];
  const rest = target.slice(1);
  const urlFromTarget = first && (first.startsWith("http://") || first.startsWith("https://")) ? first : null;
  const url = urlFromTarget ?? options.serverUrl ?? null;
  if (url) {
    if (rest.length > 0 && urlFromTarget) {
      throw new Error("Arguments cannot be passed to a URL-based MCP server.");
    }
    let transportType;
    const t = options.transport === "http" ? "streamable-http" : options.transport;
    if (t === "sse" || t === "streamable-http") {
      transportType = t;
    } else {
      const u = new URL(url);
      if (u.pathname.endsWith("/mcp")) {
        transportType = "streamable-http";
      } else if (u.pathname.endsWith("/sse")) {
        transportType = "sse";
      } else {
        throw new Error(
          `Transport type not specified and could not be determined from URL: ${url}.`
        );
      }
    }
    if (transportType === "sse") {
      const config3 = { type: "sse", url };
      return config3;
    }
    const config2 = { type: "streamable-http", url };
    return config2;
  }
  if (target.length === 0 || !first) {
    throw new Error(
      "Target is required. Specify a URL or a command to execute."
    );
  }
  if (options.transport && options.transport !== "stdio") {
    throw new Error("Only stdio transport can be used with local commands.");
  }
  const config = { type: "stdio", command: first };
  if (rest.length > 0) config.args = rest;
  if (options.env && Object.keys(options.env).length > 0)
    config.env = options.env;
  if (options.cwd?.trim()) config.cwd = options.cwd.trim();
  return config;
}
function applyOverrides(config, overrides) {
  if (config.type === "stdio") {
    const c = { ...config };
    if (overrides.env && Object.keys(overrides.env).length > 0) {
      c.env = { ...c.env ?? {}, ...overrides.env };
    }
    if (overrides.cwd?.trim()) c.cwd = overrides.cwd.trim();
    return c;
  }
  return config;
}
function hasAdHocServerOptions(options) {
  return options.target != null && options.target.length > 0 || Boolean(options.transport) || Boolean(options.serverUrl?.trim());
}
function resolveServerSource(options) {
  if (options.catalogPath?.trim()) {
    return { path: options.catalogPath, writable: true };
  }
  if (options.configPath?.trim()) {
    return { path: options.configPath, writable: false };
  }
  return null;
}
function resolveServerConfigs(options, mode) {
  const source = resolveServerSource(options);
  const hasAdHoc = hasAdHocServerOptions(options);
  if (mode === "single") {
    if (source && options.serverName) {
      const config = loadServerFromConfig(
        source.path,
        options.serverName,
        source.writable
      );
      return [
        applyOverrides(config, {
          env: options.env,
          cwd: options.cwd
        })
      ];
    }
    if (source && !options.serverName) {
      const mcpConfig = loadMcpServersConfig(source.path, source.writable);
      const servers = Object.keys(mcpConfig.mcpServers);
      if (servers.length === 0)
        throw new Error("No servers found in config file");
      if (servers.length > 1) {
        throw new Error(
          `Multiple servers found in config file. Please specify one with --server. Available servers: ${servers.join(", ")}`
        );
      }
      const serverName = servers[0];
      if (!serverName) throw new Error("No servers found in config file");
      const config = loadServerFromConfig(
        source.path,
        serverName,
        source.writable
      );
      return [
        applyOverrides(config, {
          env: options.env,
          cwd: options.cwd
        })
      ];
    }
    return [buildConfigFromOptions(options)];
  }
  if (mode === "multi") {
    if (source && hasAdHoc) {
      throw new Error(
        "In multi-server mode with a config file, do not pass --transport, --server-url, or positional command/URL. Use only --config with optional -e, --cwd."
      );
    }
    if (source) {
      const mcpConfig = loadMcpServersConfig(source.path, source.writable);
      const configs = Object.values(mcpConfig.mcpServers).map(
        (c) => applyOverrides({ ...c }, {
          env: options.env,
          cwd: options.cwd
        })
      );
      return configs;
    }
    return [buildConfigFromOptions(options)];
  }
  return [];
}

// server/web-server-config.ts
import pino from "pino";

// ../../core/mcp/remote/constants.ts
var LEGACY_AUTH_TOKEN_ENV = "MCP_PROXY_AUTH_TOKEN";
var API_SERVER_ENV_VARS = {
  /**
   * Auth token for authenticating requests to the remote API server.
   * Used by the x-mcp-remote-auth header (or Authorization header if changed).
   */
  AUTH_TOKEN: "MCP_INSPECTOR_API_TOKEN"
};
var INSPECTOR_API_TOKEN_GLOBAL = "__INSPECTOR_API_TOKEN__";

// ../../core/node/version.ts
import { readFileSync as readFileSync2 } from "fs";
import { dirname as dirname3, join as join2, parse } from "path";
import { fileURLToPath } from "url";
var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
function readInspectorVersion(callerUrl) {
  const startDir = dirname3(fileURLToPath(callerUrl));
  const { root } = parse(startDir);
  let dir = startDir;
  for (; ; ) {
    let manifest;
    try {
      manifest = JSON.parse(
        readFileSync2(join2(dir, "package.json"), "utf-8")
      );
    } catch {
    }
    if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
      return manifest.version;
    }
    if (dir === root) break;
    dir = dirname3(dir);
  }
  throw new Error(
    `Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
  );
}
function readInspectorVersionSafe(callerUrl) {
  try {
    return readInspectorVersion(callerUrl);
  } catch {
    return void 0;
  }
}

// server/sandbox-controller.ts
import { createServer } from "http";
import { readFileSync as readFileSync3 } from "fs";
import { dirname as dirname4, join as join3 } from "path";
import { fileURLToPath as fileURLToPath2 } from "url";

// ../../core/node/hostUrl.ts
import { isIPv6 } from "net";
function stripBrackets(host) {
  return host.replace(/^\[(.*)\]$/, "$1");
}
function formatHostForUrl(host) {
  const h = host.trim();
  const bare = stripBrackets(h).split("%")[0];
  return isIPv6(bare) ? `[${bare}]` : h;
}
function unmapIpv4MappedHost(host) {
  const m = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(
    stripBrackets(host)
  );
  if (!m) return host;
  const hi = parseInt(m[1], 16);
  const lo = parseInt(m[2], 16);
  return `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;
}
function canonicalUrlHost(host) {
  const formatted = formatHostForUrl(host.trim().toLowerCase());
  try {
    return unmapIpv4MappedHost(new URL(`http://${formatted}`).hostname);
  } catch {
    return formatted;
  }
}
var ALL_INTERFACES_LITERALS = /* @__PURE__ */ new Set(["", "0.0.0.0", "::", "::ffff:0:0"]);
function canonicalizeIpv6(value) {
  if (!isIPv6(value)) return value;
  const [address] = value.split("%");
  return new URL(`http://[${address}]`).hostname.slice(1, -1);
}
function parseAddressPart(part) {
  if (/^0x[0-9a-f]+$/.test(part)) return parseInt(part, 16);
  if (/^[0-9]+$/.test(part)) return parseInt(part, 10);
  return NaN;
}
function isAllZeroIpv4(value) {
  const parts = value.split(".");
  if (parts.length > 4) return false;
  return parts.every((part) => parseAddressPart(part) === 0);
}
function isAllInterfacesHost(host) {
  const normalized = canonicalizeIpv6(stripBrackets(canonicalUrlHost(host)));
  return ALL_INTERFACES_LITERALS.has(normalized) || isAllZeroIpv4(normalized);
}

// server/sandbox-controller.ts
var __dirname = dirname4(fileURLToPath2(import.meta.url));
var LOOPBACK_FRAME_ANCESTORS = ["http://127.0.0.1:*", "http://localhost:*"];
var CSP_HOST_SOURCE = /^[a-z][a-z0-9+.-]*:\/\/[^\s;,'"[\]*]+$/i;
function sandboxFrameAncestors(allowedOrigins) {
  const valid = (allowedOrigins ?? []).filter((o) => CSP_HOST_SOURCE.test(o));
  const sources = valid.length > 0 ? valid : LOOPBACK_FRAME_ANCESTORS;
  return `frame-ancestors ${sources.join(" ")}`;
}
function parseListenPort(raw) {
  const v = raw?.trim();
  if (!v || !/^\d+$/.test(v)) return void 0;
  const n = parseInt(v, 10);
  return n <= 65535 ? n : void 0;
}
function resolveSandboxPort() {
  const fromSandbox = parseListenPort(process.env.MCP_SANDBOX_PORT);
  if (fromSandbox === void 0 && process.env.MCP_SANDBOX_PORT?.trim()) {
    console.warn(
      `Ignoring invalid MCP_SANDBOX_PORT="${process.env.MCP_SANDBOX_PORT}" (need an integer 0\u201365535); falling back.`
    );
  }
  return fromSandbox ?? parseListenPort(process.env.SERVER_PORT) ?? 0;
}
function createSandboxController(options) {
  const { port, host = "localhost", allowedOrigins } = options;
  let server = null;
  let sandboxUrl = null;
  const SANDBOX_PROXY_CSP = sandboxFrameAncestors(allowedOrigins);
  let sandboxHtml;
  try {
    const sandboxHtmlPath = join3(__dirname, "../static/sandbox_proxy.html");
    sandboxHtml = readFileSync3(sandboxHtmlPath, "utf-8");
  } catch (e) {
    sandboxHtml = "<!DOCTYPE html><html><body>Sandbox not loaded: " + String(e.message) + "</body></html>";
  }
  return {
    async start() {
      if (server && sandboxUrl) {
        const p = parseInt(new URL(sandboxUrl).port, 10);
        return { port: p, url: sandboxUrl };
      }
      return new Promise((resolve6) => {
        let settled = false;
        const settle = (value) => {
          if (settled) return;
          settled = true;
          resolve6(value);
        };
        server = createServer((req, res) => {
          if (req.method !== "GET" || req.url !== "/sandbox" && req.url !== "/sandbox/") {
            res.writeHead(404, { "Content-Type": "text/plain" });
            res.end("Not Found");
            return;
          }
          res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8",
            "Cache-Control": "no-store, no-cache, must-revalidate",
            Pragma: "no-cache",
            "Content-Security-Policy": SANDBOX_PROXY_CSP
          });
          res.end(sandboxHtml);
        });
        server.on("error", (err) => {
          if (err.code === "EADDRINUSE") {
            console.error(
              `Sandbox: port ${port || "dynamic"} in use. MCP Apps tab may not work.`
            );
          } else {
            console.error("Sandbox server error:", err);
          }
          server = null;
          settle({ port: 0, url: "" });
        });
        server.listen(port, host, () => {
          const addr = server.address();
          const actualPort = typeof addr === "object" && addr !== null && "port" in addr ? addr.port : (
            /* v8 ignore next -- unreachable for a TCP listen: `address()`
               always returns an `AddressInfo` object with `port`. The
               string form only occurs for unix-socket/pipe listens, which
               this controller never performs. */
            addr
          );
          const canonicalHost = canonicalUrlHost(host);
          const urlHost = isAllInterfacesHost(canonicalHost) ? "localhost" : canonicalHost;
          sandboxUrl = `http://${urlHost}:${actualPort}/sandbox`;
          settle({ port: actualPort, url: sandboxUrl });
        });
      });
    },
    async close() {
      if (!server) return;
      return new Promise((resolve6) => {
        server.close(() => {
          server = null;
          sandboxUrl = null;
          resolve6();
        });
      });
    },
    getUrl() {
      return sandboxUrl;
    }
  };
}

// server/resolve-bind-host.ts
var BIND_ALL_INTERFACES_ENV = "DANGEROUSLY_BIND_ALL_INTERFACES";
function isEnabled(value) {
  const v = value?.trim().toLowerCase();
  return v === "true" || v === "1";
}
function resolveBindHostname(env = process.env) {
  const host = (env.HOST ?? "localhost").trim();
  if (isAllInterfacesHost(host) && !isEnabled(env[BIND_ALL_INTERFACES_ENV])) {
    const resolved = canonicalUrlHost(host);
    const shown = stripBrackets(resolved) === stripBrackets(host.toLowerCase()) ? `HOST="${host}"` : `HOST="${host}" (resolves to ${resolved})`;
    throw new Error(
      `Refusing to bind ${shown}: this exposes the MCP Inspector to your entire network, and its backend can spawn local processes and connect to MCP servers on your behalf \u2014 the exposure DNS-rebinding attacks target. Bind a loopback host (localhost / 127.0.0.1) instead. To override \u2014 only inside an isolated container or trusted network \u2014 set ${BIND_ALL_INTERFACES_ENV}=true.`
    );
  }
  return stripBrackets(host);
}

// server/web-server-config.ts
var inspectorVersion = readInspectorVersionSafe(import.meta.url);
function defaultEnvironmentFromProcess(extra) {
  const keys = process.platform === "win32" ? [
    "APPDATA",
    "HOMEDRIVE",
    "HOMEPATH",
    "LOCALAPPDATA",
    "PATH",
    "PROCESSOR_ARCHITECTURE",
    "SYSTEMDRIVE",
    "SYSTEMROOT",
    "TEMP",
    "USERNAME",
    "USERPROFILE",
    "PROGRAMFILES"
  ] : ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"];
  const out = {};
  for (const key of keys) {
    const value = process.env[key];
    if (value && !value.startsWith("()")) {
      out[key] = value;
    }
  }
  if (extra) {
    Object.assign(out, extra);
  }
  return out;
}
function webServerConfigToInitialPayload(config) {
  return { ...transportDefaults(config), version: inspectorVersion };
}
function transportDefaults(config) {
  const mc = config.initialMcpConfig;
  const defaultEnvironment = defaultEnvironmentFromProcess(
    mc && "env" in mc && mc.env ? mc.env : void 0
  );
  if (!mc) {
    return { defaultEnvironment };
  }
  if (mc.type === "stdio" || mc.type === void 0) {
    return {
      defaultCommand: mc.command,
      defaultArgs: mc.args ?? [],
      defaultTransport: "stdio",
      defaultCwd: mc.cwd,
      defaultEnvironment
    };
  }
  if (mc.type === "sse") {
    return {
      defaultTransport: "sse",
      defaultServerUrl: mc.url,
      defaultEnvironment
    };
  }
  if (mc.type === "streamable-http") {
    return {
      defaultTransport: "streamable-http",
      defaultServerUrl: mc.url,
      defaultEnvironment
    };
  }
  const c = mc;
  return {
    defaultTransport: "streamable-http",
    defaultServerUrl: c.url,
    defaultEnvironment
  };
}
function printServerBanner(config, actualPort, resolvedToken, sandboxUrl) {
  const h = canonicalUrlHost(config.hostname);
  const bannerHost = isAllInterfacesHost(h) ? "localhost" : h;
  const baseUrl = httpOrigin(bannerHost, actualPort);
  const url = config.dangerouslyOmitAuth || !resolvedToken ? baseUrl : `${baseUrl}?${API_SERVER_ENV_VARS.AUTH_TOKEN}=${resolvedToken}`;
  console.log(`
MCP Inspector Web is up and running at:
   ${url}
`);
  if (sandboxUrl) {
    console.log(`   Sandbox (MCP Apps): ${sandboxUrl}
`);
  }
  if (config.dangerouslyOmitAuth) {
    console.log("   Auth: disabled (DANGEROUSLY_OMIT_AUTH)\n");
  } else {
    console.log(`   Auth token: ${resolvedToken}
`);
  }
  if (config.autoOpen) {
    console.log("Opening browser...");
  }
  return url;
}
var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
function httpOrigin(host, port) {
  return port === 80 ? `http://${host}` : `http://${host}:${port}`;
}
function loopbackOrigins(port) {
  return [
    httpOrigin("localhost", port),
    httpOrigin("127.0.0.1", port),
    httpOrigin("[::1]", port)
  ];
}
function defaultAllowedOrigins(hostname, port) {
  const h = canonicalUrlHost(hostname);
  if (LOOPBACK_HOSTNAMES.has(h)) {
    return loopbackOrigins(port);
  }
  if (isAllInterfacesHost(h)) {
    return [
      ...loopbackOrigins(port),
      httpOrigin("0.0.0.0", port),
      httpOrigin("[::]", port)
    ];
  }
  return [httpOrigin(h, port)];
}
function buildWebServerConfig(options = {}) {
  const {
    initialMcpConfig = null,
    mcpConfigPath,
    writable = true,
    initialServers = null
  } = options;
  const rawPort = process.env.CLIENT_PORT?.trim() || "6274";
  const port = parseInt(rawPort, 10);
  if (!/^\d+$/.test(rawPort) || port < 1 || port > 65535) {
    throw new Error(
      `Invalid CLIENT_PORT="${process.env.CLIENT_PORT}": the web server needs a fixed port in 1\u201365535 (0 / dynamic is unsupported \u2014 the origin allow-list and sandbox CSP are derived from it).`
    );
  }
  const hostname = resolveBindHostname();
  const dangerouslyOmitAuth = !!process.env.DANGEROUSLY_OMIT_AUTH;
  const authToken = dangerouslyOmitAuth ? "" : process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] ?? process.env[LEGACY_AUTH_TOKEN_ENV] ?? "";
  const sandboxPort = resolveSandboxPort();
  let logger;
  if (process.env.MCP_LOG_FILE) {
    logger = pino(
      { level: "info" },
      pino.destination({
        dest: process.env.MCP_LOG_FILE,
        append: true,
        mkdir: true
      })
    );
  }
  const configuredOrigins = process.env.ALLOWED_ORIGINS?.split(",").map((o) => o.trim()).filter(Boolean).map((o) => {
    try {
      const { origin } = new URL(o);
      if (origin === "null") throw new Error("opaque origin");
      if (origin.includes("*")) throw new Error("wildcard origin");
      return origin;
    } catch {
      console.warn(`Ignoring invalid ALLOWED_ORIGINS entry: ${o}`);
      return null;
    }
  }).filter((o) => o !== null);
  return {
    port,
    hostname,
    authToken,
    dangerouslyOmitAuth,
    initialMcpConfig,
    mcpConfigPath,
    writable,
    initialServers,
    storageDir: process.env.MCP_STORAGE_DIR,
    allowedOrigins: configuredOrigins?.length ? configuredOrigins : defaultAllowedOrigins(hostname, port),
    sandboxPort,
    sandboxHost: hostname,
    logger,
    autoOpen: resolveAutoOpen()
  };
}
function resolveAutoOpen() {
  const flag = process.env.MCP_AUTO_OPEN_ENABLED;
  if (flag === "true") return true;
  if (flag === "false") return false;
  return !process.env.VITEST;
}

// server/start-vite-dev-server.ts
import { join as join6, dirname as dirname5, resolve as resolve3 } from "path";
import { fileURLToPath as fileURLToPath4 } from "url";
import { createServer as createServer2 } from "vite";
import react from "@vitejs/plugin-react";

// server/vite-hono-plugin.ts
import open from "open";

// ../../core/mcp/remote/node/server.ts
import { randomBytes, timingSafeEqual } from "crypto";
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
import { stat as fsStat } from "fs/promises";
import { homedir } from "os";
import { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { watch as chokidarWatch } from "chokidar";

// ../../core/mcp/config.ts
function getServerType(config) {
  if (!("type" in config) || config.type === void 0) {
    return "stdio";
  }
  const type = config.type;
  if (type === "stdio") {
    return "stdio";
  }
  if (type === "sse") {
    return "sse";
  }
  if (type === "streamable-http") {
    return "streamable-http";
  }
  throw new Error(
    `Invalid server type: ${type}. Valid types are: stdio, sse, streamable-http`
  );
}

// ../../core/mcp/node/transport.ts
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { SSEClientTransport } from "@modelcontextprotocol/client";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";

// ../../core/mcp/fetchTracking.ts
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
  "authorization",
  "cookie",
  "set-cookie",
  "proxy-authorization",
  "x-api-key",
  // The inspector backend's own bearer (createRemoteFetch stamps this on every
  // proxied request); same exposure as Authorization.
  "x-mcp-remote-auth"
]);
var REDACTED_HEADER_VALUE = "[REDACTED]";
var SENSITIVE_BODY_FIELDS = /* @__PURE__ */ new Set([
  "client_secret",
  "code",
  "refresh_token",
  "access_token",
  "id_token",
  "code_verifier",
  "client_assertion",
  "assertion",
  "password",
  "token"
]);
var REDACTED_VALUE = "[REDACTED]";
function isSensitiveField(name) {
  return SENSITIVE_BODY_FIELDS.has(name.toLowerCase());
}
function redactSensitiveHeaders(headers) {
  const out = {};
  for (const [key, value] of Object.entries(headers)) {
    out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? REDACTED_HEADER_VALUE : value;
  }
  return out;
}
function redactUrlQuery(url) {
  const queryStart = url.indexOf("?");
  if (queryStart === -1) return url;
  const base = url.slice(0, queryStart);
  const afterQuery = url.slice(queryStart + 1);
  const hashStart = afterQuery.indexOf("#");
  const query = hashStart === -1 ? afterQuery : afterQuery.slice(0, hashStart);
  const fragment = hashStart === -1 ? "" : afterQuery.slice(hashStart);
  try {
    const params = new URLSearchParams(query);
    let changed = false;
    for (const key of new Set(params.keys())) {
      if (isSensitiveField(key)) {
        changed = true;
        params.set(key, REDACTED_VALUE);
      }
    }
    if (!changed) return url;
    return `${base}?${params.toString()}${fragment}`;
  } catch {
    return url;
  }
}
function redactJsonValue(value) {
  if (Array.isArray(value)) {
    return value.map(redactJsonValue);
  }
  if (value !== null && typeof value === "object") {
    const out = {};
    for (const [key, val] of Object.entries(value)) {
      out[key] = isSensitiveField(key) && typeof val === "string" ? REDACTED_VALUE : redactJsonValue(val);
    }
    return out;
  }
  return value;
}
function redactBody(body, contentType) {
  if (!body) return body;
  const type = (contentType ?? "").toLowerCase();
  if (type.includes("application/x-www-form-urlencoded")) {
    try {
      const params = new URLSearchParams(body);
      let changed = false;
      for (const key of new Set(params.keys())) {
        if (isSensitiveField(key)) {
          changed = true;
          params.set(key, REDACTED_VALUE);
        }
      }
      return changed ? params.toString() : body;
    } catch {
      return body;
    }
  }
  try {
    const parsed = JSON.parse(body);
    if (parsed !== null && typeof parsed === "object") {
      return JSON.stringify(redactJsonValue(parsed));
    }
  } catch {
  }
  return body;
}
function findHeader(headers, name) {
  const target = name.toLowerCase();
  for (const [key, value] of Object.entries(headers)) {
    if (key.toLowerCase() === target) return value;
  }
  return void 0;
}
function isLongLivedStreamResponse(method, contentType) {
  if (method !== "GET") return false;
  if (!contentType) return false;
  return contentType.includes("text/event-stream") || contentType.includes("application/x-ndjson");
}
function createFetchTracker(baseFetch, callbacks) {
  return async (input, init) => {
    const startTime = Date.now();
    const timestamp = /* @__PURE__ */ new Date();
    const id = `${timestamp.getTime()}-${Math.random().toString(36).slice(2, 11)}`;
    const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
    const method = init?.method || "GET";
    const rawRequestHeaders = {};
    if (input instanceof Request) {
      input.headers.forEach((value, key) => {
        rawRequestHeaders[key] = value;
      });
    }
    if (init?.headers) {
      const headers = new Headers(init.headers);
      headers.forEach((value, key) => {
        rawRequestHeaders[key] = value;
      });
    }
    const requestHeaders = redactSensitiveHeaders(rawRequestHeaders);
    const requestContentType = findHeader(rawRequestHeaders, "content-type");
    const redactedUrl = redactUrlQuery(url);
    let requestBody;
    if (init?.body) {
      if (typeof init.body === "string") {
        requestBody = init.body;
      } else {
        try {
          requestBody = String(init.body);
        } catch {
          requestBody = void 0;
        }
      }
    } else if (input instanceof Request && input.body) {
      try {
        const cloned = input.clone();
        requestBody = await cloned.text();
      } catch {
        requestBody = void 0;
      }
    }
    const redactedRequestBody = redactBody(requestBody, requestContentType);
    let response;
    let error;
    try {
      response = await baseFetch(input, init);
    } catch (err) {
      error = err instanceof Error ? err.message : String(err);
      const entry2 = {
        id,
        timestamp,
        method,
        url: redactedUrl,
        requestHeaders,
        requestBody: redactedRequestBody,
        error,
        duration: Date.now() - startTime
      };
      callbacks.trackRequest?.(entry2);
      throw err;
    }
    const responseStatus = response.status;
    const responseStatusText = response.statusText;
    const rawResponseHeaders = {};
    response.headers.forEach((value, key) => {
      rawResponseHeaders[key] = value;
    });
    const responseHeaders = redactSensitiveHeaders(rawResponseHeaders);
    const isLongLivedStream = isLongLivedStreamResponse(
      method,
      response.headers.get("content-type")
    );
    const duration = Date.now() - startTime;
    const entry = {
      id,
      timestamp,
      method,
      url: redactedUrl,
      requestHeaders,
      requestBody: redactedRequestBody,
      responseStatus,
      responseStatusText,
      responseHeaders,
      responseBody: void 0,
      duration
    };
    callbacks.trackRequest?.(entry);
    if (!isLongLivedStream && response.body && !response.bodyUsed) {
      const responseContentType = response.headers.get("content-type");
      try {
        const cloned = response.clone();
        cloned.text().then((body) => {
          callbacks.updateResponseBody?.(
            id,
            redactBody(body, responseContentType) ?? body
          );
        }).catch(() => {
        });
      } catch {
      }
    }
    return response;
  };
}

// ../../core/auth/utils.ts
import { UnauthorizedError } from "@modelcontextprotocol/client";
import { ZodError } from "zod";

// ../../core/auth/challenge.ts
var EMA_STEP_UP_PENDING_URL = new URL("mcp-inspector:ema-step-up");
var AuthChallengeError = class extends Error {
  authChallenge;
  status;
  constructor(authChallenge, status, message) {
    super(message ?? `Auth challenge: ${authChallenge.reason}`);
    this.name = "AuthChallengeError";
    this.authChallenge = authChallenge;
    this.status = status;
  }
};
function parseWwwAuthenticateBearer(header) {
  const match = header.match(/Bearer\s+(.+)/i);
  if (!match) {
    return {};
  }
  const params = {};
  const paramRegex = /([\w-]+)(?:="([^"]*)"|=([^\s,]+))?/g;
  let m;
  while ((m = paramRegex.exec(match[1])) !== null) {
    const value = m[2] ?? m[3];
    if (value !== void 0) {
      params[m[1].toLowerCase()] = value;
    }
  }
  return {
    error: params.error,
    scope: params.scope,
    resourceMetadata: params.resource_metadata,
    errorDescription: params.error_description
  };
}
function parseScopeString(scope) {
  if (!scope?.trim()) {
    return [];
  }
  return scope.trim().split(/\s+/).filter(Boolean);
}
function reasonFromHttpResponse(status, bearer) {
  if (status === 403) {
    if (bearer.error === "insufficient_scope") {
      return "insufficient_scope";
    }
    return "unauthorized";
  }
  if (bearer.error === "insufficient_scope") {
    return "insufficient_scope";
  }
  if (bearer.error === "invalid_token") {
    return "invalid_token";
  }
  if (status === 401) {
    return "token_expired";
  }
  return "unauthorized";
}
function parseAuthChallengeFromResponse(response, context) {
  const status = response.status;
  if (status !== 401 && status !== 403) {
    return void 0;
  }
  const wwwAuthenticate = response.headers.get("WWW-Authenticate") ?? void 0;
  const bearer = wwwAuthenticate ? parseWwwAuthenticateBearer(wwwAuthenticate) : {};
  const requiredScopes = parseScopeString(bearer.scope);
  return {
    reason: reasonFromHttpResponse(status, bearer),
    ...requiredScopes.length > 0 ? { requiredScopes } : {},
    ...bearer.errorDescription ? { message: bearer.errorDescription } : {},
    ...context ? { context } : {},
    raw: {
      httpStatus: status,
      ...wwwAuthenticate ? { wwwAuthenticate } : {}
    }
  };
}

// ../../core/mcp/node/authChallengeFetch.ts
function createAuthChallengeInterceptFetch(baseFetch) {
  return async (input, init) => {
    const response = await baseFetch(input, init);
    if (response.status !== 401 && response.status !== 403) {
      return response;
    }
    const challenge = parseAuthChallengeFromResponse(response);
    if (!challenge) {
      return response;
    }
    await response.body?.cancel().catch(() => {
    });
    throw new AuthChallengeError(
      challenge,
      response.status,
      `MCP auth challenge (${response.status})`
    );
  };
}

// ../../core/mcp/node/transport.ts
var PROXY_ENV_VARS = [
  "HTTPS_PROXY",
  "https_proxy",
  "HTTP_PROXY",
  "http_proxy"
];
function readProxyEnv() {
  for (const name of PROXY_ENV_VARS) {
    const value = process.env[name];
    if (value && value.trim() !== "") return value;
  }
  return void 0;
}
function withProxyDispatcher(baseFetch) {
  if (readProxyEnv() === void 0) return baseFetch;
  let dispatcherPromise;
  const getDispatcher = () => {
    dispatcherPromise ??= import("./undici-HXPKCIY3.js").then(
      ({ EnvHttpProxyAgent }) => new EnvHttpProxyAgent(),
      (cause) => {
        throw new Error(
          "HTTPS_PROXY / HTTP_PROXY is set but the `undici` package is not available. Install it (it ships with the CLI client) or unset the proxy env var.",
          { cause }
        );
      }
    );
    return dispatcherPromise;
  };
  const proxiedFetch = async (input, init) => {
    const dispatcher = await getDispatcher();
    return baseFetch(input, { ...init, dispatcher });
  };
  return proxiedFetch;
}
function headersFromSettings(settings) {
  if (!settings || settings.headers.length === 0) return void 0;
  const out = {};
  for (const { key, value } of settings.headers) {
    if (key.trim() === "") continue;
    out[key] = value;
  }
  return Object.keys(out).length > 0 ? out : void 0;
}
function createTransportNode(config, options = {}) {
  const serverType = getServerType(config);
  const {
    fetchFn: optionsFetchFn,
    onStderr,
    pipeStderr = false,
    onFetchRequest,
    onFetchResponseBody,
    authProvider,
    settings,
    interceptAuthChallenges = false
  } = options;
  const baseFetch = withProxyDispatcher(optionsFetchFn ?? globalThis.fetch);
  const fetchWithOptionalAuthIntercept = interceptAuthChallenges ? createAuthChallengeInterceptFetch(baseFetch) : baseFetch;
  if (serverType === "stdio") {
    const stdioConfig = config;
    const transport = new StdioClientTransport({
      command: stdioConfig.command,
      args: stdioConfig.args || [],
      env: stdioConfig.env,
      cwd: stdioConfig.cwd,
      stderr: pipeStderr ? "pipe" : void 0
    });
    if (pipeStderr && transport.stderr && onStderr) {
      transport.stderr.on("data", (data) => {
        const logEntry = data.toString().trim();
        if (logEntry) {
          onStderr({
            timestamp: /* @__PURE__ */ new Date(),
            message: logEntry
          });
        }
      });
    }
    return { transport };
  } else if (serverType === "sse") {
    const sseConfig = config;
    const url = new URL(sseConfig.url);
    const sseFetch = sseConfig.eventSourceInit?.fetch || fetchWithOptionalAuthIntercept;
    const trackedFetch = onFetchRequest ? createFetchTracker(sseFetch, {
      trackRequest: onFetchRequest,
      updateResponseBody: onFetchResponseBody
    }) : sseFetch;
    const headers = headersFromSettings(settings);
    const eventSourceInit = {
      ...sseConfig.eventSourceInit,
      ...headers && { headers },
      fetch: trackedFetch
    };
    const requestInit = {
      ...sseConfig.requestInit,
      ...headers && { headers }
    };
    const postFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
      trackRequest: onFetchRequest,
      updateResponseBody: onFetchResponseBody
    }) : fetchWithOptionalAuthIntercept;
    const transport = new SSEClientTransport(url, {
      authProvider,
      eventSourceInit,
      requestInit,
      fetch: postFetch
    });
    return { transport };
  } else {
    const httpConfig = config;
    const url = new URL(httpConfig.url);
    const headers = headersFromSettings(settings);
    const requestInit = {
      ...httpConfig.requestInit,
      ...headers && { headers }
    };
    const transportFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
      trackRequest: onFetchRequest,
      updateResponseBody: onFetchResponseBody
    }) : fetchWithOptionalAuthIntercept;
    const transport = new StreamableHTTPClientTransport(url, {
      authProvider,
      requestInit,
      fetch: transportFetch,
      // SEP-2350: how the transport reacts to a `403 insufficient_scope`
      // challenge. Defaults to the SDK's `reauthorize` when unset.
      ...settings?.oauthOnInsufficientScope && {
        onInsufficientScope: settings.oauthOnInsufficientScope
      }
    });
    return { transport };
  }
}

// ../../core/json/xMcpHeader.ts
var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";

// ../../core/mcp/import/clientConfig.ts
function parseJsonObject(raw) {
  let data;
  try {
    data = JSON.parse(raw);
  } catch (err) {
    const detail = err instanceof Error ? err.message : String(err);
    throw new Error(`Invalid JSON: ${detail}`, { cause: err });
  }
  if (!data || typeof data !== "object" || Array.isArray(data)) {
    throw new Error("Expected a JSON object at the top level");
  }
  return data;
}
function normalizeServerMap(servers) {
  const out = {};
  for (const [id, raw] of Object.entries(servers)) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
    const normalized = normalizeServerType(
      raw
    );
    out[id] = normalized;
  }
  return out;
}
function parseMcpServersConfig(raw) {
  const data = parseJsonObject(raw);
  const servers = data.mcpServers;
  if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
    throw new Error("No 'mcpServers' object found in config file");
  }
  return {
    mcpServers: normalizeServerMap(servers)
  };
}
function parseVsCodeConfig(raw) {
  const data = parseJsonObject(raw);
  const servers = data.servers;
  if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
    throw new Error("No 'servers' object found in VS Code config file");
  }
  return {
    mcpServers: normalizeServerMap(servers)
  };
}

// ../../core/mcp/import/strategies.ts
function join4(...segments) {
  return segments.map(
    (s, i) => i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")
  ).filter((s) => s.length > 0).join("/");
}
function appData(homeDir) {
  return join4(homeDir, "AppData", "Roaming");
}
function appSupportDir(platform, homeDir, app) {
  if (platform === "darwin") {
    return join4(homeDir, "Library", "Application Support", app);
  }
  if (platform === "win32") {
    return join4(appData(homeDir), app);
  }
  return join4(homeDir, ".config", app);
}
var claudeDesktop = {
  id: "claude-desktop",
  label: "Claude Desktop",
  defaultPaths: (platform, homeDir) => [
    join4(
      appSupportDir(platform, homeDir, "Claude"),
      "claude_desktop_config.json"
    )
  ],
  parse: parseMcpServersConfig
};
var cursor = {
  id: "cursor",
  label: "Cursor",
  defaultPaths: (_platform, homeDir) => [join4(homeDir, ".cursor", "mcp.json")],
  parse: parseMcpServersConfig
};
var cline = {
  id: "cline",
  label: "Cline",
  defaultPaths: (platform, homeDir) => [
    // Cline (VS Code extension) global settings live under the editor's
    // globalStorage; the documented cross-platform fallback is ~/Documents.
    join4(
      appSupportDir(platform, homeDir, "Code"),
      "User",
      "globalStorage",
      "saoudrizwan.claude-dev",
      "settings",
      "cline_mcp_settings.json"
    ),
    join4(homeDir, "Documents", "Cline", "MCP", "cline_mcp_settings.json")
  ],
  parse: parseMcpServersConfig
};
var vscode = {
  id: "vscode",
  label: "VS Code",
  defaultPaths: (platform, homeDir) => [
    join4(appSupportDir(platform, homeDir, "Code"), "User", "mcp.json")
  ],
  parse: parseVsCodeConfig
};
var IMPORT_STRATEGY_LIST = [
  claudeDesktop,
  cursor,
  cline,
  vscode
];
var IMPORT_STRATEGIES = Object.fromEntries(IMPORT_STRATEGY_LIST.map((s) => [s.id, s]));
function getImportStrategy(id) {
  return IMPORT_STRATEGIES[id];
}

// ../../core/mcp/import/resolveSource.ts
function resolveImportSource(type, platform, homeDir, readFile2) {
  const strategy = getImportStrategy(type);
  if (!strategy) return null;
  const searched = strategy.defaultPaths(platform, homeDir);
  for (const path3 of searched) {
    let raw;
    try {
      raw = readFile2(path3);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return {
        type,
        found: true,
        path: path3,
        error: `Failed to read ${path3}: ${msg}`,
        searched
      };
    }
    if (raw === null) continue;
    try {
      const config = strategy.parse(raw);
      return { type, found: true, path: path3, config, searched };
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return { type, found: true, path: path3, error: msg, searched };
    }
  }
  return { type, found: false, searched };
}

// ../../core/mcp/remote/node/remote-session.ts
var RemoteSession = class _RemoteSession {
  sessionId;
  transport;
  eventQueue = [];
  eventConsumer = null;
  transportDead = false;
  transportError = null;
  activeSendCount = 0;
  /**
   * Suppress duplicate ambient SSE auth echoes after HTTP command-scoped delivery.
   * Stale transport `onerror` may arrive after the send completes.
   */
  authHttpEchoSuppressUntilMs = 0;
  static AUTH_HTTP_ECHO_SUPPRESS_MS = 3e4;
  requestWaits = /* @__PURE__ */ new Map();
  authProviderHandle = null;
  appliedProtocolVersion;
  /** A protocol version is a dated token (`2025-11-25`, `2026-07-28`, …). */
  static PROTOCOL_VERSION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
  constructor(sessionId) {
    this.sessionId = sessionId;
  }
  setAuthProviderHandle(handle) {
    this.authProviderHandle = handle;
  }
  setAuthState(authState) {
    if (!this.authProviderHandle) {
      throw new Error("Session has no OAuth auth provider");
    }
    this.authProviderHandle.setAuthState(authState);
  }
  setTransport(transport) {
    this.transport = transport;
  }
  /**
   * Apply the client's negotiated protocol version to the upstream transport
   * (#1935). The browser's SDK Client owns the `initialize` handshake, so the
   * `setProtocolVersion` call it makes lands on the *remote* transport; without
   * this the backend's real HTTP transport never learns the version and drops
   * `Mcp-Protocol-Version` from every subsequent request.
   *
   * Ignores anything that isn't a plausible version token so a client can't
   * push arbitrary bytes into an upstream header, and re-applies only on
   * change (the value rides every `/api/mcp/send`). The value arrives from an
   * unvalidated JSON body, so the type is checked rather than assumed —
   * `RegExp.test` would otherwise coerce a `123` or `true` into a "matching"
   * string and hand the wrong type to the transport.
   */
  applyProtocolVersion(version) {
    if (typeof version !== "string" || version === this.appliedProtocolVersion || !_RemoteSession.PROTOCOL_VERSION_PATTERN.test(version)) {
      return;
    }
    this.appliedProtocolVersion = version;
    this.transport.setProtocolVersion?.(version);
  }
  setEventConsumer(consumer) {
    this.eventConsumer = consumer;
    while (this.eventQueue.length > 0) {
      const ev = this.eventQueue.shift();
      consumer(ev);
    }
  }
  clearEventConsumer() {
    this.eventConsumer = null;
    return this.transportDead;
  }
  markTransportDead(error) {
    this.transportDead = true;
    this.transportError = error;
    this.pushEvent({
      type: "transport_error",
      data: {
        error,
        code: -32e3
        // MCP error code for connection closed
      }
    });
  }
  isTransportDead() {
    return this.transportDead;
  }
  getTransportError() {
    return this.transportError;
  }
  hasEventConsumer() {
    return this.eventConsumer !== null;
  }
  beginSend() {
    this.activeSendCount++;
  }
  endSend() {
    if (this.activeSendCount > 0) {
      this.activeSendCount--;
    }
  }
  /** Command-scoped auth was returned on HTTP; suppress async onerror echo on SSE. */
  noteAuthChallengeDeliveredViaHttp() {
    this.extendAuthHttpEchoSuppression();
  }
  extendAuthHttpEchoSuppression() {
    this.authHttpEchoSuppressUntilMs = Date.now() + _RemoteSession.AUTH_HTTP_ECHO_SUPPRESS_MS;
  }
  shouldSuppressAuthEcho() {
    return Date.now() < this.authHttpEchoSuppressUntilMs;
  }
  hasActiveSend() {
    return this.activeSendCount > 0;
  }
  pushAuthChallenge(challenge) {
    this.pushEvent({ type: "auth_challenge", data: challenge });
  }
  /**
   * Streamable HTTP `transport.send()` can return before the JSON-RPC response
   * arrives. The remote send handler awaits this so `/api/mcp/send` returns
   * `auth_challenge` or `ok: true` only after the MCP round-trip (or auth error).
   */
  waitForRequestResponse(requestId, timeoutMs = 6e4) {
    return new Promise((resolve6, reject) => {
      const timer = timeoutMs > 0 ? setTimeout(() => {
        this.requestWaits.delete(requestId);
        reject(
          new Error(
            `MCP request ${String(requestId)} timed out after ${timeoutMs}ms`
          )
        );
      }, timeoutMs) : void 0;
      this.requestWaits.set(requestId, {
        resolve: () => {
          if (timer !== void 0) {
            clearTimeout(timer);
          }
          resolve6();
        },
        reject: (error) => {
          if (timer !== void 0) {
            clearTimeout(timer);
          }
          reject(error);
        }
      });
    });
  }
  cancelRequestWait(requestId) {
    const wait = this.requestWaits.get(requestId);
    if (!wait) {
      return;
    }
    this.requestWaits.delete(requestId);
    wait.reject(new Error("MCP request wait cancelled"));
  }
  rejectActiveRequestWaits(error) {
    for (const wait of this.requestWaits.values()) {
      wait.reject(error);
    }
    this.requestWaits.clear();
  }
  /**
   * Auth errors from the MCP transport.
   * - During an active send: command path owns delivery (HTTP); never SSE.
   * - After HTTP already returned auth_challenge: swallow async onerror echo.
   * - Otherwise: ambient SSE (idle session).
   */
  handleTransportAuthError(error) {
    if (!(error instanceof AuthChallengeError)) {
      return false;
    }
    this.rejectActiveRequestWaits(error);
    if (this.hasActiveSend()) {
      this.extendAuthHttpEchoSuppression();
      return true;
    }
    if (this.shouldSuppressAuthEcho()) {
      return true;
    }
    this.pushAuthChallenge(error.authChallenge);
    return true;
  }
  settleRequestWait(requestId) {
    const wait = this.requestWaits.get(requestId);
    if (!wait) {
      return;
    }
    this.requestWaits.delete(requestId);
    wait.resolve();
  }
  pushEvent(event) {
    if (this.eventConsumer) {
      this.eventConsumer(event);
    } else {
      this.eventQueue.push(event);
    }
  }
  onMessage(message) {
    if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) {
      this.settleRequestWait(message.id);
    }
    this.pushEvent({ type: "message", data: message });
  }
  onFetchRequest(entry) {
    this.pushEvent({
      type: "fetch_request",
      data: {
        ...entry,
        timestamp: entry.timestamp instanceof Date ? entry.timestamp.toISOString() : entry.timestamp
      }
    });
  }
  onFetchResponseBody(id, responseBody) {
    this.pushEvent({
      type: "fetch_request_body_update",
      data: { id, responseBody }
    });
  }
  onStderr(entry) {
    this.pushEvent({
      type: "stdio_log",
      data: {
        timestamp: entry.timestamp.toISOString(),
        message: entry.message
      }
    });
  }
};

// ../../core/mcp/remote/node/tokenAuthProvider.ts
var REMOTE_OAUTH_STUB_METADATA = {
  redirect_uris: [],
  scope: ""
};
function cloneAuthState(state) {
  return {
    ...state.oauthTokens && {
      oauthTokens: { ...state.oauthTokens }
    },
    ...state.oauthClient && {
      oauthClient: { ...state.oauthClient }
    }
  };
}
function createRemoteAuthProvider(initialAuthState) {
  if (!initialAuthState?.oauthTokens && !initialAuthState?.oauthClient) {
    return void 0;
  }
  let authState = cloneAuthState(initialAuthState);
  const provider = {
    get clientMetadata() {
      return REMOTE_OAUTH_STUB_METADATA;
    },
    get redirectUrl() {
      return "";
    },
    async tokens() {
      return authState.oauthTokens;
    },
    async clientInformation() {
      if (!authState.oauthClient?.client_id) {
        return void 0;
      }
      return {
        client_id: authState.oauthClient.client_id,
        ...authState.oauthClient.client_secret && {
          client_secret: authState.oauthClient.client_secret
        }
      };
    },
    async saveTokens(tokens) {
      authState = {
        ...authState,
        oauthTokens: {
          access_token: tokens.access_token,
          token_type: tokens.token_type,
          expires_in: tokens.expires_in,
          refresh_token: tokens.refresh_token,
          scope: tokens.scope,
          id_token: tokens.id_token
        }
      };
    },
    codeVerifier() {
      return void 0;
    },
    async saveCodeVerifier() {
    },
    clear() {
      authState = {};
    },
    async redirectToAuthorization() {
      throw new Error(
        "OAuth re-authorization must be performed in the Inspector web client (remote server cannot complete OAuth flows)"
      );
    },
    state() {
      return "";
    }
    // This provider implements only the members the remote backend transport
    // exercises (token get/save, client info, non-interactive redirect); the
    // interactive-only members the SDK never calls on the server side are stubbed
    // with narrower shapes (e.g. `codeVerifier()` returns `undefined`, not the
    // interface's `string | Promise<string>`), so it can't be assigned to the
    // full `OAuthClientProvider` without the double cast.
  };
  return {
    provider,
    setAuthState(next) {
      authState = cloneAuthState(next);
    },
    getAuthState() {
      return cloneAuthState(authState);
    }
  };
}

// ../../core/auth/node/secret-store.ts
var SERVICE_NAME = "mcp-inspector";
var keyringLoad;
var KeyringModuleShapeError = class extends Error {
  constructor(detail, options) {
    super(`@napi-rs/keyring loaded but ${detail}`, options);
    this.name = "KeyringModuleShapeError";
  }
};
var checkKeyringShape = (mod) => {
  try {
    if (typeof mod.AsyncEntry === "function" && typeof mod.findCredentialsAsync === "function") {
      return { ok: true, mod };
    }
    return {
      ok: false,
      err: new KeyringModuleShapeError(
        "did not expose AsyncEntry / findCredentialsAsync"
      )
    };
  } catch (err) {
    return {
      ok: false,
      err: new KeyringModuleShapeError(
        `its exports could not be read: ${err instanceof Error ? err.message : String(err)}`,
        { cause: err }
      )
    };
  }
};
var loadKeyring = () => {
  keyringLoad ??= import("@napi-rs/keyring").then(
    checkKeyringShape,
    (err) => ({ ok: false, err })
  );
  return keyringLoad;
};
function parseAccount(account) {
  const idx = account.indexOf(":");
  if (idx <= 0 || idx === account.length - 1) return null;
  return {
    serverId: account.slice(0, idx),
    field: account.slice(idx + 1)
  };
}
var buildAccount = (serverId, field) => `${serverId}:${field}`;
var KeychainUnavailableError = class extends Error {
  constructor(cause) {
    const message = cause instanceof Error ? cause.message : String(cause);
    super(
      `OS keychain is not available. ${hintFor(cause, message)} Underlying error: ${message}`
    );
    this.name = "KeychainUnavailableError";
  }
};
var hintFor = (cause, message) => {
  if (cause instanceof KeyringModuleShapeError) {
    return `The @napi-rs/keyring package loaded but does not expose the API this build expects \u2014 most likely a version or packaging mismatch; reinstall the Inspector, and report this if it persists.`;
  }
  if (message.includes("Cannot find native binding")) {
    return `The @napi-rs/keyring platform package for this OS is missing or unavailable \u2014 reinstall the Inspector (for npx, clear the npx cache under your npm cache directory first).`;
  }
  return `On Linux, install libsecret / gnome-keyring.`;
};
var KeyringSecretStore = class {
  async get(serverId, field) {
    try {
      const keyring = await loadKeyring();
      if (!keyring.ok) return null;
      const entry = new keyring.mod.AsyncEntry(
        SERVICE_NAME,
        buildAccount(serverId, field)
      );
      const v = await entry.getPassword();
      return v ?? null;
    } catch {
      return null;
    }
  }
  async set(serverId, field, value) {
    try {
      const keyring = await loadKeyring();
      if (!keyring.ok) throw new KeychainUnavailableError(keyring.err);
      const entry = new keyring.mod.AsyncEntry(
        SERVICE_NAME,
        buildAccount(serverId, field)
      );
      await entry.setPassword(value);
    } catch (err) {
      if (err instanceof KeychainUnavailableError) throw err;
      throw new KeychainUnavailableError(err);
    }
  }
  async delete(serverId, field) {
    try {
      const keyring = await loadKeyring();
      if (!keyring.ok) return;
      const entry = new keyring.mod.AsyncEntry(
        SERVICE_NAME,
        buildAccount(serverId, field)
      );
      await entry.deleteCredential();
    } catch {
    }
  }
  async deleteAllForServer(serverId) {
    let creds;
    try {
      const keyring = await loadKeyring();
      if (!keyring.ok) return;
      creds = await keyring.mod.findCredentialsAsync(SERVICE_NAME);
    } catch {
      return;
    }
    const prefix = `${serverId}:`;
    for (const c of creds) {
      if (!c.account.startsWith(prefix)) continue;
      const parsed = parseAccount(c.account);
      if (!parsed || parsed.serverId !== serverId) continue;
      await this.delete(serverId, parsed.field);
    }
  }
};

// ../../core/client/config-parse.ts
import { z } from "zod";
function isAbsoluteHttpUrl(value) {
  let url;
  try {
    url = new URL(value.trim());
  } catch {
    return false;
  }
  if (url.protocol !== "http:" && url.protocol !== "https:") return false;
  return isRealHost(url.hostname);
}
function isRealHost(hostname) {
  if (hostname === "localhost") return true;
  if (hostname.startsWith("[")) return hostname.endsWith("]");
  const labels = hostname.split(".");
  return labels.length >= 2 && labels.every((label) => label !== "");
}
var HttpUrlStringSchema = z.string().min(1).superRefine((val, ctx) => {
  const trimmed = val.trim();
  if (!isAbsoluteHttpUrl(trimmed)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
    });
  }
});
var CIMD_METADATA_URL_INVALID_ERROR = "Must be a valid URL, like https://example.com/oauth/client.json";
var CIMD_METADATA_URL_HTTPS_ERROR = "CIMD client metadata URL must use HTTPS";
var CIMD_METADATA_URL_PATH_ERROR = "Must include a path (not the site root), like https://example.com/oauth/client.json";
function getCimdClientMetadataUrlError(value) {
  const trimmed = value.trim();
  if (trimmed === "") return void 0;
  if (!isAbsoluteHttpUrl(trimmed)) {
    return CIMD_METADATA_URL_INVALID_ERROR;
  }
  try {
    const url = new URL(trimmed);
    if (url.protocol !== "https:") {
      return CIMD_METADATA_URL_HTTPS_ERROR;
    }
    if (url.pathname === "/" || url.pathname === "") {
      return CIMD_METADATA_URL_PATH_ERROR;
    }
  } catch {
    return CIMD_METADATA_URL_INVALID_ERROR;
  }
  return void 0;
}
function refineCimdMetadataUrl(val, ctx, required) {
  const trimmed = val.trim();
  if (trimmed === "") {
    if (required) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "CIMD client metadata URL is required when CIMD is enabled"
      });
    }
    return;
  }
  const error = getCimdClientMetadataUrlError(trimmed);
  if (error) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: error
    });
  }
}
var CimdConfigSchema = z.object({
  enabled: z.boolean(),
  clientMetadataUrl: z.string()
}).superRefine((data, ctx) => {
  refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
});
var EnterpriseManagedAuthIdpConfigSchema = z.object({
  issuer: HttpUrlStringSchema,
  clientId: z.string().min(1),
  clientSecret: z.string().optional()
});
var ClientConfigSchema = z.object({
  enterpriseManagedAuth: z.object({
    enabled: z.boolean().optional(),
    idp: EnterpriseManagedAuthIdpConfigSchema
  }).optional(),
  cimd: CimdConfigSchema.optional()
});
function parseClientConfig(raw) {
  return ClientConfigSchema.parse(raw);
}
function formatClientConfigLoadError(error) {
  if (error instanceof z.ZodError) {
    const issue = error.issues[0];
    if (issue) {
      const field = issue.path.length > 0 ? issue.path.map(String).join(".") : "config";
      return `${field}: ${issue.message}`;
    }
    return "Invalid client.json shape";
  }
  if (error instanceof Error) {
    return error.message;
  }
  return String(error);
}

// ../../core/client/secrets.ts
var CLIENT_KEYCHAIN_ID = "client";
function hasClientPlaintextSecret(config) {
  const secret = config.enterpriseManagedAuth?.idp?.clientSecret;
  return typeof secret === "string" && secret.length > 0;
}
function extractSecretsFromClientConfig(config) {
  const secrets = {};
  const ema = config.enterpriseManagedAuth;
  if (!ema?.idp?.clientSecret) {
    return { stripped: config, secrets };
  }
  const { clientSecret, ...idpRest } = ema.idp;
  secrets[SECRET_FIELD_IDP_CLIENT_SECRET] = clientSecret;
  return {
    stripped: {
      ...config,
      enterpriseManagedAuth: {
        ...ema,
        idp: idpRest
      }
    },
    secrets
  };
}
function mergeSecretsIntoClientConfig(config, secrets) {
  const idpSecret = secrets[SECRET_FIELD_IDP_CLIENT_SECRET];
  const ema = config.enterpriseManagedAuth;
  if (!idpSecret || !ema?.idp) {
    return config;
  }
  return {
    ...config,
    enterpriseManagedAuth: {
      ...ema,
      idp: {
        ...ema.idp,
        clientSecret: idpSecret
      }
    }
  };
}

// ../../core/client/node-persistence.ts
async function readIdpSecretFromKeychain(secretStore) {
  const secret = await secretStore.get(
    CLIENT_KEYCHAIN_ID,
    SECRET_FIELD_IDP_CLIENT_SECRET
  );
  if (!secret) return {};
  return { [SECRET_FIELD_IDP_CLIENT_SECRET]: secret };
}
async function migrateClientPlaintextSecret(filePath, config, secretStore) {
  const { stripped, secrets } = extractSecretsFromClientConfig(config);
  const value = secrets[SECRET_FIELD_IDP_CLIENT_SECRET];
  if (!value) return config;
  try {
    const existing = await secretStore.get(
      CLIENT_KEYCHAIN_ID,
      SECRET_FIELD_IDP_CLIENT_SECRET
    );
    if (existing === null) {
      await secretStore.set(
        CLIENT_KEYCHAIN_ID,
        SECRET_FIELD_IDP_CLIENT_SECRET,
        value
      );
    }
    await writeStoreFile(filePath, serializeStore(stripped));
    return stripped;
  } catch (err) {
    if (err instanceof KeychainUnavailableError) {
      return config;
    }
    throw err;
  }
}
async function readClientConfigStore(filePath, secretStore) {
  const raw = await readStoreFile(filePath);
  if (raw === null) {
    return {};
  }
  let config = parseClientConfig(parseStore(raw));
  if (hasClientPlaintextSecret(config)) {
    config = await migrateClientPlaintextSecret(filePath, config, secretStore);
  }
  const secrets = await readIdpSecretFromKeychain(secretStore);
  return mergeSecretsIntoClientConfig(config, secrets);
}
async function writeClientConfigStore(filePath, body, secretStore) {
  const validated = parseClientConfig(body);
  const { stripped, secrets } = extractSecretsFromClientConfig(validated);
  const idpSecret = secrets[SECRET_FIELD_IDP_CLIENT_SECRET];
  if (idpSecret) {
    await secretStore.set(
      CLIENT_KEYCHAIN_ID,
      SECRET_FIELD_IDP_CLIENT_SECRET,
      idpSecret
    );
  } else {
    await secretStore.delete(
      CLIENT_KEYCHAIN_ID,
      SECRET_FIELD_IDP_CLIENT_SECRET
    );
  }
  await writeStoreFile(filePath, serializeStore(stripped));
}
async function deleteClientConfigStore(filePath, secretStore) {
  await deleteStoreFile(filePath);
  await secretStore.delete(CLIENT_KEYCHAIN_ID, SECRET_FIELD_IDP_CLIENT_SECRET);
}

// ../../core/mcp/remote/node/server.ts
import { ZodError as ZodError2 } from "zod";
var SSE_PRIMING_COMMENT = ":\n\n";
function createOriginMiddleware(allowedOrigins) {
  return async (c, next) => {
    if (!allowedOrigins || allowedOrigins.length === 0) {
      await next();
      return;
    }
    const origin = c.req.header("origin");
    if (c.req.method === "OPTIONS") {
      if (origin && allowedOrigins.includes(origin)) {
        c.header("Access-Control-Allow-Origin", origin);
        c.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
        c.header(
          "Access-Control-Allow-Headers",
          "Content-Type, x-mcp-remote-auth"
        );
        c.header("Access-Control-Max-Age", "86400");
        return c.body(null, 204);
      }
      return c.json(
        {
          error: "Forbidden",
          message: "Invalid origin. Request blocked to prevent DNS rebinding attacks."
        },
        403
      );
    }
    if (origin) {
      if (!allowedOrigins.includes(origin)) {
        return c.json(
          {
            error: "Forbidden",
            message: "Invalid origin. Request blocked to prevent DNS rebinding attacks. Configure allowed origins via allowedOrigins option."
          },
          403
        );
      }
      c.header("Access-Control-Allow-Origin", origin);
    }
    await next();
  };
}
function createAuthMiddleware(authToken) {
  return async (c, next) => {
    const authHeader = c.req.header("x-mcp-remote-auth");
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
      return c.json(
        {
          error: "Unauthorized",
          message: "Authentication required. Use the x-mcp-remote-auth header with Bearer token."
        },
        401
      );
    }
    const providedToken = authHeader.substring(7);
    const expectedToken = authToken;
    const providedBuffer = Buffer.from(providedToken);
    const expectedBuffer = Buffer.from(expectedToken);
    if (providedBuffer.length !== expectedBuffer.length) {
      return c.json(
        {
          error: "Unauthorized",
          message: "Authentication required. Use the x-mcp-remote-auth header with Bearer token."
        },
        401
      );
    }
    if (!timingSafeEqual(providedBuffer, expectedBuffer)) {
      return c.json(
        {
          error: "Unauthorized",
          message: "Authentication required. Use the x-mcp-remote-auth header with Bearer token."
        },
        401
      );
    }
    await next();
  };
}
function forwardLogEvent(logger, logEvent) {
  const levelLabel = (logEvent?.level?.label ?? "info").toLowerCase();
  const method = toRecord(logger)[levelLabel];
  if (typeof method !== "function") return;
  const bindings = Object.assign(
    {},
    ...Array.isArray(logEvent.bindings) ? logEvent.bindings : []
  );
  const messages = Array.isArray(logEvent.messages) ? logEvent.messages : [];
  if (messages.length === 0) {
    method.call(logger, bindings);
    return;
  }
  const first = messages[0];
  if (typeof first === "object" && first !== null && !Array.isArray(first)) {
    const obj = { ...bindings, ...first };
    const msg = messages[1];
    const args = messages.slice(2);
    method.call(
      logger,
      obj,
      msg,
      ...args
    );
  } else {
    const msg = messages[0];
    const args = messages.slice(1);
    method.call(
      logger,
      bindings,
      msg,
      ...args
    );
  }
}
function requestIdForSendWait(message) {
  if ("method" in message && "id" in message && message.id !== null && message.id !== void 0) {
    if (message.method === "subscriptions/listen") {
      return void 0;
    }
    return message.id;
  }
  return void 0;
}
function mcpParamHeadersOnly(headers) {
  if (headers === null || typeof headers !== "object") return void 0;
  const out = {};
  for (const [key, value] of Object.entries(headers)) {
    if (typeof value === "string" && key.toLowerCase().startsWith(MCP_PARAM_HEADER_PREFIX.toLowerCase())) {
      out[key] = value;
    }
  }
  return Object.keys(out).length > 0 ? out : void 0;
}
function createRemoteApp(options) {
  const dangerouslyOmitAuth = !!options.dangerouslyOmitAuth;
  const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
  const app = new Hono();
  const sessions = /* @__PURE__ */ new Map();
  const { logger: fileLogger, allowedOrigins } = options;
  const storageDir = options.storageDir ?? getDefaultStorageDir();
  const mcpConfigPath = options.mcpConfigPath ?? getDefaultMcpConfigPath();
  const secretStore = options.secretStore ?? new KeyringSecretStore();
  const writable = options.writable ?? true;
  const inMemoryServers = options.initialServers;
  const watchable = !inMemoryServers;
  const serverEventSubscribers = /* @__PURE__ */ new Set();
  let mcpConfigWatcher = null;
  let lastWrittenMtimeMs = null;
  const broadcastServerListChange = () => {
    const payload = JSON.stringify({ type: "change" });
    for (const send of serverEventSubscribers) {
      try {
        send(payload);
      } catch {
      }
    }
  };
  const writeMcpAndTrackMtime = async (data) => {
    let externalEditDetected = false;
    if (lastWrittenMtimeMs !== null) {
      try {
        const s = await fsStat(mcpConfigPath);
        if (s.mtimeMs !== lastWrittenMtimeMs) {
          externalEditDetected = true;
        }
      } catch {
        externalEditDetected = true;
      }
    }
    await writeStoreFile(mcpConfigPath, data);
    try {
      const s = await fsStat(mcpConfigPath);
      lastWrittenMtimeMs = s.mtimeMs;
    } catch {
    }
    if (externalEditDetected) {
      broadcastServerListChange();
    }
  };
  const handleWatcherEvent = async (event) => {
    if (event !== "add" && event !== "change" && event !== "unlink") return;
    if (event !== "unlink") {
      try {
        const s = await fsStat(mcpConfigPath);
        if (lastWrittenMtimeMs !== null && s.mtimeMs === lastWrittenMtimeMs) {
          return;
        }
      } catch {
      }
    }
    broadcastServerListChange();
  };
  const handleWatcherError = (err) => {
    const msg = err instanceof Error ? err.message : String(err);
    if (fileLogger) {
      fileLogger.warn({ err: msg }, "mcp.json watcher error");
    } else {
      console.warn("[mcp.json watcher]", msg);
    }
  };
  const ensureWatcher = () => {
    if (mcpConfigWatcher) return;
    mcpConfigWatcher = chokidarWatch(mcpConfigPath, {
      ignoreInitial: true,
      awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
    });
    mcpConfigWatcher.on("all", (event) => {
      void handleWatcherEvent(event);
    });
    mcpConfigWatcher.on("error", handleWatcherError);
  };
  const maybeStopWatcher = async () => {
    if (serverEventSubscribers.size > 0) return;
    if (!mcpConfigWatcher) return;
    const w = mcpConfigWatcher;
    mcpConfigWatcher = null;
    try {
      await w.close();
    } catch {
    }
  };
  app.use("*", createOriginMiddleware(allowedOrigins));
  if (!dangerouslyOmitAuth) {
    app.use("*", createAuthMiddleware(authToken));
  }
  app.get("/api/config", (c) => {
    const payload = {
      ...options.initialConfig,
      writable,
      ...options.sandboxUrl ? { sandboxUrl: options.sandboxUrl } : {}
    };
    return c.json(payload);
  });
  app.post("/api/mcp/connect", async (c) => {
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    const config = body.config;
    if (!config) {
      return c.json({ error: "Missing config" }, 400);
    }
    const sessionId = crypto.randomUUID();
    const session = new RemoteSession(sessionId);
    let transport;
    let authHandle;
    try {
      const initialAuthState = body.authState ?? (body.oauthTokens ? { oauthTokens: body.oauthTokens } : void 0);
      authHandle = createRemoteAuthProvider(initialAuthState);
      const result = createTransportNode(config, {
        pipeStderr: true,
        onStderr: (entry) => session.onStderr(entry),
        onFetchRequest: (entry) => session.onFetchRequest(entry),
        onFetchResponseBody: (id, body2) => session.onFetchResponseBody(id, body2),
        authProvider: authHandle?.provider,
        settings: body.settings,
        // Always intercept 401/403 on the node MCP transport. Without this, the
        // SDK's built-in auth() retry on a frozen token snapshot can hang or
        // succeed locally while /api/mcp/send still awaits a JSON-RPC response.
        interceptAuthChallenges: true
      });
      transport = result.transport;
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return c.json({ error: `Failed to create transport: ${msg}` }, 500);
    }
    session.setAuthProviderHandle(authHandle ?? null);
    session.setTransport(transport);
    transport.onmessage = (msg) => session.onMessage(msg);
    let transportFailed = false;
    let transportError = null;
    const originalOnclose = transport.onclose;
    const originalOnerror = transport.onerror;
    transport.onerror = (err) => {
      if (session.handleTransportAuthError(err)) {
        originalOnerror?.(err);
        return;
      }
      transportFailed = true;
      transportError = err instanceof Error ? err.message : String(err);
      originalOnerror?.(err);
    };
    transport.onclose = () => {
      const session2 = sessions.get(sessionId);
      if (session2) {
        const errorMsg = transportError || "Transport closed - process may have exited";
        session2.markTransportDead(errorMsg);
        if (!session2.hasEventConsumer()) {
          setTimeout(() => {
            const stale = sessions.get(sessionId);
            if (stale && !stale.hasEventConsumer()) {
              sessions.delete(sessionId);
            }
          }, 3e4).unref();
        }
      } else {
        transportFailed = true;
        transportError = transportError || "Transport closed during start - process may have failed";
      }
      originalOnclose?.();
    };
    try {
      await transport.start();
      if (transportFailed) {
        const errorMsg = transportError || "Transport failed during start";
        return c.json({ error: `Failed to start transport: ${errorMsg}` }, 500);
      }
    } catch (err) {
      if (err instanceof AuthChallengeError) {
        try {
          await transport.close();
        } catch {
        }
        session.noteAuthChallengeDeliveredViaHttp();
        return c.json({
          ok: false,
          kind: "auth_challenge",
          authChallenge: err.authChallenge
        });
      }
      const msg = err instanceof Error ? err.message : String(err);
      const status = err.code ?? err.status;
      const is401 = status === 401;
      return c.json(
        { error: `Failed to start transport: ${msg}` },
        is401 ? 401 : 500
      );
    }
    sessions.set(sessionId, session);
    return c.json({ sessionId });
  });
  app.post("/api/mcp/send", async (c) => {
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    const { sessionId, message, relatedRequestId, headers, protocolVersion } = body;
    if (!sessionId || !message) {
      return c.json({ error: "Missing sessionId or message" }, 400);
    }
    const session = sessions.get(sessionId);
    if (!session) {
      return c.json({ error: "Session not found" }, 404);
    }
    if (session.isTransportDead()) {
      const errorMsg = session.getTransportError() || "Transport closed";
      return c.json({ ok: false, kind: "transport_error", error: errorMsg });
    }
    session.applyProtocolVersion(protocolVersion);
    session.beginSend();
    const requestId = requestIdForSendWait(message);
    let responseWait;
    if (requestId !== void 0) {
      responseWait = session.waitForRequestResponse(requestId);
      void responseWait.catch(() => {
      });
    }
    try {
      const paramHeaders = mcpParamHeadersOnly(headers);
      await session.transport.send(message, {
        relatedRequestId,
        ...paramHeaders && { headers: paramHeaders }
      });
      if (responseWait) {
        await responseWait;
      }
      return c.json({ ok: true });
    } catch (err) {
      if (requestId !== void 0) {
        session.cancelRequestWait(requestId);
      }
      if (err instanceof AuthChallengeError) {
        session.noteAuthChallengeDeliveredViaHttp();
        return c.json({
          ok: false,
          kind: "auth_challenge",
          authChallenge: err.authChallenge
        });
      }
      const msg = err instanceof Error ? err.message : String(err);
      return c.json({ ok: false, kind: "transport_error", error: msg });
    } finally {
      session.endSend();
    }
  });
  app.get("/api/mcp/events", async (c) => {
    const sessionId = c.req.query("sessionId");
    if (!sessionId) {
      return c.json({ error: "Missing sessionId query" }, 400);
    }
    const session = sessions.get(sessionId);
    if (!session) {
      return c.json({ error: "Session not found" }, 404);
    }
    return streamSSE(c, async (stream) => {
      session.setEventConsumer((event) => {
        const data = JSON.stringify(event);
        void stream.writeSSE({
          event: event.type,
          data
        });
      });
      if (session.isTransportDead()) {
        await new Promise((resolve6) => setTimeout(resolve6, 0));
        session.clearEventConsumer();
        sessions.delete(sessionId);
        return;
      }
      await stream.write(SSE_PRIMING_COMMENT);
      stream.onAbort(() => {
        const shouldCleanup = session.clearEventConsumer();
        stream.close();
        if (shouldCleanup || session.isTransportDead()) {
          sessions.delete(sessionId);
        }
      });
      await new Promise((resolve6) => {
        stream.onAbort(() => {
          resolve6();
        });
      });
    });
  });
  app.post("/api/mcp/disconnect", async (c) => {
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    const sessionId = body.sessionId;
    if (!sessionId) {
      return c.json({ error: "Missing sessionId" }, 400);
    }
    const session = sessions.get(sessionId);
    if (session) {
      session.clearEventConsumer();
      await session.transport.close();
      sessions.delete(sessionId);
    }
    return c.json({ ok: true });
  });
  app.post("/api/mcp/auth-state", async (c) => {
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    const { sessionId, authState } = body;
    if (!sessionId || !authState) {
      return c.json({ error: "Missing sessionId or authState" }, 400);
    }
    const session = sessions.get(sessionId);
    if (!session) {
      return c.json({ error: "Session not found" }, 404);
    }
    if (session.isTransportDead()) {
      const errorMsg = session.getTransportError() || "Transport closed";
      return c.json({ ok: false, kind: "transport_error", error: errorMsg });
    }
    try {
      session.setAuthState(authState);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return c.json({ error: msg }, 400);
    }
    return c.json({ ok: true });
  });
  app.post("/api/fetch", async (c) => {
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    const { url, method = "GET", headers = {}, body: reqBody } = body;
    if (!url) {
      return c.json({ error: "Missing url" }, 400);
    }
    try {
      const res = await fetch(url, {
        method,
        headers: new Headers(headers),
        body: reqBody
      });
      const resHeaders = {};
      res.headers.forEach((v, k) => {
        resHeaders[k] = v;
      });
      const contentType = res.headers.get("content-type");
      const isStream = contentType?.includes("text/event-stream") || contentType?.includes("application/x-ndjson");
      let resBody;
      if (!isStream && res.body) {
        resBody = await res.text();
      }
      return c.json({
        ok: res.ok,
        status: res.status,
        statusText: res.statusText,
        headers: resHeaders,
        body: resBody
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return c.json({ error: msg }, 500);
    }
  });
  app.post("/api/log", async (c) => {
    const body = await c.req.json().catch(() => ({}));
    if (fileLogger) {
      forwardLogEvent(fileLogger, body);
    }
    return c.json({ ok: true });
  });
  app.get("/api/storage/:storeId", async (c) => {
    const storeId = c.req.param("storeId");
    if (!storeId || !validateStoreId(storeId)) {
      return c.json({ error: "Invalid storeId" }, 400);
    }
    const filePath = getStoreFilePath(storageDir, storeId);
    try {
      if (storeId === "client") {
        const config = await readClientConfigStore(filePath, secretStore);
        return c.json(config);
      }
      const raw = await readStoreFile(filePath);
      if (raw === null) {
        return c.json({}, 200);
      }
      const store = parseStore(raw);
      return c.json(store);
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to read store: ${msg}` }, 500);
    }
  });
  app.post("/api/storage/:storeId", async (c) => {
    const storeId = c.req.param("storeId");
    if (!storeId || !validateStoreId(storeId)) {
      return c.json({ error: "Invalid storeId" }, 400);
    }
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    const filePath = getStoreFilePath(storageDir, storeId);
    try {
      if (storeId === "client") {
        await writeClientConfigStore(filePath, body, secretStore);
        return c.json({ ok: true });
      }
      const jsonData = serializeStore(body);
      await writeStoreFile(filePath, jsonData);
      return c.json({ ok: true });
    } catch (error) {
      if (error instanceof ZodError2) {
        return c.json({ error: formatClientConfigLoadError(error) }, 400);
      }
      const keychainResp = storeId === "client" ? keychainErrorResponse(c, error) : void 0;
      if (keychainResp) return keychainResp;
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to write store: ${msg}` }, 500);
    }
  });
  app.delete("/api/storage/:storeId", async (c) => {
    const storeId = c.req.param("storeId");
    if (!storeId || !validateStoreId(storeId)) {
      return c.json({ error: "Invalid storeId" }, 400);
    }
    const filePath = getStoreFilePath(storageDir, storeId);
    try {
      if (storeId === "client") {
        await deleteClientConfigStore(filePath, secretStore);
        return c.json({ ok: true });
      }
      await deleteStoreFile(filePath);
      return c.json({ ok: true });
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to delete store: ${msg}` }, 500);
    }
  });
  const logWarn = (bindings, msg) => {
    if (fileLogger) {
      fileLogger.warn(bindings, msg);
    } else {
      console.warn("[mcp.json]", msg, bindings);
    }
  };
  const isStringRecord = (v) => {
    if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
    for (const val of Object.values(v)) {
      if (typeof val !== "string") return false;
    }
    return true;
  };
  const isKvArray = (v) => {
    if (!Array.isArray(v)) return false;
    return v.every(
      (e) => e !== null && typeof e === "object" && typeof e.key === "string" && typeof e.value === "string"
    );
  };
  const isOauthObject = (v) => {
    if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
    const o = v;
    for (const k of ["clientId", "clientSecret", "scopes"]) {
      if (o[k] !== void 0 && typeof o[k] !== "string") return false;
    }
    if (o.enterpriseManaged !== void 0 && typeof o.enterpriseManaged !== "boolean") {
      return false;
    }
    return true;
  };
  const isRootArray = (v) => {
    if (!Array.isArray(v)) return false;
    return v.every((e) => {
      if (e === null || typeof e !== "object") return false;
      const o = e;
      if (typeof o.uri !== "string") return false;
      if (o.name !== void 0 && typeof o.name !== "string") return false;
      return true;
    });
  };
  const isNonNegNumber = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
  const normalizeMcpServers = (raw) => {
    if (!raw || typeof raw !== "object") return {};
    const out = {};
    for (const [id, val] of Object.entries(raw)) {
      if (!val || typeof val !== "object") continue;
      const valObj = val;
      if ("settings" in valObj) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "settings" },
          "Dropping legacy `settings` node from mcp.json entry \u2014 fields now live at the top level. Re-enter via the settings form or hand-edit the file into the flat shape."
        );
        delete valObj.settings;
      }
      if ("headers" in valObj && !isStringRecord(valObj.headers)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "headers" },
          "Dropping malformed `headers` field \u2014 expected `Record<string, string>`."
        );
        delete valObj.headers;
      }
      if ("metadata" in valObj && !isKvArray(valObj.metadata)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "metadata" },
          "Dropping malformed `metadata` field \u2014 expected `Array<{ key: string, value: string }>`."
        );
        delete valObj.metadata;
      }
      if ("connectionTimeout" in valObj && !isNonNegNumber(valObj.connectionTimeout)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "connectionTimeout" },
          "Dropping malformed `connectionTimeout` field \u2014 expected non-negative number."
        );
        delete valObj.connectionTimeout;
      }
      if ("requestTimeout" in valObj && !isNonNegNumber(valObj.requestTimeout)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "requestTimeout" },
          "Dropping malformed `requestTimeout` field \u2014 expected non-negative number."
        );
        delete valObj.requestTimeout;
      }
      if ("taskTtl" in valObj && !isNonNegNumber(valObj.taskTtl)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "taskTtl" },
          "Dropping malformed `taskTtl` field \u2014 expected non-negative number."
        );
        delete valObj.taskTtl;
      }
      if ("maxFetchRequests" in valObj && !isNonNegNumber(valObj.maxFetchRequests)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "maxFetchRequests" },
          "Dropping malformed `maxFetchRequests` field \u2014 expected non-negative number."
        );
        delete valObj.maxFetchRequests;
      }
      if ("oauth" in valObj && !isOauthObject(valObj.oauth)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "oauth" },
          "Dropping malformed `oauth` field \u2014 expected `{ clientId?, clientSecret?, scopes?, enterpriseManaged? }`."
        );
        delete valObj.oauth;
      }
      if ("roots" in valObj && !isRootArray(valObj.roots)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "roots" },
          "Dropping malformed `roots` field \u2014 expected `Array<{ uri: string, name?: string }>`."
        );
        delete valObj.roots;
      }
      if ("protocolEra" in valObj && !isProtocolEra(valObj.protocolEra)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "protocolEra" },
          "Dropping malformed `protocolEra` field \u2014 expected 'legacy', 'auto', or 'modern'."
        );
        delete valObj.protocolEra;
      }
      if ("modernLogLevel" in valObj && !isModernLogLevel(valObj.modernLogLevel)) {
        logWarn(
          { route: "/api/servers", id, droppedKey: "modernLogLevel" },
          "Dropping malformed `modernLogLevel` field \u2014 expected 'off' or a logging level."
        );
        delete valObj.modernLogLevel;
      }
      out[id] = normalizeServerType(
        valObj
      );
    }
    return out;
  };
  const SMUGGLE_GUARDED_KEYS = /* @__PURE__ */ new Set([
    ...INSPECTOR_FIELD_KEYS,
    "settings"
  ]);
  const buildStoredEntry = (id, config, settings) => {
    const configObj = config !== null && typeof config === "object" ? config : {};
    const smuggled = [];
    const configOnly = {};
    for (const [k, v] of Object.entries(configObj)) {
      if (SMUGGLE_GUARDED_KEYS.has(k)) {
        smuggled.push(k);
        continue;
      }
      configOnly[k] = v;
    }
    if (smuggled.length > 0) {
      logWarn(
        { route: "/api/servers", id, smuggledKeys: smuggled },
        "Stripping Inspector-extension keys from request body's `config` \u2014 those must travel through the top-level `settings` field, not nested inside `config`."
      );
    }
    const normalized = normalizeServerType(
      configOnly
    );
    if (settings !== void 0) {
      Object.assign(normalized, inspectorSettingsToStoredFields(settings));
    }
    return normalized;
  };
  const applyStdioSettingsToConfig = (entry, settings, provided) => {
    if (!(entry.type === "stdio" || entry.type === void 0)) return;
    const stdio = entry;
    if (provided.env) {
      const env = envPairsToRecord(settings.env);
      if (Object.keys(env).length > 0) {
        stdio.env = env;
      } else {
        delete stdio.env;
      }
    }
    if (provided.cwd) {
      const cwd = settings.cwd?.trim();
      if (cwd) {
        stdio.cwd = cwd;
      } else {
        delete stdio.cwd;
      }
    }
  };
  const validateSettings = (raw) => {
    if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
      return { ok: false, error: "settings must be an object" };
    }
    const obj = raw;
    const isKvArray2 = (v) => {
      if (!Array.isArray(v)) return false;
      return v.every(
        (e) => e !== null && typeof e === "object" && typeof e.key === "string" && typeof e.value === "string"
      );
    };
    const isBooleanRecord = (v) => {
      if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
      return Object.values(v).every(
        (flag) => typeof flag === "boolean"
      );
    };
    if (!isKvArray2(obj.headers)) {
      return {
        ok: false,
        error: "settings.headers must be an array of { key, value }"
      };
    }
    if (!isKvArray2(obj.metadata)) {
      return {
        ok: false,
        error: "settings.metadata must be an array of { key, value }"
      };
    }
    if (obj.env !== void 0 && !isKvArray2(obj.env)) {
      return {
        ok: false,
        error: "settings.env must be an array of { key, value }"
      };
    }
    if (obj.cwd !== void 0 && typeof obj.cwd !== "string") {
      return { ok: false, error: "settings.cwd must be a string" };
    }
    if (typeof obj.connectionTimeout !== "number" || obj.connectionTimeout < 0) {
      return {
        ok: false,
        error: "settings.connectionTimeout must be a non-negative number"
      };
    }
    if (typeof obj.requestTimeout !== "number" || obj.requestTimeout < 0) {
      return {
        ok: false,
        error: "settings.requestTimeout must be a non-negative number"
      };
    }
    if (obj.taskTtl !== void 0 && (typeof obj.taskTtl !== "number" || obj.taskTtl < 0)) {
      return {
        ok: false,
        error: "settings.taskTtl must be a non-negative number"
      };
    }
    if (obj.autoRefreshOnListChanged !== void 0 && typeof obj.autoRefreshOnListChanged !== "boolean") {
      return {
        ok: false,
        error: "settings.autoRefreshOnListChanged must be a boolean"
      };
    }
    if (obj.paginatedLists !== void 0 && typeof obj.paginatedLists !== "boolean") {
      return {
        ok: false,
        error: "settings.paginatedLists must be a boolean"
      };
    }
    if (obj.maxFetchRequests !== void 0 && (typeof obj.maxFetchRequests !== "number" || obj.maxFetchRequests < 0)) {
      return {
        ok: false,
        error: "settings.maxFetchRequests must be a non-negative number"
      };
    }
    for (const optional of [
      "oauthClientId",
      "oauthClientSecret",
      "oauthScopes"
    ]) {
      if (obj[optional] !== void 0 && typeof obj[optional] !== "string") {
        return { ok: false, error: `settings.${optional} must be a string` };
      }
    }
    if (obj.enterpriseManaged !== void 0 && typeof obj.enterpriseManaged !== "boolean") {
      return {
        ok: false,
        error: "settings.enterpriseManaged must be a boolean"
      };
    }
    if (obj.oauthOnInsufficientScope !== void 0 && obj.oauthOnInsufficientScope !== "reauthorize" && obj.oauthOnInsufficientScope !== "throw") {
      return {
        ok: false,
        error: "settings.oauthOnInsufficientScope must be 'reauthorize' or 'throw'"
      };
    }
    if (obj.roots !== void 0 && !isRootArray(obj.roots)) {
      return {
        ok: false,
        error: "settings.roots must be an array of { uri, name? }"
      };
    }
    if (obj.protocolEra !== void 0 && !isProtocolEra(obj.protocolEra)) {
      return {
        ok: false,
        error: "settings.protocolEra must be 'legacy', 'auto', or 'modern'"
      };
    }
    if (obj.modernLogLevel !== void 0 && !isModernLogLevel(obj.modernLogLevel)) {
      return {
        ok: false,
        error: "settings.modernLogLevel must be 'off' or a logging level (debug\u2026emergency)"
      };
    }
    if (obj.advertisedExtensions !== void 0 && !isBooleanRecord(obj.advertisedExtensions)) {
      return {
        ok: false,
        error: "settings.advertisedExtensions must be an object of boolean flags"
      };
    }
    const value = {
      headers: obj.headers,
      // Absent → empty list, matching the read side. The write-through drops
      // empty-key rows and clears `config.env` when the list is empty.
      env: isKvArray2(obj.env) ? obj.env : [],
      metadata: obj.metadata,
      connectionTimeout: obj.connectionTimeout,
      requestTimeout: obj.requestTimeout,
      // Absent → product default, matching the read side
      // (storedFieldsToInspectorSettings). The default is the omit-sentinel in
      // inspectorSettingsToStoredFields, so this won't write a spurious taskTtl
      // to disk for a client that didn't send one.
      taskTtl: typeof obj.taskTtl === "number" ? obj.taskTtl : DEFAULT_TASK_TTL_MS,
      // Absent → false, matching the read side. The omit-on-false logic lives
      // in inspectorSettingsToStoredFields, so a false value writes nothing.
      autoRefreshOnListChanged: obj.autoRefreshOnListChanged === true,
      // Absent → false, matching the read side (omit-on-false on the write side).
      paginatedLists: obj.paginatedLists === true,
      // Absent → product default, matching the read side. The default is the
      // omit-sentinel in inspectorSettingsToStoredFields, so a client that
      // didn't send one writes no spurious maxFetchRequests to disk.
      maxFetchRequests: typeof obj.maxFetchRequests === "number" ? obj.maxFetchRequests : DEFAULT_MAX_FETCH_REQUESTS,
      // Absent → empty list, matching the read side
      // (storedFieldsToInspectorSettings). Empty rows are dropped on the way
      // to disk by inspectorSettingsToStoredFields, so an empty array here
      // writes no spurious `roots` field.
      roots: isRootArray(obj.roots) ? obj.roots : []
    };
    if (typeof obj.oauthClientId === "string" && obj.oauthClientId !== "") {
      value.oauthClientId = obj.oauthClientId;
    }
    if (typeof obj.oauthClientSecret === "string" && obj.oauthClientSecret !== "") {
      value.oauthClientSecret = obj.oauthClientSecret;
    }
    if (typeof obj.oauthScopes === "string" && obj.oauthScopes !== "") {
      value.oauthScopes = obj.oauthScopes;
    }
    if (obj.enterpriseManaged === true) {
      value.enterpriseManaged = true;
    }
    if (obj.oauthOnInsufficientScope === "reauthorize" || obj.oauthOnInsufficientScope === "throw") {
      value.oauthOnInsufficientScope = obj.oauthOnInsufficientScope;
    }
    if (isProtocolEra(obj.protocolEra)) {
      value.protocolEra = obj.protocolEra;
    }
    if (isModernLogLevel(obj.modernLogLevel)) {
      value.modernLogLevel = obj.modernLogLevel;
    }
    if (isBooleanRecord(obj.advertisedExtensions) && Object.keys(obj.advertisedExtensions).length > 0) {
      value.advertisedExtensions = { ...obj.advertisedExtensions };
    }
    if (typeof obj.cwd === "string" && obj.cwd !== "") {
      value.cwd = obj.cwd;
    }
    return {
      ok: true,
      value,
      envProvided: obj.env !== void 0,
      cwdProvided: obj.cwd !== void 0
    };
  };
  let writeQueue = Promise.resolve();
  const withWriteLock = async (fn) => {
    const prev = writeQueue;
    let release = () => {
    };
    writeQueue = new Promise((r) => {
      release = r;
    });
    try {
      await prev;
      return await fn();
    } finally {
      release();
    }
  };
  const readMcpConfig = async () => {
    const raw = await readStoreFile(mcpConfigPath);
    if (raw === null) return { mcpServers: {} };
    const parsed = parseStore(raw);
    return { mcpServers: normalizeMcpServers(parsed?.mcpServers) };
  };
  const writeKeychainEntriesFor = async (id, secrets) => {
    await Promise.all(
      Object.entries(secrets).map(
        ([field, value]) => secretStore.set(id, field, value)
      )
    );
  };
  const readKeychainEntriesFor = async (id, fields) => {
    const values = await Promise.all(
      fields.map(
        async (field) => [field, await secretStore.get(id, field)]
      )
    );
    const out = {};
    for (const [field, v] of values) {
      if (v !== null) out[field] = v;
    }
    return out;
  };
  const hasPlaintextSecrets = (config) => {
    for (const stored of Object.values(config.mcpServers)) {
      const { secrets } = extractSecretsFromStored(stored);
      if (Object.keys(secrets).length > 0) return true;
    }
    return false;
  };
  const migratePlaintextSecrets = async (config) => {
    let changed = false;
    const next = { mcpServers: {} };
    try {
      for (const [id, stored] of Object.entries(config.mcpServers)) {
        const { stripped, secrets } = extractSecretsFromStored(stored);
        if (Object.keys(secrets).length === 0) {
          next.mcpServers[id] = stored;
          continue;
        }
        for (const [field, value] of Object.entries(secrets)) {
          const existing = await secretStore.get(id, field);
          if (existing === null) {
            await secretStore.set(id, field, value);
          }
        }
        next.mcpServers[id] = stripped;
        changed = true;
      }
    } catch (err) {
      if (err instanceof KeychainUnavailableError) {
        if (fileLogger) {
          fileLogger.warn(
            { err: err.message },
            "Keychain unavailable; skipping plaintext-secret migration on this read. Existing mcp.json plaintext values are preserved."
          );
        }
        return { migrated: config, changed: false };
      }
      throw err;
    }
    return { migrated: next, changed };
  };
  const rehydrateConfig = async (config) => {
    const out = { mcpServers: {} };
    for (const [id, stored] of Object.entries(config.mcpServers)) {
      const fields = expectedSecretFields(stored);
      const secrets = await readKeychainEntriesFor(id, fields);
      out.mcpServers[id] = mergeSecretsIntoStored(stored, secrets);
    }
    return out;
  };
  const computeObsoleteFields = (previousFields, nextSecrets) => {
    const nextFieldSet = new Set(Object.keys(nextSecrets));
    const obsolete = [];
    for (const field of previousFields) {
      if (!nextFieldSet.has(field)) obsolete.push(field);
    }
    return obsolete;
  };
  const mergeRenameKeychainSecrets = (stripped, keychainSecrets, nextSecrets) => {
    const allowedFields = new Set(expectedSecretFields(stripped));
    const merged = { ...keychainSecrets, ...nextSecrets };
    const out = {};
    for (const [field, value] of Object.entries(merged)) {
      if (allowedFields.has(field)) out[field] = value;
    }
    return out;
  };
  const deleteKeychainFields = async (id, fields) => {
    await Promise.all(fields.map((field) => secretStore.delete(id, field)));
  };
  const keychainErrorResponse = (c, err) => {
    if (err instanceof KeychainUnavailableError) {
      return c.json({ error: err.message }, 503);
    }
    return void 0;
  };
  const requireWritable = (c) => {
    if (writable) return void 0;
    return c.json({ error: "Server list is read-only for this session." }, 403);
  };
  app.get("/api/servers", async (c) => {
    try {
      if (inMemoryServers) {
        const normalized = {
          mcpServers: normalizeMcpServers(inMemoryServers.mcpServers)
        };
        return c.json(await rehydrateConfig(normalized));
      }
      const raw = await readStoreFile(mcpConfigPath);
      if (raw !== null) {
        const parsed = parseStore(raw);
        const onDisk = {
          mcpServers: normalizeMcpServers(parsed?.mcpServers)
        };
        if (!writable) {
          return c.json(await rehydrateConfig(onDisk));
        }
        if (!hasPlaintextSecrets(onDisk)) {
          return c.json(await rehydrateConfig(onDisk));
        }
      } else if (!writable) {
        return c.json({ mcpServers: {} });
      }
      const settled = await withWriteLock(async () => {
        const rawInside = await readStoreFile(mcpConfigPath);
        if (rawInside === null) {
          await writeMcpAndTrackMtime(serializeStore(DEFAULT_SEED_CONFIG));
          return DEFAULT_SEED_CONFIG;
        }
        const parsedInside = parseStore(rawInside);
        const inside = {
          mcpServers: normalizeMcpServers(parsedInside?.mcpServers)
        };
        if (!hasPlaintextSecrets(inside)) return inside;
        const { migrated, changed } = await migratePlaintextSecrets(inside);
        if (changed) {
          await writeMcpAndTrackMtime(serializeStore(migrated));
        }
        return migrated;
      });
      return c.json(await rehydrateConfig(settled));
    } catch (error) {
      const keychainResp = keychainErrorResponse(c, error);
      if (keychainResp) return keychainResp;
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to read server list: ${msg}` }, 500);
    }
  });
  app.get("/api/import-source", (c) => {
    const type = c.req.query("type") ?? "";
    const result = resolveImportSource(
      type,
      process.platform,
      homedir(),
      (path3) => existsSync2(path3) ? readFileSync4(path3, "utf-8") : null
    );
    if (!result) {
      return c.json(
        { error: `Unknown import source: ${type || "(none)"}` },
        400
      );
    }
    return c.json(result);
  });
  app.post("/api/servers", async (c) => {
    const ro = requireWritable(c);
    if (ro) return ro;
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    if (typeof body.id !== "string" || !validateStoreId(body.id)) {
      return c.json(
        {
          error: "Invalid id: must be non-empty and contain only alphanumeric, hyphen, or underscore"
        },
        400
      );
    }
    if (!body.config || typeof body.config !== "object") {
      return c.json({ error: "Missing or invalid config" }, 400);
    }
    let postSettings;
    if (body.settings !== void 0 && body.settings !== null) {
      const validated = validateSettings(body.settings);
      if (!validated.ok) return c.json({ error: validated.error }, 400);
      postSettings = validated.value;
    }
    const id = body.id;
    try {
      return await withWriteLock(async () => {
        const current = await readMcpConfig();
        if (id in current.mcpServers) {
          return c.json({ error: `Server '${id}' already exists` }, 409);
        }
        const built = buildStoredEntry(id, body.config, postSettings);
        const { stripped, secrets } = extractSecretsFromStored(built);
        await secretStore.deleteAllForServer(id);
        await writeKeychainEntriesFor(id, secrets);
        current.mcpServers[id] = stripped;
        await writeMcpAndTrackMtime(serializeStore(current));
        return c.json({ ok: true });
      });
    } catch (error) {
      const keychainResp = keychainErrorResponse(c, error);
      if (keychainResp) return keychainResp;
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to add server: ${msg}` }, 500);
    }
  });
  app.put("/api/servers/order", async (c) => {
    const ro = requireWritable(c);
    if (ro) return ro;
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    if (!Array.isArray(body.order) || !body.order.every((id) => typeof id === "string")) {
      return c.json({ error: "order must be an array of strings" }, 400);
    }
    const order = body.order;
    if (new Set(order).size !== order.length) {
      return c.json({ error: "order contains duplicate ids" }, 400);
    }
    try {
      return await withWriteLock(async () => {
        const current = await readMcpConfig();
        const currentIds = Object.keys(current.mcpServers);
        const currentSet = new Set(currentIds);
        const sameSet = currentIds.length === order.length && order.every((id) => currentSet.has(id));
        if (!sameSet) {
          return c.json(
            {
              error: "order does not match the current server set (it may have changed on disk)"
            },
            409
          );
        }
        const next = { mcpServers: {} };
        for (const id of order) {
          next.mcpServers[id] = current.mcpServers[id];
        }
        await writeMcpAndTrackMtime(serializeStore(next));
        return c.json({ ok: true });
      });
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to reorder servers: ${msg}` }, 500);
    }
  });
  app.put("/api/servers/:id", async (c) => {
    const ro = requireWritable(c);
    if (ro) return ro;
    const originalId = c.req.param("id");
    if (!originalId || !validateStoreId(originalId)) {
      return c.json({ error: "Invalid id" }, 400);
    }
    let body;
    try {
      body = await c.req.json();
    } catch {
      return c.json({ error: "Invalid JSON body" }, 400);
    }
    if (body.config !== void 0 && (body.config === null || typeof body.config !== "object")) {
      return c.json({ error: "Invalid config" }, 400);
    }
    let settingsIntent;
    if (body.settings === void 0) {
      settingsIntent = { kind: "preserve" };
    } else if (body.settings === null) {
      settingsIntent = { kind: "clear" };
    } else {
      const validated = validateSettings(body.settings);
      if (!validated.ok) return c.json({ error: validated.error }, 400);
      settingsIntent = {
        kind: "apply",
        value: validated.value,
        envProvided: validated.envProvided,
        cwdProvided: validated.cwdProvided
      };
    }
    const newId = typeof body.id === "string" ? body.id : originalId;
    if (!validateStoreId(newId)) {
      return c.json({ error: "Invalid new id" }, 400);
    }
    try {
      return await withWriteLock(async () => {
        const current = await readMcpConfig();
        if (!(originalId in current.mcpServers)) {
          return c.json({ error: `Server '${originalId}' not found` }, 404);
        }
        if (newId !== originalId && newId in current.mcpServers) {
          return c.json({ error: `Server '${newId}' already exists` }, 409);
        }
        const existing = current.mcpServers[originalId];
        if (!existing) {
          return c.json({ error: `Server '${originalId}' not found` }, 404);
        }
        const existingConfig = stripInspectorFields(existing);
        const existingSettings = storedFieldsToInspectorSettings(existing);
        const nextConfig = body.config !== void 0 ? body.config : existingConfig;
        let nextSettings;
        switch (settingsIntent.kind) {
          case "preserve":
            nextSettings = existingSettings;
            break;
          case "clear":
            nextSettings = void 0;
            break;
          case "apply":
            nextSettings = settingsIntent.value;
            break;
        }
        const built = buildStoredEntry(newId, nextConfig, nextSettings);
        if (body.config === void 0 && settingsIntent.kind === "apply") {
          applyStdioSettingsToConfig(built, settingsIntent.value, {
            env: settingsIntent.envProvided,
            cwd: settingsIntent.cwdProvided
          });
          if (!settingsIntent.envProvided && (built.type === "stdio" || built.type === void 0)) {
            const stdio = built;
            if (stdio.env && Object.keys(stdio.env).length > 0) {
              const keys = Object.keys(stdio.env);
              const existingSecrets = await readKeychainEntriesFor(
                originalId,
                keys.map((k) => envSecretField(k))
              );
              const rehydrated = { ...stdio.env };
              for (const k of keys) {
                const v = existingSecrets[envSecretField(k)];
                if (v !== void 0) rehydrated[k] = v;
              }
              stdio.env = rehydrated;
            }
          }
        }
        const { stripped, secrets } = extractSecretsFromStored(built);
        const next = { mcpServers: {} };
        for (const [key, val] of Object.entries(current.mcpServers)) {
          if (key === originalId) {
            next.mcpServers[newId] = stripped;
          } else {
            next.mcpServers[key] = val;
          }
        }
        if (newId !== originalId) {
          const previousFields = expectedSecretFields(existing);
          const keychainSecrets = await readKeychainEntriesFor(
            originalId,
            previousFields
          );
          const secretsToWrite = mergeRenameKeychainSecrets(
            stripped,
            keychainSecrets,
            secrets
          );
          await writeKeychainEntriesFor(newId, secretsToWrite);
          await writeMcpAndTrackMtime(serializeStore(next));
          await secretStore.deleteAllForServer(originalId);
        } else {
          const previousFields = new Set(expectedSecretFields(existing));
          const obsolete = computeObsoleteFields(previousFields, secrets);
          await writeKeychainEntriesFor(newId, secrets);
          await writeMcpAndTrackMtime(serializeStore(next));
          await deleteKeychainFields(newId, obsolete);
        }
        return c.json({ ok: true });
      });
    } catch (error) {
      const keychainResp = keychainErrorResponse(c, error);
      if (keychainResp) return keychainResp;
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to update server: ${msg}` }, 500);
    }
  });
  app.delete("/api/servers/:id", async (c) => {
    const ro = requireWritable(c);
    if (ro) return ro;
    const id = c.req.param("id");
    if (!id || !validateStoreId(id)) {
      return c.json({ error: "Invalid id" }, 400);
    }
    try {
      return await withWriteLock(async () => {
        const current = await readMcpConfig();
        if (!(id in current.mcpServers)) {
          await secretStore.deleteAllForServer(id);
          return c.json({ ok: true });
        }
        delete current.mcpServers[id];
        await writeMcpAndTrackMtime(serializeStore(current));
        await secretStore.deleteAllForServer(id);
        return c.json({ ok: true });
      });
    } catch (error) {
      const keychainResp = keychainErrorResponse(c, error);
      if (keychainResp) return keychainResp;
      const msg = error instanceof Error ? error.message : String(error);
      return c.json({ error: `Failed to delete server: ${msg}` }, 500);
    }
  });
  app.get("/api/servers/events", async (c) => {
    return streamSSE(c, async (stream) => {
      const send = (data) => {
        void stream.writeSSE({ event: "change", data });
      };
      if (watchable) {
        serverEventSubscribers.add(send);
        ensureWatcher();
      }
      await stream.write(SSE_PRIMING_COMMENT);
      stream.onAbort(() => {
        serverEventSubscribers.delete(send);
        void maybeStopWatcher();
        stream.close();
      });
      await new Promise((resolve6) => {
        stream.onAbort(() => resolve6());
      });
    });
  });
  return {
    app,
    authToken,
    close: async () => {
      serverEventSubscribers.clear();
      await maybeStopWatcher();
    }
  };
}

// server/inject-auth-token.ts
function serializeTokenForScript(token) {
  return JSON.stringify(token).replace(/</g, "\\u003c");
}
function injectAuthToken(html, token) {
  if (!token) return html;
  const script = `<script>window.${INSPECTOR_API_TOKEN_GLOBAL} = ${serializeTokenForScript(
    token
  )};</script>`;
  const headClose = html.indexOf("</head>");
  if (headClose !== -1) {
    return html.slice(0, headClose) + script + html.slice(headClose);
  }
  const bodyClose = html.indexOf("</body>");
  if (bodyClose !== -1) {
    return html.slice(0, bodyClose) + script + html.slice(bodyClose);
  }
  return script + html;
}

// server/vite-hono-plugin.ts
function honoMiddlewarePlugin(config) {
  let resolvedAuthToken = "";
  return {
    name: "hono-api-middleware",
    // Embed the API token into the dev-served index.html so a reload at the
    // bare URL (no `?MCP_INSPECTOR_API_TOKEN=…`) still authenticates. The
    // prod server applies the same injection in `server.ts`.
    transformIndexHtml(html) {
      return injectAuthToken(html, resolvedAuthToken);
    },
    // `apply: 'serve'` keeps the plugin out of `vite build`, but Vitest still
    // instantiates a Vite server in middleware mode (no HTTP server) for
    // transforms and invokes `configureServer` regardless. Returning early
    // when `server.httpServer` is missing keeps the plugin inert in that
    // context — only an actual `vite dev` (or `vite preview`) instance has
    // an HTTP server to attach to.
    apply: "serve",
    async configureServer(server) {
      if (process.env.VITEST) {
        return;
      }
      if (!server.httpServer) {
        return;
      }
      const sandboxController = createSandboxController({
        port: config.sandboxPort,
        host: config.sandboxHost,
        allowedOrigins: config.allowedOrigins
      });
      await sandboxController.start();
      const {
        app: honoApp,
        authToken: resolvedToken,
        close: closeApi
      } = createRemoteApp({
        authToken: config.dangerouslyOmitAuth ? void 0 : config.authToken,
        dangerouslyOmitAuth: config.dangerouslyOmitAuth,
        storageDir: config.storageDir,
        mcpConfigPath: config.mcpConfigPath,
        writable: config.writable,
        initialServers: config.initialServers ?? void 0,
        allowedOrigins: config.allowedOrigins,
        sandboxUrl: sandboxController.getUrl() ?? void 0,
        logger: config.logger,
        initialConfig: webServerConfigToInitialPayload(config)
      });
      resolvedAuthToken = config.dangerouslyOmitAuth ? "" : resolvedToken;
      const originalClose = server.close.bind(server);
      server.close = async () => {
        await closeApi();
        await sandboxController.close();
        return originalClose();
      };
      const sandboxUrl = sandboxController.getUrl();
      const logBanner = () => {
        const address = server.httpServer?.address();
        const actualPort = typeof address === "object" && address !== null ? address.port : config.port;
        const url = printServerBanner(
          config,
          actualPort,
          resolvedToken,
          sandboxUrl ?? void 0
        );
        if (config.autoOpen) {
          open(url);
        }
      };
      server.httpServer.once("listening", () => {
        setImmediate(async () => {
          try {
            await server.warmupRequest("/src/main.tsx");
          } catch (err) {
            console.warn("[vite] entry warmup failed:", err);
          }
          logBanner();
        });
      });
      const honoMiddleware = async (req, res, next) => {
        try {
          const pathname = req.url || "";
          if (!pathname.startsWith("/api")) {
            return next();
          }
          const url = `http://${req.headers.host}${pathname}`;
          const headers = new Headers();
          Object.entries(req.headers).forEach(
            ([key, value]) => {
              if (value) {
                headers.set(
                  key,
                  Array.isArray(value) ? value.join(", ") : value
                );
              }
            }
          );
          const init = { method: req.method, headers };
          if (req.method !== "GET" && req.method !== "HEAD") {
            const chunks = [];
            req.on("data", (chunk) => chunks.push(chunk));
            await new Promise((resolve6) => {
              req.once("end", () => resolve6());
              req.once("error", () => resolve6());
              req.once("close", () => resolve6());
            });
            if (chunks.length > 0) {
              init.body = Buffer.concat(chunks);
            }
          }
          const response = await honoApp.fetch(new Request(url, init));
          res.statusCode = response.status;
          response.headers.forEach((value, key) => {
            res.setHeader(key, value);
          });
          const isSSE = response.headers.get("content-type")?.includes("text/event-stream");
          if (isSSE) {
            res.setHeader("X-Accel-Buffering", "no");
            res.setHeader("Cache-Control", "no-cache");
            res.setHeader("Connection", "keep-alive");
          }
          if (response.body) {
            res.flushHeaders?.();
            const reader = response.body.getReader();
            const isDisconnect = (code) => code === "ERR_STREAM_DESTROYED" || code === "EPIPE" || code === "ECONNRESET";
            let clientGone = false;
            const onClose = () => {
              clientGone = true;
              reader.cancel().catch(() => {
              });
            };
            const onError = (err) => {
              clientGone = true;
              reader.cancel().catch(() => {
              });
              if (!isDisconnect(err.code)) {
                console.error("[Hono Middleware] Response error:", err);
              }
            };
            res.on("close", onClose);
            res.on("error", onError);
            const pump = async () => {
              try {
                while (true) {
                  const { done, value } = await reader.read();
                  if (done || clientGone || res.destroyed) break;
                  await new Promise((resolve6, reject) => {
                    res.write(
                      Buffer.from(value),
                      (err) => err ? reject(err) : resolve6()
                    );
                  });
                }
              } catch (err) {
                const code = err?.code;
                if (!clientGone && !res.destroyed && !isDisconnect(code)) {
                  console.error("[Hono Middleware] Stream error:", err);
                }
              } finally {
                res.off("close", onClose);
                res.off("error", onError);
                await reader.cancel().catch(() => {
                });
                if (!res.writableEnded && !res.destroyed) {
                  res.end();
                }
              }
            };
            void pump();
          } else {
            res.end();
          }
        } catch (error) {
          next(error);
        }
      };
      server.middlewares.use(honoMiddleware);
    }
  };
}

// server/vite-base-config.ts
import { rmSync } from "fs";
import { join as join5 } from "path";
var NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE = [
  "@modelcontextprotocol/client/stdio",
  // `atomically` is reached only through `core/storage/store-io.ts`,
  // which is imported by `core/mcp/remote/node/server.ts` (the Hono
  // app). The module never lands in the browser graph; excluding it
  // keeps Vite's dev-time scanner from chasing it through the plugin's
  // node-only import chain.
  "atomically",
  // `chokidar` is only loaded inside `core/mcp/remote/node/server.ts`
  // when the lazy mcp.json watcher starts. It transitively imports
  // `readdirp` and core node fs/os modules; excluding it keeps Vite's
  // dep scanner from walking into them during dev startup.
  "chokidar",
  "cross-spawn",
  "which",
  // `@napi-rs/keyring` is loaded only inside
  // `core/auth/node/secret-store.ts` from the Hono `/api/servers`
  // handlers — and lazily even there, via a cached dynamic import, so
  // an unsupported platform degrades instead of crashing at startup
  // (#1905). It's a native-binding package (no browser code path) so
  // excluding it keeps Vite's dep scanner from chasing into the
  // platform-specific binaries during dev startup.
  "@napi-rs/keyring"
];
function getViteDevOptimizeDeps() {
  return {
    exclude: [...NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE],
    force: true,
    ignoreOutdatedRequests: true,
    include: ["ajv", "@modelcontextprotocol/client/validators/ajv"]
  };
}
function clearViteDepsCache(clientWebRoot) {
  rmSync(join5(clientWebRoot, "node_modules", ".vite"), {
    recursive: true,
    force: true
  });
}

// ../../vitest.shared.mts
import path2 from "path";
import { fileURLToPath as fileURLToPath3 } from "url";
function vitestSharedPaths(clientDir) {
  const dirname8 = path2.resolve(clientDir);
  const repoRoot = path2.resolve(dirname8, "../..");
  const sharedAliases = {
    "@inspector/core": path2.resolve(repoRoot, "core"),
    "@modelcontextprotocol/inspector-test-server": path2.resolve(
      repoRoot,
      "test-servers/build/index.js"
    )
  };
  const sharedDedupe = [
    "react",
    "react-dom",
    "@modelcontextprotocol/client",
    "@modelcontextprotocol/core"
  ];
  const nodeModulesAliases = [
    {
      find: /^react$/,
      replacement: path2.resolve(dirname8, "node_modules/react")
    },
    {
      find: /^react\/jsx-runtime$/,
      replacement: path2.resolve(dirname8, "node_modules/react/jsx-runtime.js")
    },
    {
      find: /^react\/jsx-dev-runtime$/,
      replacement: path2.resolve(
        dirname8,
        "node_modules/react/jsx-dev-runtime.js"
      )
    },
    {
      find: /^react-dom$/,
      replacement: path2.resolve(dirname8, "node_modules/react-dom")
    },
    {
      find: /^react-dom\/client$/,
      replacement: path2.resolve(dirname8, "node_modules/react-dom/client.js")
    },
    { find: /^pino$/, replacement: path2.resolve(dirname8, "node_modules/pino") },
    {
      find: /^pino\/browser\.js$/,
      replacement: path2.resolve(dirname8, "node_modules/pino/browser.js")
    },
    {
      find: /^hono$/,
      replacement: path2.resolve(dirname8, "node_modules/hono/dist/index.js")
    },
    {
      find: /^hono\/streaming$/,
      replacement: path2.resolve(
        dirname8,
        "node_modules/hono/dist/helper/streaming/index.js"
      )
    },
    {
      find: /^@hono\/node-server$/,
      replacement: path2.resolve(dirname8, "node_modules/@hono/node-server")
    },
    {
      find: /^atomically$/,
      replacement: path2.resolve(dirname8, "node_modules/atomically")
    },
    {
      find: /^chokidar$/,
      replacement: path2.resolve(dirname8, "node_modules/chokidar")
    },
    {
      find: /^@napi-rs\/keyring$/,
      replacement: path2.resolve(dirname8, "node_modules/@napi-rs/keyring")
    },
    // `express` and `yaml` resolve from the **repo root**, unlike every pin
    // above. Both are reached only through `test-servers/src` — express by the
    // http/oauth servers, yaml by `load-config.ts` — which is root-owned code
    // with no manifest of its own, so the root is where they are declared and
    // the only place a client's resolution chain is guaranteed to find them.
    // Pointing these at `<client>/node_modules` is what broke when the MCP
    // packages moved to the root (#1970): express was never declared by a client
    // at all, it arrived in `clients/cli` as a peer of `express-rate-limit`
    // under `@modelcontextprotocol/server-legacy`, so removing that manifest
    // entry took express with it and every cli test that spawns a test server
    // failed to resolve it.
    {
      find: /^express$/,
      replacement: path2.resolve(repoRoot, "node_modules/express")
    },
    {
      find: /^yaml$/,
      replacement: path2.resolve(repoRoot, "node_modules/yaml")
    }
  ];
  const projectResolve = {
    alias: [
      ...Object.entries(sharedAliases).map(([find, replacement]) => ({
        find,
        replacement
      })),
      ...nodeModulesAliases
    ],
    dedupe: sharedDedupe
  };
  return {
    repoRoot,
    sharedAliases,
    sharedDedupe,
    nodeModulesAliases,
    projectResolve
  };
}

// server/start-vite-dev-server.ts
var __dirname2 = dirname5(fileURLToPath4(import.meta.url));
async function startViteDevServer(config) {
  const root = resolve3(join6(__dirname2, ".."));
  clearViteDepsCache(root);
  const { repoRoot, sharedAliases, sharedDedupe, nodeModulesAliases } = vitestSharedPaths(root);
  const inlineConfig = {
    optimizeDeps: getViteDevOptimizeDeps(),
    configFile: false,
    root,
    resolve: {
      alias: [
        ...Object.entries(sharedAliases).map(([find, replacement]) => ({
          find,
          replacement
        })),
        ...nodeModulesAliases
      ],
      dedupe: sharedDedupe
    },
    server: {
      port: config.port,
      host: config.hostname,
      // `strictPort: true` (matching `vite.config.ts`) so a busy port fails
      // loudly instead of silently binding a different one — the origin
      // allow-list and the sandbox `frame-ancestors` are derived from
      // `config.port`, so a drifted bind would 403 every connect and CSP-block
      // the MCP Apps iframe while the banner advertises the unusable port.
      strictPort: true,
      // Allow Vite to serve source files from the repo root (core/ lives
      // outside clients/web), matching `vite.config.ts`'s `server.fs.allow`.
      fs: {
        allow: [repoRoot]
      }
    },
    plugins: [react(), honoMiddlewarePlugin(config)]
  };
  const server = await createServer2(inlineConfig);
  await server.listen();
  return {
    async close() {
      await server.close();
    }
  };
}

// server/server.ts
import { readFileSync as readFileSync5 } from "fs";
import { join as join7, dirname as dirname6 } from "path";
import { fileURLToPath as fileURLToPath5 } from "url";
import { randomBytes as randomBytes2 } from "crypto";
import open2 from "open";
import { serve } from "@hono/node-server";
import { serveStatic } from "@hono/node-server/serve-static";
import { Hono as Hono2 } from "hono";
var __filename = fileURLToPath5(import.meta.url);
var __dirname3 = dirname6(__filename);
async function startHonoServer(config) {
  const sandboxController = createSandboxController({
    port: config.sandboxPort,
    host: config.sandboxHost,
    allowedOrigins: config.allowedOrigins
  });
  await sandboxController.start();
  const resolvedAuthToken = config.authToken || (config.dangerouslyOmitAuth ? "" : randomBytes2(32).toString("hex"));
  const rootPath = config.staticRoot ?? __dirname3;
  const { app: apiApp, close: closeApi } = createRemoteApp({
    authToken: config.dangerouslyOmitAuth ? void 0 : resolvedAuthToken,
    dangerouslyOmitAuth: config.dangerouslyOmitAuth,
    storageDir: config.storageDir,
    mcpConfigPath: config.mcpConfigPath,
    writable: config.writable,
    initialServers: config.initialServers ?? void 0,
    allowedOrigins: config.allowedOrigins,
    sandboxUrl: sandboxController.getUrl() ?? void 0,
    logger: config.logger,
    initialConfig: webServerConfigToInitialPayload(config)
  });
  const app = new Hono2();
  app.use("/api/*", async (c) => {
    return apiApp.fetch(c.req.raw);
  });
  const serveIndexHtml = (c) => {
    const indexPath = join7(rootPath, "index.html");
    const html = readFileSync5(indexPath, "utf-8");
    c.header("Cache-Control", "no-store");
    return c.html(injectAuthToken(html, resolvedAuthToken));
  };
  app.get("/", async (c) => {
    try {
      return serveIndexHtml(c);
    } catch (error) {
      console.error("Error serving index.html:", error);
      return c.notFound();
    }
  });
  app.use("/*", serveStatic({ root: rootPath }));
  app.get("*", async (c) => {
    if (c.req.path.startsWith("/api")) {
      return c.notFound();
    }
    try {
      return serveIndexHtml(c);
    } catch (error) {
      console.error("Error serving index.html:", error);
      return c.notFound();
    }
  });
  const httpServer = serve(
    {
      fetch: app.fetch,
      port: config.port,
      hostname: config.hostname
    },
    (info) => {
      const sandboxUrl = sandboxController.getUrl();
      const url = printServerBanner(
        config,
        info.port,
        resolvedAuthToken,
        sandboxUrl ?? void 0
      );
      if (config.autoOpen) {
        open2(url);
      }
    }
  );
  httpServer.on("error", (err) => {
    if (err.message.includes("EADDRINUSE")) {
      console.error(
        `MCP Inspector PORT IS IN USE at http://${formatHostForUrl(config.hostname)}:${config.port}`
      );
      process.exit(1);
    } else {
      throw err;
    }
  });
  return {
    async close() {
      await closeApi();
      await sandboxController.close();
      if ("closeAllConnections" in httpServer) {
        httpServer.closeAllConnections();
      }
      await new Promise((resolve6, reject) => {
        httpServer.close((err) => err ? reject(err) : resolve6());
      });
    }
  };
}

// server/ensure-web-build.ts
import { existsSync as existsSync3 } from "fs";
import { join as join8 } from "path";
import { spawnSync } from "child_process";
function runViteBuild(webRoot, log) {
  const result = spawnSync("npm", ["run", "build:client"], {
    cwd: webRoot,
    stdio: "inherit",
    // npm resolves to npm.cmd on Windows, which needs a shell to be spawnable.
    shell: process.platform === "win32"
  });
  if (result.error) {
    log(`Web build failed to start: ${result.error.message}`);
  }
  return result.status;
}
function ensureWebBuild(webRoot, distRoot, deps = {}) {
  const exists = deps.exists ?? existsSync3;
  const build = deps.build ?? runViteBuild;
  const log = deps.log ?? ((message) => console.log(message));
  const indexHtml = join8(distRoot, "index.html");
  if (exists(indexHtml)) return;
  log(
    "No production web build found at clients/web/dist. Building the web UI now (first run only; this can take a minute)..."
  );
  const status = build(webRoot, log);
  if (status !== 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."
    );
  }
}

// server/run-web.ts
var __dirname4 = dirname7(fileURLToPath6(import.meta.url));
function ensureStdioCwd(config) {
  if ((config.type === "stdio" || config.type === void 0) && config.command && !config.cwd) {
    return { ...config, cwd: resolve4(process.cwd()) };
  }
  return config;
}
function deriveSeedServerId(config) {
  if (config.type === "sse" || config.type === "streamable-http") {
    try {
      return new URL(config.url).host || "server";
    } catch {
      return "server";
    }
  }
  return config.command?.split(/[\\/]/).pop() || "server";
}
async function runWeb(argv) {
  const program = new Command();
  const argSeparatorIndex = argv.indexOf("--");
  let preArgs = argv;
  let postArgs = [];
  if (argSeparatorIndex !== -1) {
    preArgs = argv.slice(0, argSeparatorIndex);
    postArgs = argv.slice(argSeparatorIndex + 1);
  }
  program.name("mcp-inspector-web").description("Web UI for MCP Inspector").allowExcessArguments().allowUnknownOption().option(
    "-e <env>",
    "environment variables in KEY=VALUE format",
    parseKeyValuePair,
    {}
  ).option("--catalog <path>", "writable catalog file path").option("--config <path>", "read-only session config file path").option("--server <name>", "server name from config file").option("--transport <type>", "transport type (stdio, sse, http)").option("--server-url <url>", "server URL for SSE/HTTP transport").option("--cwd <path>", "working directory for stdio server process").option(
    "--header <headers...>",
    'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
    parseHeaderPair,
    {}
  ).option("--dev", "run in development mode (Vite)").parse(preArgs);
  const opts = program.opts();
  const args = program.args;
  const target = [...args, ...postArgs];
  const isDev = !!opts.dev;
  const catalogPath = opts.catalog ?? process.env.MCP_CATALOG_PATH;
  const hasCatalog = !!catalogPath;
  const hasConfig = !!opts.config;
  const hasHeaders = !!opts.header && Object.keys(opts.header).length > 0;
  const hasAdHocServer = target.length > 0 || !!opts.serverUrl || !!opts.transport && opts.transport !== "stdio";
  const conflict = hasCatalog && hasConfig ? "--catalog and --config are mutually exclusive. --catalog is the writable catalog; --config is a read-only session file." : hasCatalog && hasAdHocServer ? "--catalog cannot be combined with an ad-hoc server URL/command." : hasCatalog && hasHeaders ? "--header cannot be combined with --catalog. Set per-server headers in the catalog file." : hasConfig && hasAdHocServer ? "--config cannot be combined with an ad-hoc server URL/command. --config selects a read-only session file." : hasConfig && hasHeaders ? "--header cannot be combined with --config. Set per-server headers inside the config file instead." : null;
  if (conflict) {
    console.error(`Error: ${conflict}`);
    process.exit(1);
  }
  let initialMcpConfig = null;
  let mcpConfigPath;
  let writable = true;
  let initialServers = null;
  if (hasCatalog) {
    if (opts.server) {
      console.warn(
        "Note: --server has no effect on the web UI yet; it lists every server in the catalog."
      );
    }
    mcpConfigPath = resolve4(process.cwd(), catalogPath);
    writable = true;
  } else if (hasConfig) {
    if (opts.server) {
      console.warn(
        "Note: --server has no effect on the web UI yet; it lists every server in the file."
      );
    }
    const resolvedConfigPath = resolve4(process.cwd(), opts.config);
    try {
      resolveServerConfigs({ configPath: resolvedConfigPath }, "multi");
    } catch (err) {
      const message = err instanceof Error ? err.message : "Could not load config file.";
      console.error("Error:", message);
      process.exit(1);
    }
    mcpConfigPath = resolvedConfigPath;
    writable = false;
  } else if (hasAdHocServer) {
    let config;
    try {
      const serverOptions = {
        serverName: opts.server,
        target: target.length > 0 ? target : void 0,
        transport: opts.transport,
        serverUrl: opts.serverUrl,
        cwd: opts.cwd,
        env: opts.e
      };
      const resolved = resolveServerConfigs(serverOptions, "single")[0];
      if (!resolved) {
        console.error(
          "Error: Could not resolve server config. Pass a command/URL, or use --catalog / --config to serve a file."
        );
        process.exit(1);
      }
      config = ensureStdioCwd(resolved);
    } catch (err) {
      const message = err instanceof Error ? err.message : "Could not resolve server config.";
      console.error("Error:", message);
      process.exit(1);
    }
    if (hasHeaders && (config.type === "stdio" || config.type === void 0)) {
      console.error(
        "Error: --header only applies to HTTP/SSE servers; stdio servers take no HTTP headers."
      );
      process.exit(1);
    }
    initialMcpConfig = config;
    const entry = hasHeaders && config.type !== "stdio" && config.type !== void 0 ? { ...config, headers: opts.header } : { ...config };
    initialServers = {
      mcpServers: { [deriveSeedServerId(config)]: entry }
    };
    writable = false;
  } else if (hasHeaders) {
    console.error(
      "Error: --header requires an ad-hoc --server-url or command target."
    );
    process.exit(1);
  }
  let webConfig;
  try {
    webConfig = buildWebServerConfig({
      initialMcpConfig,
      mcpConfigPath,
      writable,
      initialServers
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : (
      /* v8 ignore next -- buildWebServerConfig only ever throws Error instances; this fallback is a defensive guard for the impossible non-Error throw. */
      "Invalid web server configuration."
    );
    console.error("Error:", message);
    process.exit(1);
  }
  const webRoot = join9(__dirname4, "..");
  const distRoot = join9(webRoot, "dist");
  if (!isDev) {
    webConfig.staticRoot = distRoot;
  }
  console.log(
    isDev ? "Starting MCP inspector in development mode..." : "Starting MCP inspector..."
  );
  let handle;
  try {
    if (isDev) {
      handle = await startViteDevServer(webConfig);
    } else {
      ensureWebBuild(webRoot, distRoot);
      handle = await startHonoServer(webConfig);
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : "Web client failed to start.";
    console.error("Error:", message);
    process.exit(1);
  }
  const shutdown = () => {
    void handle.close().then(() => process.exit(0));
  };
  process.on("SIGINT", shutdown);
  process.on("SIGTERM", shutdown);
  return new Promise(() => {
  });
}

// src/index.ts
var __filename2 = fileURLToPath7(import.meta.url);
var isMain = process.argv[1] !== void 0 && resolve5(process.argv[1]) === resolve5(__filename2);
if (isMain) {
  runWeb(process.argv).then((code) => process.exit(code ?? 0)).catch((e) => {
    console.error(e);
    process.exit(1);
  });
}
export {
  runWeb
};
