#!/usr/bin/env node

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

// src/cli.ts
import { Command } from "commander";

// src/utils/awaitable-log.ts
function awaitableLog(logValue) {
  return new Promise((resolve7) => {
    process.stdout.write(logValue, () => {
      resolve7();
    });
  });
}
function awaitableError(logValue) {
  return new Promise((resolve7) => {
    process.stderr.write(logValue, () => {
      resolve7();
    });
  });
}

// ../../core/mcp/types.ts
function isTerminalStatus(status) {
  return status === "disconnected" || status === "error";
}
var INACTIVE_SUBSCRIPTION_STREAM_STATE = {
  active: false,
  status: "ended",
  honoredUris: []
};
var DEFAULT_TASK_TTL_MS = 6e4;
var DEFAULT_MAX_FETCH_REQUESTS = 1e3;
var DEFAULT_MODERN_LOG_LEVEL = "debug";
function resolveModernLogLevel(settings) {
  const level = settings?.modernLogLevel ?? DEFAULT_MODERN_LOG_LEVEL;
  return level === "off" ? void 0 : level;
}
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);
}
var MODERN_PROTOCOL_VERSION = "2026-07-28";
function eraToVersionNegotiation(era) {
  switch (era) {
    case "auto":
      return { mode: "auto" };
    case "modern":
      return { mode: { pin: MODERN_PROTOCOL_VERSION } };
    case "legacy":
      return { mode: "legacy" };
  }
}

// src/handlers/connect-timeout.ts
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
function withConnectTimeout(settings, connectionTimeout) {
  if (connectionTimeout === void 0) return settings;
  if (settings) return { ...settings, connectionTimeout };
  return {
    headers: [],
    metadata: {},
    env: [],
    connectionTimeout,
    requestTimeout: 0,
    taskTtl: DEFAULT_TASK_TTL_MS,
    maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS,
    autoRefreshOnListChanged: false,
    paginatedLists: false,
    roots: []
  };
}

// ../../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/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 SecretStoreUnavailableError = class extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = "SecretStoreUnavailableError";
  }
};
var KeychainUnavailableError = class extends SecretStoreUnavailableError {
  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.`;
};
async function probeKeyringAvailable() {
  const keyring = await loadKeyring();
  if (!keyring.ok) {
    const err = keyring.err;
    return {
      available: false,
      detail: err instanceof Error ? err.message : String(err)
    };
  }
  try {
    const entry = new keyring.mod.AsyncEntry(SERVICE_NAME, PROBE_ACCOUNT);
    await entry.getPassword();
    return { available: true };
  } catch (err) {
    return {
      available: false,
      detail: err instanceof Error ? err.message : String(err)
    };
  }
}
var PROBE_ACCOUNT = "__inspector:probe";
var KeyringSecretStore = class {
  /**
   * The intolerant read. Same lookup as {@link get}, minus the catch — a
   * keychain that cannot answer must not be reported as an empty one to a
   * caller that is about to write based on the answer.
   */
  async getStrict(serverId, field) {
    try {
      const keyring = await loadKeyring();
      if (!keyring.ok) throw new KeychainUnavailableError(keyring.err);
      const entry = new keyring.mod.AsyncEntry(
        SERVICE_NAME,
        buildAccount(serverId, field)
      );
      return await entry.getPassword() ?? null;
    } catch (err) {
      if (err instanceof KeychainUnavailableError) throw err;
      throw new KeychainUnavailableError(err);
    }
  }
  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);
    }
  }
};
async function secretStoreGetStrict(store, serverId, field) {
  return store.getStrict ? store.getStrict(serverId, field) : store.get(serverId, field);
}
async function secretStoreGetMany(store, requests) {
  if (store.getMany) return store.getMany(requests);
  const out = {};
  await Promise.all(
    requests.map(async ({ serverId, fields }) => {
      const entries = await Promise.all(
        fields.map(
          async (field) => [field, await store.get(serverId, field)]
        )
      );
      const found = {};
      for (const [field, value] of entries) {
        if (value !== null) found[field] = value;
      }
      out[serverId] = found;
    })
  );
  return out;
}
async function secretStoreSetMany(store, serverId, values) {
  if (store.setMany) return store.setMany(serverId, values);
  await Promise.all(
    Object.entries(values).map(
      ([field, value]) => store.set(serverId, field, value)
    )
  );
}
async function secretStoreIsDurable(store) {
  return store.isDurable ? store.isDurable() : true;
}
var InMemorySecretStore = class {
  map = /* @__PURE__ */ new Map();
  async get(serverId, field) {
    return this.map.get(buildAccount(serverId, field)) ?? null;
  }
  async set(serverId, field, value) {
    this.map.set(buildAccount(serverId, field), value);
  }
  async delete(serverId, field) {
    this.map.delete(buildAccount(serverId, field));
  }
  async deleteAllForServer(serverId) {
    const prefix = `${serverId}:`;
    for (const key of [...this.map.keys()]) {
      if (key.startsWith(prefix)) this.map.delete(key);
    }
  }
};
var SessionSecretStore = class extends InMemorySecretStore {
  async isDurable() {
    return false;
  }
};

// ../../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-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/json/jsonUtils.ts
function isSerializableJson(value) {
  if (value === null) return true;
  switch (typeof value) {
    case "string":
    case "boolean":
      return true;
    case "number":
      return Number.isFinite(value);
    case "object":
      return Array.isArray(value) ? value.every(isSerializableJson) : Object.values(value).every(
        isSerializableJson
      );
    default:
      return false;
  }
}
function toRecord(value) {
  return value;
}
function convertParameterValue(value, schema) {
  if (!value) {
    return value;
  }
  if (schema.type === "number" || schema.type === "integer") {
    return Number(value);
  }
  if (schema.type === "boolean") {
    return value.toLowerCase() === "true";
  }
  if (schema.type === "object" || schema.type === "array") {
    try {
      return JSON.parse(value);
    } catch {
      return value;
    }
  }
  return value;
}
function convertToolParameters(tool, params) {
  const result = {};
  const properties = tool.inputSchema?.properties || {};
  for (const [key, value] of Object.entries(params)) {
    const paramSchema = properties[key];
    if (paramSchema) {
      result[key] = convertParameterValue(value, paramSchema);
    } else {
      result[key] = value;
    }
  }
  return result;
}
function convertPromptArguments(args) {
  const stringArgs = {};
  for (const [key, value] of Object.entries(args)) {
    if (typeof value === "string") {
      stringArgs[key] = value;
    } else if (value === null || value === void 0) {
      stringArgs[key] = String(value);
    } else {
      stringArgs[key] = JSON.stringify(value);
    }
  }
  return stringArgs;
}

// ../../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 normalizeStoredMetadata(metadata) {
  if (metadata === void 0 || metadata === null) return {};
  if (Array.isArray(metadata)) {
    const out = {};
    for (const entry of metadata) {
      if (typeof entry !== "object" || entry === null || typeof entry.key !== "string") {
        console.warn(
          "Ignoring malformed legacy metadata entry (expected `{ key, value }`)."
        );
        continue;
      }
      const { key, value } = entry;
      if (key.trim() === "") continue;
      const resolved2 = value === void 0 ? "" : value;
      if (!keepSerializable(key, resolved2)) continue;
      defineMetadataKey(out, key, resolved2);
    }
    return out;
  }
  if (typeof metadata === "object") {
    const out = {};
    for (const [key, value] of Object.entries(metadata)) {
      if (keepSerializable(key, value)) defineMetadataKey(out, key, value);
    }
    return out;
  }
  console.warn(
    `Ignoring malformed \`metadata\` (expected a JSON object, got ${describeJsonType(metadata)}).`
  );
  return {};
}
function defineMetadataKey(out, key, value) {
  Object.defineProperty(out, key, {
    value,
    writable: true,
    enumerable: true,
    configurable: true
  });
}
function keepSerializable(key, value) {
  if (isSerializableJson(value)) return true;
  console.warn(
    `Ignoring metadata key "${key}" \u2014 its value is not JSON that can be sent (got ${describeJsonType(value)}; note \`1e400\` parses to Infinity).`
  );
  return false;
}
function describeJsonType(value) {
  if (value === null) return "null";
  if (Array.isArray(value)) return "array";
  if (typeof value === "number" && !Number.isFinite(value)) {
    return Number.isNaN(value) ? "NaN" : "a non-finite number";
  }
  return typeof value;
}
function cleanAuthorizationParams(params) {
  if (params === void 0) return void 0;
  if (typeof params !== "object" || params === null || Array.isArray(params)) {
    console.warn(
      "Ignoring `oauth.authorizationParams`: expected an object of string values, got",
      Array.isArray(params) ? "array" : typeof params
    );
    return void 0;
  }
  const out = {};
  for (const [key, value] of Object.entries(params)) {
    if (typeof value !== "string") {
      console.warn(
        `Dropping \`oauth.authorizationParams.${key}\`: expected a string value, got ${typeof value}.`
      );
      continue;
    }
    if (key.trim() === "") continue;
    out[key] = value;
  }
  return Object.keys(out).length > 0 ? out : void 0;
}
function oauthAuthorizationParamsFromSettings(settings) {
  if (!settings.oauthAuthorizationParams) return void 0;
  const record = envPairsToRecord(settings.oauthAuthorizationParams);
  return Object.keys(record).length > 0 ? record : void 0;
}
function cleanEndpointOverride(value, field) {
  if (value === void 0) return void 0;
  if (typeof value !== "string") {
    console.warn(
      `Ignoring \`oauth.${field}\`: expected a string, got ${typeof value}.`
    );
    return void 0;
  }
  return value.trim() || void 0;
}
function oauthEndpointOverridesFromSettings(settings) {
  const authorizationUrl = settings.oauthAuthorizationUrl?.trim();
  const tokenUrl = settings.oauthTokenUrl?.trim();
  if (!authorizationUrl && !tokenUrl) return void 0;
  return {
    ...authorizationUrl && { authorizationUrl },
    ...tokenUrl && { tokenUrl }
  };
}
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: normalizeStoredMetadata(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;
  const authorizationParams = cleanAuthorizationParams(
    stored.oauth?.authorizationParams
  );
  if (authorizationParams) {
    settings.oauthAuthorizationParams = envRecordToPairs(authorizationParams);
  }
  const authorizationUrl = cleanEndpointOverride(
    stored.oauth?.authorizationUrl,
    "authorizationUrl"
  );
  if (authorizationUrl) settings.oauthAuthorizationUrl = authorizationUrl;
  const tokenUrl = cleanEndpointOverride(stored.oauth?.tokenUrl, "tokenUrl");
  if (tokenUrl) settings.oauthTokenUrl = tokenUrl;
  if (stored.oauth?.onInsufficientScope) {
    settings.oauthOnInsufficientScope = stored.oauth.onInsufficientScope;
  }
  if (stored.oauth?.enterpriseManaged === true) {
    settings.enterpriseManaged = true;
  }
  if (stored.oauth?.requestRefreshToken === false) {
    settings.oauthRequestRefreshToken = false;
  }
  if (stored.cwd) settings.cwd = stored.cwd;
  return settings;
}
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 mcpConfigToServerEntries(config) {
  return Object.entries(config.mcpServers).map(([id, raw]) => {
    const inspectorFields = {};
    const sdkOnly = {};
    const inspectorRecord = toRecord(inspectorFields);
    for (const [k, v] of Object.entries(toRecord(raw))) {
      if (INSPECTOR_FIELD_KEYS.has(k)) {
        inspectorRecord[k] = v;
      } else {
        sdkOnly[k] = v;
      }
    }
    const normalizedConfig = normalizeServerType(
      sdkOnly
    );
    const entry = {
      id,
      name: id,
      config: normalizedConfig,
      connection: { status: "disconnected" }
    };
    const isStdio = normalizedConfig.type === "stdio" || normalizedConfig.type === void 0;
    const stdioConfig = isStdio ? normalizedConfig : void 0;
    const settings = storedFieldsToInspectorSettings({
      ...inspectorFields,
      env: stdioConfig?.env,
      cwd: stdioConfig?.cwd
    });
    if (settings !== void 0) entry.settings = settings;
    return entry;
  });
}
var isStdioStored = (stored) => stored.type === "stdio" || stored.type === void 0;
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;
}

// ../../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 serverSourceConflict(flags) {
  if (flags.hasCatalog && flags.hasConfig) {
    return "--catalog and --config are mutually exclusive. --catalog is the writable catalog; --config is a read-only session file.";
  }
  if (flags.hasCatalog && flags.hasAdHoc) {
    return "--catalog cannot be combined with an ad-hoc server URL/command.";
  }
  if (flags.hasConfig && flags.hasAdHoc) {
    return "--config cannot be combined with an ad-hoc server URL/command. --config selects a read-only session file.";
  }
  return null;
}
function readServerListFile(configPath, writable) {
  return loadMcpServersConfig(configPath, writable);
}
function withDefaultCatalogPath(options) {
  if (options.catalogPath?.trim() || options.configPath?.trim() || hasAdHocServerOptions(options)) {
    return options;
  }
  return { ...options, catalogPath: getDefaultMcpConfigPath() };
}
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 [];
}

// ../../core/auth/node/secret-store-selection.ts
import { randomUUID } from "crypto";
import * as fsSync from "fs";
import * as fs4 from "fs/promises";
import * as path4 from "path";

// ../../core/auth/secret-storage-info.ts
function secretStorageLabel(info) {
  switch (info.kind) {
    case "keyring":
      return "OS keychain";
    case "file":
      if (info.encryptionUnknown !== void 0) return "File (unreadable)";
      return info.plaintext ? "File (unencrypted)" : "File (encrypted)";
    case "memory":
      return "Memory (this session only)";
  }
}
function secretStorageCaveat(info) {
  if (info.kind === "memory") {
    return "Secrets are not written anywhere and are lost on exit.";
  }
  if (info.looseMode !== void 0) {
    const mode = info.looseMode.toString(8).padStart(4, "0");
    if (info.encryptionUnknown !== void 0) {
      return `The secrets file is mode ${mode}, not 0600, and could not be tightened \u2014 others can copy it, and whether that exposes the secrets depends on encryption this build could not determine (${info.encryptionUnknown}).`;
    }
    return info.plaintext === false ? `The secrets file is mode ${mode}, not 0600, and could not be tightened \u2014 others can copy it, and the passphrase is then the only thing protecting its contents.` : `The secrets file is mode ${mode}, not 0600, and could not be tightened \u2014 anyone who can read it can read the secrets in it.`;
  }
  if (info.permissionsUnknown !== void 0) {
    return `The secrets file's permissions could not be checked (${info.permissionsUnknown}), so it is not known whether others can read it.`;
  }
  if (info.encryptionUnknown !== void 0) {
    return `The secrets file could not be read (${info.encryptionUnknown}). Saving a secret will fail rather than overwrite it.`;
  }
  if (info.plaintext) {
    return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY to encrypt them.";
  }
  return void 0;
}
function secretStorageSummary(info) {
  const parts = [secretStorageLabel(info)];
  if (info.path) parts.push(`at ${info.path}`);
  if (info.reason === "fallback") {
    parts.push("\u2014 fell back from the OS keychain");
  }
  return parts.join(" ");
}

// ../../core/auth/node/file-secret-store.ts
import * as crypto2 from "crypto";
import * as fs3 from "fs/promises";
import * as path3 from "path";

// ../../core/auth/node/file-lock.ts
import nodeFs, { statSync, rmdirSync } from "fs";
import * as fs2 from "fs/promises";
import * as path2 from "path";
import properLockfile from "proper-lockfile";
var STALE_MS = 1e4;
var RETRY = {
  retries: 20,
  factor: 2,
  minTimeout: 20,
  maxTimeout: 1e3
};
var RETRY_BUDGET_MS = (() => {
  let total = 0;
  let delay = RETRY.minTimeout;
  for (let i = 0; i < RETRY.retries; i++) {
    total += Math.min(delay, RETRY.maxTimeout);
    delay *= RETRY.factor;
  }
  return total;
})();
var warned = /* @__PURE__ */ new Set();
function warnOnce(message) {
  if (warned.has(message)) return;
  warned.add(message);
  console.warn(`[mcp-inspector] ${message}`);
}
function isStuckOrHeld(err, lockPath) {
  return isHeldElsewhere(err) || identifySync(lockPath) !== null;
}
var isHeldElsewhere = (err) => err.code === "ELOCKED";
var describeError = (err) => err instanceof Error ? err.message : String(err);
var lockPathOf = (target) => `${target}.lock`;
function identifySync(lockPath) {
  try {
    const stat2 = statSync(lockPath);
    return { ino: stat2.ino, birthtimeMs: stat2.birthtimeMs };
  } catch {
    return null;
  }
}
function guardedFs(lockPath, owned, onRefused) {
  const mine = () => {
    if (owned.id === null) return true;
    const now = identifySync(lockPath);
    if (now === null) return false;
    return now.ino === owned.id.ino && now.birthtimeMs === owned.id.birthtimeMs;
  };
  const removeIfMine = () => {
    if (!mine()) {
      onRefused();
      return;
    }
    rmdirSync(lockPath);
  };
  return {
    ...nodeFs,
    // **Identity is captured here, not after `lock()` resolves.** `mkdir` is
    // the moment the directory becomes ours, and it is synchronous with
    // respect to this process: recording it in the callback, before yielding,
    // leaves no window. Capturing after the acquire promise settled — as this
    // did originally — spans the library's own `utimes`/`stat` probe, and a
    // waiter replacing our directory inside that span would have us record
    // *the winner's* identity as our own, after which the guard below would
    // cheerfully delete their lock.
    mkdir: (p, cb) => {
      nodeFs.mkdir(p, (err) => {
        if (!err) owned.id = identifySync(lockPath);
        cb(err);
      });
    },
    // Reported as success when refused: the library's bookkeeping should
    // forget this lock either way. Leaving it registered would hand the
    // `signal-exit` handler a record pointing at the winner's directory.
    rmdir: (_p, cb) => {
      try {
        removeIfMine();
        cb(null);
      } catch (err) {
        cb(err);
      }
    },
    /* v8 ignore next 3 -- @preserve: only reachable from proper-lockfile's
       signal-exit handler, i.e. at real process exit, which no in-process
       test can drive. Its logic is `removeIfMine`, covered via `rmdir`. */
    rmdirSync: () => {
      removeIfMine();
    }
  };
}
function acquire(target, retries, fsShim) {
  return properLockfile.lock(target, {
    realpath: false,
    stale: STALE_MS,
    retries,
    // Every directory removal the library performs — on release and from its
    // exit handler — routes through here. See `guardedFs`.
    fs: fsShim,
    // The library's default `onCompromised` *throws* — from a timer, with no
    // caller on the stack, so it lands as an uncaught exception and takes the
    // process down. This is also the library's *only* signal for the
    // stale-takeover race described at the top of this file — someone
    // declared our lock stale and replaced it — so it is the one place a user
    // learns the guarantee was lost. Worth saying; not worth killing an
    // Inspector session over.
    onCompromised: (err) => warnOnce(
      `The lock on the secrets file at ${target} was taken over by another process while a write was in progress (${err.message}). If a secret you just saved is missing, save it again.`
    )
  });
}
async function isFileLockHeld(filePath) {
  try {
    return await properLockfile.check(path2.resolve(filePath), {
      realpath: false,
      stale: STALE_MS
    });
  } catch {
    return false;
  }
}
async function openSecretFileLock(filePath) {
  const target = path2.resolve(filePath);
  await fs2.mkdir(path2.dirname(target), { recursive: true }).catch(() => {
  });
  const owned = { id: null };
  const fsShim = guardedFs(
    lockPathOf(target),
    owned,
    () => warnOnce(
      `The lock on the secrets file at ${target} was taken over by another process while a write was in progress, so it was left alone rather than removed. If a secret you just saved is missing, save it again.`
    )
  );
  let release;
  try {
    release = await acquire(target, 0, fsShim).catch((err) => {
      if (!isStuckOrHeld(err, lockPathOf(target))) throw err;
      return acquire(target, RETRY, fsShim);
    });
  } catch (err) {
    if (isStuckOrHeld(err, lockPathOf(target))) {
      throw new SecretStoreUnavailableError(
        `Could not save to the secrets file at ${target}: its lock (${lockPathOf(target)}) was still held after the ${Math.round(RETRY_BUDGET_MS / 1e3)} seconds this save waited. Its secrets are intact; the value you just entered was not saved. If no other Inspector is running, remove that lock and try again.`
      );
    }
    warnOnce(
      `Could not take a lock on the secrets file at ${target} (${describeError(err)}), so writes to it are not protected against another process writing at the same moment.`
    );
    return null;
  }
  return async () => {
    try {
      await release();
    } catch (err) {
      if (err.code === "ERELEASED") return;
      warnOnce(
        // **Conditional, not an instruction.** Two blanket versions of this
        // have now been wrong in opposite directions: "it expires on its own"
        // (false whenever the same `rmdir` that blocked us also blocks stale
        // takeover — `ENOTEMPTY`, `EACCES`, `EPERM`, `EROFS`), and "remove it
        // by hand" (false whenever the failure was transient, since the path
        // may by then hold a *different, live* Inspector's lock, and deleting
        // that destroys the exclusion of a process that did nothing wrong).
        //
        // `proper-lockfile` forwards whatever the filesystem returned, and
        // nothing here can tell a permanent refusal from a passing one. So
        // this reports what happened and states the two conditions the
        // operator can check for themselves, rather than asserting an outcome
        // this code does not know.
        `Could not release the lock on the secrets file at ${target} (${describeError(err)}). It may clear on its own \u2014 stale takeover reclaims a lock through the same directory removal, so it will not if whatever blocked this persists. If saves keep failing against this file and no other Inspector is running, remove ${lockPathOf(target)} by hand.`
      );
    }
  };
}
async function withSecretFileLock(filePath, fn) {
  const release = await openSecretFileLock(filePath);
  if (release === null) return fn();
  try {
    return await fn();
  } finally {
    await release();
  }
}

// ../../core/auth/node/file-secret-store.ts
var scrypt2 = (passphrase, salt, keylen, options) => new Promise((resolve7, reject) => {
  crypto2.scrypt(passphrase, salt, keylen, options, (err, key) => {
    if (err) reject(err);
    else resolve7(key);
  });
});
var FORMAT_VERSION = 1;
var CIPHER = "aes-256-gcm";
var KEY_BYTES = 32;
var IV_BYTES = 12;
var GCM_TAG_BYTES = 16;
var SCRYPT_N = 16384;
var SCRYPT_R = 8;
var SCRYPT_P = 1;
var SECRET_KEY_ENV = "MCP_INSPECTOR_SECRET_KEY";
var buildAccount2 = (serverId, field) => `${serverId}:${field}`;
function asSecretMap(value, filePath) {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    throw new SecretStoreUnavailableError(
      `The secrets file at ${filePath} does not hold a secret map (found ${Array.isArray(value) ? "an array" : typeof value}). Refusing to write, which would replace it. Move the file aside to start over.`
    );
  }
  for (const [key, entry] of Object.entries(value)) {
    if (typeof entry !== "string") {
      throw new SecretStoreUnavailableError(
        `The secrets file at ${filePath} holds a non-string value for "${key}". Refusing to write, which would replace it. Move the file aside to start over.`
      );
    }
  }
  return { ...value };
}
var SecretFileKeyMismatchError = class extends SecretStoreUnavailableError {
  constructor(filePath, hasKey) {
    super(
      hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV}. Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set. Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
    );
    this.name = "SecretFileKeyMismatchError";
  }
};
var encodeParts = (iv, tag, body) => `${iv.toString("base64")}.${tag.toString("base64")}.${body.toString("base64")}`;
function decodeParts(payload) {
  const parts = payload.split(".");
  if (parts.length !== 3) return null;
  const [iv, tag, body] = parts;
  return {
    iv: Buffer.from(iv, "base64"),
    tag: Buffer.from(tag, "base64"),
    body: Buffer.from(body, "base64")
  };
}
var MAX_WRITE_ATTEMPTS = 5;
var fileQueues = /* @__PURE__ */ new Map();
function sameMap(a, b) {
  const ak = Object.keys(a);
  if (ak.length !== Object.keys(b).length) return false;
  return ak.every(
    (k) => Object.prototype.hasOwnProperty.call(b, k) && a[k] === b[k]
  );
}
var isPositiveInt = (v) => typeof v === "number" && Number.isInteger(v) && v > 0;
function encryptedEnvelopeProblem(parsed) {
  if (!parsed.kdf || typeof parsed.data !== "string") {
    return "encrypted envelope is missing its kdf or data";
  }
  const parts = decodeParts(parsed.data);
  if (!parts) return "encrypted payload is not iv.tag.ciphertext";
  if (parts.iv.length !== IV_BYTES) {
    return `initialization vector is ${parts.iv.length} bytes, expected ${IV_BYTES}`;
  }
  if (parts.tag.length !== GCM_TAG_BYTES) {
    return `authentication tag is ${parts.tag.length} bytes, expected ${GCM_TAG_BYTES}`;
  }
  if (parts.body.length === 0) return "ciphertext is empty";
  const { algorithm, salt, N, r, p } = parsed.kdf;
  if (algorithm !== "scrypt") return `unsupported kdf "${String(algorithm)}"`;
  if (typeof salt !== "string" || Buffer.from(salt, "base64").length === 0) {
    return "kdf salt is missing or not base64";
  }
  if (!isPositiveInt(N) || N < 2 || (N & N - 1) !== 0) {
    return `kdf N is ${String(N)}, which is not a power of two above 1`;
  }
  if (!isPositiveInt(r) || !isPositiveInt(p)) {
    return `kdf r/p are ${String(r)}/${String(p)}, which are not positive integers`;
  }
  if (N > SCRYPT_N || r > SCRYPT_R || p > SCRYPT_P) {
    return `kdf cost parameters (N=${N}, r=${r}, p=${p}) exceed the supported maximum (N=${SCRYPT_N}, r=${SCRYPT_R}, p=${SCRYPT_P})`;
  }
  return null;
}
var FileSecretStore = class {
  filePath;
  passphrase;
  constructor(options) {
    this.filePath = options.filePath;
    const raw = options.passphrase ?? process.env[SECRET_KEY_ENV];
    this.passphrase = raw && raw.trim() ? raw : void 0;
  }
  /**
   * True when the *next write* will encrypt. This is the configured policy,
   * not the state of the file — see {@link readOnDiskEncryption}, which is
   * what the user-facing descriptor is built from.
   */
  get encrypted() {
    return this.passphrase !== void 0;
  }
  /**
   * What the file on disk is *currently* written as — `null` when there is
   * no file yet, or when it cannot be read or parsed.
   *
   * Separate from {@link encrypted} because the two genuinely disagree for a
   * whole session: adding `MCP_INSPECTOR_SECRET_KEY` to an install that
   * already has a plaintext file flips `encrypted` to true immediately,
   * while the bytes stay readable until the next `set`. A descriptor built
   * from the policy would tell that user "File (encrypted)" while their
   * existing secrets sat in the clear — the precise reassurance this whole
   * subsystem exists not to give.
   *
   * Reads the envelope, and for an encrypted one **attempts to open it**.
   * Structure alone is not access: a well-formed file this passphrase cannot
   * decrypt answers `null` from every `get` and refuses every `set`, so
   * reporting it as a healthy "encrypted" store hands the quiet footer to
   * exactly the state the unreadable one exists to warn about. The
   * authentication costs one scrypt derivation per call, which is once per
   * `/api/config`, and the decrypted bytes are discarded.
   *
   * `absent` and `unreadable` are kept apart deliberately. Collapsing them
   * (as a bare `null` did) means an existing file that is corrupt, or
   * written in an envelope this build does not know, is reported as "no
   * file yet" — and the descriptor then falls back to the configured write
   * policy, so a passphrase-configured install claims "Encrypted file"
   * about a file whose encryption state was never established and which
   * `set` is in fact about to refuse.
   */
  async readOnDiskEncryption() {
    let raw;
    try {
      raw = await readStoreFile(this.filePath);
    } catch (err) {
      return {
        state: "unreadable",
        detail: err instanceof Error ? err.message : String(err)
      };
    }
    if (raw === null) return { state: "absent" };
    try {
      const parsed = JSON.parse(raw);
      if (parsed.version !== FORMAT_VERSION) {
        return {
          state: "unreadable",
          detail: `format version ${String(parsed.version)}, expected ${FORMAT_VERSION}`
        };
      }
      if (parsed.encryption === "none") {
        try {
          asSecretMap(parsed.secrets ?? {}, this.filePath);
        } catch (err) {
          return {
            state: "unreadable",
            detail: err instanceof Error ? err.message : String(err)
          };
        }
        return { state: "plaintext" };
      }
      if (parsed.encryption === CIPHER) {
        const problem = encryptedEnvelopeProblem(parsed);
        if (problem) return { state: "unreadable", detail: problem };
        try {
          await this.readMap();
        } catch (err) {
          return {
            state: "unreadable",
            detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV}` : err instanceof Error ? err.message : String(err)
          };
        }
        return { state: "encrypted" };
      }
      return {
        state: "unreadable",
        detail: `unsupported envelope (encryption="${String(parsed.encryption)}", version=${String(parsed.version)})`
      };
    } catch {
      return { state: "unreadable", detail: "not valid JSON" };
    }
  }
  async deriveKey(kdf) {
    return scrypt2(
      // Only reached when the passphrase is set — every caller checks first,
      // which is what makes the non-null assertion safe here rather than a
      // widened `string | undefined` threaded through the crypto helpers.
      this.passphrase,
      Buffer.from(kdf.salt, "base64"),
      KEY_BYTES,
      { N: kdf.N, r: kdf.r, p: kdf.p }
    );
  }
  /**
   * Read and decode the file into a plain map.
   *
   * Distinguishes three outcomes that callers treat differently: `null` for
   * "no file yet" (a first write should create one), a map for success, and a
   * throw for "there is a file and we cannot read it" — which must never be
   * flattened into an empty map, or `set` would cheerfully overwrite it.
   */
  /**
   * Every secret in the file, keyed by `${serverId}:${field}` — `null` when
   * there is no file.
   *
   * Exists for one caller: the hand-off performed when a keychain becomes
   * available on a box that had been falling back to a file (see
   * `absorbFileSecretsIntoKeyring`). Nothing in the normal request path
   * enumerates secrets, and deliberately so — this is the widest possible
   * read of the store and it should stay a one-purpose seam rather than a
   * general convenience.
   *
   * Throws for the same reasons `get` swallows: an unreadable or
   * undecryptable file must not present as "no secrets", because the caller
   * would conclude there was nothing to migrate and move on.
   */
  async readAll() {
    return this.serialize(() => this.readMap());
  }
  async readMap() {
    const raw = await readStoreFile(this.filePath);
    if (raw === null) return null;
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch (err) {
      throw new SecretStoreUnavailableError(
        `The secrets file at ${this.filePath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
      );
    }
    if (parsed.version !== FORMAT_VERSION) {
      throw new SecretStoreUnavailableError(
        `The secrets file at ${this.filePath} declares format version ${String(parsed.version)}, but this Inspector only understands version ${FORMAT_VERSION}. It was probably written by a newer Inspector; upgrade, or move the file aside to start over.`
      );
    }
    if (parsed.encryption === "none") {
      return asSecretMap(parsed.secrets ?? {}, this.filePath);
    }
    if (parsed.encryption !== CIPHER || !parsed.kdf || !parsed.data) {
      throw new SecretStoreUnavailableError(
        `The secrets file at ${this.filePath} declares an unsupported format (encryption="${String(parsed.encryption)}", version=${String(parsed.version)}). It may have been written by a newer Inspector.`
      );
    }
    if (!this.passphrase)
      throw new SecretFileKeyMismatchError(this.filePath, false);
    const structural = encryptedEnvelopeProblem(parsed);
    if (structural) {
      throw new SecretStoreUnavailableError(
        `The secrets file at ${this.filePath} is not readable: ${structural}. Restoring a passphrase will not repair it; the file itself is malformed.`
      );
    }
    const parts = decodeParts(parsed.data);
    if (!parts) throw new SecretFileKeyMismatchError(this.filePath, true);
    let plain;
    try {
      const key = await this.deriveKey(parsed.kdf);
      const decipher = crypto2.createDecipheriv(CIPHER, key, parts.iv);
      decipher.setAuthTag(parts.tag);
      plain = Buffer.concat([
        decipher.update(parts.body),
        decipher.final()
      ]).toString("utf-8");
    } catch {
      throw new SecretFileKeyMismatchError(this.filePath, true);
    }
    let payload;
    try {
      payload = JSON.parse(plain);
    } catch (err) {
      throw new SecretStoreUnavailableError(
        `The secrets file at ${this.filePath} decrypted successfully but its contents are not valid JSON: ${err instanceof Error ? err.message : String(err)}. The passphrase is correct; the file itself is corrupt.`
      );
    }
    return asSecretMap(payload, this.filePath);
  }
  async writeMap(map) {
    let file;
    if (this.passphrase) {
      const kdf = {
        algorithm: "scrypt",
        // A fresh salt per write, not per file: rewriting with the same salt
        // and a fresh IV would still be safe for GCM, but re-salting costs one
        // scrypt derivation we were doing anyway and removes the question.
        salt: crypto2.randomBytes(16).toString("base64"),
        N: SCRYPT_N,
        r: SCRYPT_R,
        p: SCRYPT_P
      };
      const key = await this.deriveKey(kdf);
      const iv = crypto2.randomBytes(IV_BYTES);
      const cipher = crypto2.createCipheriv(CIPHER, key, iv);
      const body = Buffer.concat([
        cipher.update(JSON.stringify(map), "utf-8"),
        cipher.final()
      ]);
      file = {
        version: FORMAT_VERSION,
        encryption: CIPHER,
        kdf,
        data: encodeParts(iv, cipher.getAuthTag(), body)
      };
    } else {
      file = { version: FORMAT_VERSION, encryption: "none", secrets: map };
    }
    await writeStoreFile(this.filePath, `${JSON.stringify(file, null, 2)}
`);
  }
  /**
   * Apply a mutation to the file, under a cross-process lock, and confirm it
   * survived.
   *
   * **Two mechanisms, and they answer different questions** — this is not
   * belt-and-braces.
   *
   * The lock ({@link withSecretFileLock}, #2082) is the one that provides
   * mutual exclusion: a durable secrets file is shared state, and a second
   * Inspector on the same box — a CLI run beside a web session is the
   * ordinary case, not a contrived one — otherwise reads the same "before"
   * map and then atomically replaces the file, dropping whatever the first
   * process had just added. Both writes report success; one secret is simply
   * gone. Held across the read *and* the write, so no other holder can
   * observe or replace the map in between.
   *
   * The verify covers what a lock structurally cannot. A lock is an advisory
   * convention between the processes that take it, so it says nothing about
   * a writer that does not: an editor, a restored backup, a `jq` one-liner,
   * or an Inspector predating #2082. It is also the fallback when the lock
   * is unavailable at all — `withSecretFileLock` runs the body anyway on a
   * read-only or lock-hostile directory rather than failing a `set` on
   * exactly the deployments this store exists for (#1848, #1905).
   *
   * So: read `M0`, apply the mutation to get `M1`, write it, then read back
   * `M2`. If `M2` equals `M1` nothing interleaved. If it does not, someone
   * wrote between our write and our read — so re-apply onto what they left
   * and try again.
   *
   * The comparison is over the **whole map**, not just the entry we touched.
   * Checking only our own key would pass in precisely the case that loses
   * data: our entry is present, and it is the *other* writer's that is gone.
   * When the verify does fire, the writer that was clobbered is the one that
   * repairs it — A writes `MA`, B writes `MB` over it, B verifies `MB`
   * correctly, A verifies, sees `M2 !== MA`, re-applies onto `MB`.
   *
   * **What is left, stated without overselling.** Against an unlocked writer
   * the verify is not mutual exclusion: it only catches a clobber that has
   * already landed, so ordering the two as write-A, verify-A, write-B,
   * verify-B loses A's entry with both reporting success. That residual is
   * now confined to a writer outside this codebase, or to a directory where
   * no lock could be taken — and the latter announces itself, once, on the
   * console.
   *
   * Reads deliberately do not participate: `writeStoreFile` is atomic
   * (write-temp-then-rename), so a reader sees either the old file or the
   * new one, never a torn one. Taking the lock for them would serialize
   * every `GET /api/servers` behind whatever else holds it, to prevent
   * nothing.
   */
  async mutate(apply) {
    await withSecretFileLock(this.filePath, () => this.mutateLocked(apply));
  }
  /** {@link mutate}'s body, with the lock already held. */
  async mutateLocked(apply) {
    for (let attempt = 0; attempt < MAX_WRITE_ATTEMPTS; attempt++) {
      let current;
      try {
        current = await this.readMap() ?? {};
      } catch (err) {
        if (err instanceof SecretStoreUnavailableError) throw err;
        throw new SecretStoreUnavailableError(
          `Could not read the secrets file at ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
        );
      }
      const next = apply(current);
      if (next === null) return;
      try {
        await this.writeMap(next);
      } catch (err) {
        throw new SecretStoreUnavailableError(
          `Could not write the secrets file at ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
        );
      }
      let observed;
      try {
        observed = await this.readMap() ?? {};
      } catch {
        continue;
      }
      if (sameMap(next, observed)) return;
    }
    throw new SecretStoreUnavailableError(
      `Could not write the secrets file at ${this.filePath}: another process kept overwriting it (gave up after ${MAX_WRITE_ATTEMPTS} attempts). Its secrets are intact; the value you just entered was not saved.`
    );
  }
  /**
   * Run `fn` with the file to itself, so concurrent callers can't lose
   * writes.
   *
   * Keyed on the **resolved path, process-wide** rather than on this
   * instance. Two `FileSecretStore`s pointing at one file are ordinary —
   * the resolved store holds one and `absorbFileSecretsIntoKeyring`
   * constructs another — and a per-instance queue does not order them
   * against each other at all. They then race in-process, which the
   * optimistic verify is only *supposed* to catch for writers in different
   * processes: both read the same map, both write, and whichever verifies
   * before the other's write lands sees its own value and returns happy.
   * Measured, not theorised — two instances racing `set` lost an entry on
   * the first run of the convergence test below.
   *
   * So the in-process case is made correct by construction here, and
   * {@link mutate}'s cross-process lock covers what this cannot see: a
   * second Inspector process.
   *
   * The queue is *also* what keeps that lock usable. `proper-lockfile` is
   * not reentrant — a second `lock()` on a path this process already holds
   * fails `ELOCKED`, which is indistinguishable from a genuine remote
   * holder. Serializing here means the lock is only ever contended between
   * processes, which is the only contention it is asked to arbitrate.
   */
  serialize(fn) {
    const key = path3.resolve(this.filePath);
    const run = (fileQueues.get(key) ?? Promise.resolve()).then(fn);
    const settled = run.then(
      () => {
      },
      () => {
      }
    );
    fileQueues.set(key, settled);
    void settled.then(() => {
      if (fileQueues.get(key) === settled) fileQueues.delete(key);
    });
    return run;
  }
  /**
   * One read and one derivation for an entire rehydration.
   *
   * `get` reads and decrypts the whole file, so the cost is per *call*, not
   * per field — which is why the batch has to span servers. A per-server
   * version of this shipped first and left a 20-server catalog paying 20
   * serialized derivations, the same stall it was written to remove.
   * Tolerant like `get`: an unreadable store yields no fields for anyone
   * rather than throwing.
   */
  async getMany(requests) {
    let map;
    try {
      map = await this.serialize(() => this.readMap());
    } catch {
      map = null;
    }
    const out = {};
    for (const { serverId, fields } of requests) {
      const found = {};
      for (const field of fields) {
        const value = map?.[buildAccount2(serverId, field)];
        if (value !== void 0) found[field] = value;
      }
      out[serverId] = found;
    }
    return out;
  }
  /**
   * The intolerant read — same lookup as {@link get}, minus the catch.
   *
   * Without this, `secretStoreGetStrict` fell back to `get` for the file
   * store and turned every read failure into `null`. Both plaintext
   * migrations *write* on `null` and then strip the disk copy, so a
   * transient read error that cleared before the write let an older
   * on-disk value replace a newer stored one — the same inversion the
   * strict seam was introduced to prevent, still open on the store the
   * seam was introduced for.
   */
  async getStrict(serverId, field) {
    try {
      const map = await this.serialize(() => this.readMap());
      return map?.[buildAccount2(serverId, field)] ?? null;
    } catch (err) {
      if (err instanceof SecretStoreUnavailableError) throw err;
      throw new SecretStoreUnavailableError(
        `Could not read the secrets file at ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
      );
    }
  }
  async get(serverId, field) {
    try {
      const map = await this.serialize(() => this.readMap());
      return map?.[buildAccount2(serverId, field)] ?? null;
    } catch {
      return null;
    }
  }
  /**
   * One read-modify-write-verify cycle for a whole server's fields.
   *
   * `set` pays that cycle — and, encrypted, three scrypt derivations — per
   * call, serialized behind this store's queue. The settings form resends a
   * server's full `env` map on any edit, so a 10-variable stdio server spent
   * ~30 derivations saving an unrelated timeout change.
   */
  async setMany(serverId, values) {
    if (Object.keys(values).length === 0) return;
    await this.serialize(
      () => this.mutate((map) => {
        const next = { ...map };
        for (const [field, value] of Object.entries(values)) {
          next[buildAccount2(serverId, field)] = value;
        }
        return next;
      })
    );
  }
  async set(serverId, field, value) {
    await this.serialize(
      () => this.mutate((map) => ({
        ...map,
        [buildAccount2(serverId, field)]: value
      }))
    );
  }
  async delete(serverId, field) {
    await this.deleteWhere((key) => key === buildAccount2(serverId, field));
  }
  async deleteAllForServer(serverId) {
    const prefix = `${serverId}:`;
    await this.deleteWhere((key) => key.startsWith(prefix));
  }
  /**
   * Shared delete body. Silent on every failure, matching the keyring store:
   * every reason a delete can fail collapses to "the entry isn't there
   * anymore", and `set` is the operation that reports a broken store.
   */
  async deleteWhere(match) {
    try {
      await this.serialize(
        () => this.mutate((map) => {
          const keys = Object.keys(map).filter(match);
          if (keys.length === 0) return null;
          const next = { ...map };
          for (const key of keys) delete next[key];
          return next;
        })
      );
    } catch {
    }
  }
};
async function tightenSecretFilePermissions(filePath) {
  try {
    await fs3.chmod(filePath, 384);
  } catch {
  }
  return readSecretFilePermissions(filePath);
}
async function readSecretFilePermissions(filePath) {
  try {
    if (process.platform === "win32") {
      await fs3.stat(filePath);
      return { state: "unknown", detail: "not checked on Windows" };
    }
    const mode = (await fs3.stat(filePath)).mode & 511;
    if (mode === 384) return { state: "ok" };
    return { state: "loose", mode };
  } catch (err) {
    const code = err.code;
    if (code === "ENOENT") {
      return { state: "absent" };
    }
    return { state: "unknown", detail: code ?? "unknown error" };
  }
}

// ../../core/auth/node/secret-store-selection.ts
var SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
var SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
var STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
var KINDS = ["keyring", "file", "memory"];
function parseSecretStoreEnv(raw) {
  const value = raw?.trim().toLowerCase();
  if (!value) return void 0;
  if (KINDS.includes(value)) return value;
  console.warn(
    `[mcp-inspector] Ignoring ${SECRET_STORE_ENV}="${raw}": expected one of ${KINDS.join(", ")}.`
  );
  return void 0;
}
function defaultSecretFilePath() {
  const override = process.env[SECRET_FILE_ENV]?.trim();
  if (override) return path4.resolve(override);
  const storageDir = process.env[STORAGE_DIR_ENV]?.trim();
  if (storageDir) return path4.resolve(storageDir, "secrets.json");
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
  return path4.resolve(homeDir, ".mcp-inspector", "secrets.json");
}
function isContainer() {
  if (process.env.KUBERNETES_SERVICE_HOST) return true;
  try {
    if (fsSync.existsSync("/.dockerenv") || fsSync.existsSync("/run/.containerenv")) {
      return true;
    }
  } catch {
  }
  try {
    const cgroup = fsSync.readFileSync("/proc/1/cgroup", "utf-8");
    return /\b(docker|containerd|kubepods|podman|libpod|lxc)\b/.test(cgroup);
  } catch {
    return false;
  }
}
function nearestExisting(dir) {
  let current = path4.resolve(dir);
  while (!fsSync.existsSync(current)) {
    const parent = path4.dirname(current);
    if (parent === current) return null;
    current = parent;
  }
  return current;
}
function unescapeMountPoint(point) {
  return point.replace(
    /\\(\d{3})/g,
    (_match, octal) => String.fromCharCode(parseInt(octal, 8))
  );
}
var EPHEMERAL_FSTYPES = /* @__PURE__ */ new Set(["tmpfs", "ramfs"]);
function readMountPoints() {
  try {
    return fsSync.readFileSync("/proc/self/mountinfo", "utf-8").split("\n").flatMap((line) => {
      const point = line.split(" ")[4];
      if (!point) return [];
      const after = line.split(" - ")[1];
      const fstype = after?.trim().split(/\s+/)[0];
      if (fstype && EPHEMERAL_FSTYPES.has(fstype)) return [];
      return [unescapeMountPoint(point)];
    }).filter((point) => point !== "/");
  } catch {
    return null;
  }
}
function isOnMountPoint(dir) {
  try {
    const nearest = nearestExisting(dir);
    if (nearest === null) return false;
    let current = nearest;
    try {
      current = fsSync.realpathSync(nearest);
    } catch {
    }
    const mounts = readMountPoints();
    if (mounts !== null) {
      return mounts.some(
        (point) => current === point || current.startsWith(`${point}/`)
      );
    }
    const parent = path4.dirname(current);
    if (parent === current) return false;
    return fsSync.statSync(current).dev !== fsSync.statSync("/").dev;
  } catch {
    return false;
  }
}
async function buildStore(kind, reason, detail) {
  if (kind === "keyring") {
    return {
      store: new KeyringSecretStore(),
      info: { kind, reason, durable: true }
    };
  }
  if (kind === "memory") {
    return {
      store: new SessionSecretStore(),
      info: { kind, reason, durable: false, detail }
    };
  }
  const filePath = defaultSecretFilePath();
  const store = new FileSecretStore({ filePath });
  await tightenSecretFilePermissions(filePath);
  return {
    store,
    info: await describeFileStore(store, filePath, kind, reason, detail)
  };
}
async function describeFileStore(store, filePath, kind, reason, detail) {
  const onDisk = await store.readOnDiskEncryption();
  const unreadable = onDisk.state === "unreadable" ? onDisk.detail : void 0;
  const plaintext = onDisk.state === "absent" ? !store.encrypted : onDisk.state === "plaintext";
  const perms = await readSecretFilePermissions(filePath);
  return {
    kind,
    reason,
    path: filePath,
    // Omitted entirely when the envelope could not be read, so no consumer
    // can read a default out of it.
    ...unreadable === void 0 ? { plaintext } : {},
    ...unreadable !== void 0 ? { encryptionUnknown: unreadable } : {},
    ...perms.state === "loose" ? { looseMode: perms.mode } : {},
    ...perms.state === "unknown" ? { permissionsUnknown: perms.detail } : {},
    // Only meaningful while the two disagree; omitted otherwise so the
    // payload does not carry a flag that says nothing.
    ...unreadable === void 0 && plaintext && store.encrypted ? { pendingEncryption: true } : {},
    durable: true,
    detail
  };
}
function chooseFallbackKind(opts) {
  return opts.container && !opts.mounted ? "memory" : "file";
}
function warnAboutSecretStorage(info) {
  if (info.reason === "fallback") {
    console.warn(
      `
[mcp-inspector] The OS keychain is not available, so secrets will be kept in: ${secretStorageSummary(info)}.` + (info.detail ? `
[mcp-inspector] Keychain error: ${info.detail}` : "") + `
[mcp-inspector] Set ${SECRET_STORE_ENV}=keyring|file|memory to choose explicitly.`
    );
  }
  const caveat = secretStorageCaveat(info);
  if (caveat) console.warn(`[mcp-inspector] ${caveat}`);
}
var resolved;
function resolveSecretStore() {
  resolved ??= (async () => {
    const configured = parseSecretStoreEnv(process.env[SECRET_STORE_ENV]);
    if (configured) {
      const result2 = await buildStore(configured, "configured");
      if (configured === "keyring") {
        await absorbFileSecretsIntoKeyring(result2.store);
      }
      warnAboutSecretStorage(result2.info);
      return result2;
    }
    const probe = await probeKeyringAvailable();
    if (probe.available) {
      const result2 = await buildStore("keyring", "default");
      await absorbFileSecretsIntoKeyring(result2.store);
      return result2;
    }
    const kind = chooseFallbackKind({
      container: isContainer(),
      mounted: isOnMountPoint(path4.dirname(defaultSecretFilePath()))
    });
    const result = await buildStore(kind, "fallback", probe.detail);
    warnAboutSecretStorage(result.info);
    return result;
  })();
  return resolved;
}
async function absorbFileSecretsIntoKeyring(keyring) {
  const filePath = defaultSecretFilePath();
  if (!await anythingToMigrate(filePath)) return;
  const claimed = await withSecretFileLock(filePath, async () => {
    await recoverOrphanedSnapshots(filePath);
    if (!fsSync.existsSync(filePath)) return null;
    const staged = `${filePath}.migrating-${process.pid}-${randomUUID()}`;
    try {
      await fs4.rename(filePath, staged);
    } catch (err) {
      const code = err.code;
      if (code !== "ENOENT") {
        console.warn(
          `
[mcp-inspector] Could not claim the secrets file at ${filePath} for migration into the OS keychain (${code ?? "unknown error"}), so it has been left in place. Its secrets are not visible to this session.`
        );
      }
      return null;
    }
    const releaseSnapshot = await openSecretFileLock(staged).catch(() => null);
    return { staged, releaseSnapshot };
  }).catch((err) => {
    console.warn(
      `
[mcp-inspector] Could not lock the secrets file at ${filePath} to migrate it into the OS keychain (${err.message}), so it has been left in place. Its secrets are not visible to this session; the next run will try again.`
    );
    return null;
  });
  if (claimed === null) return;
  try {
    await handOffStagedSecrets(claimed.staged, filePath, keyring);
  } finally {
    await claimed.releaseSnapshot?.();
  }
}
async function handOffStagedSecrets(staged, filePath, keyring) {
  const file = new FileSecretStore({ filePath: staged });
  const absorbed = await handOff(file, staged, keyring);
  if (absorbed) {
    try {
      await fs4.rm(staged, { force: true });
      console.warn(
        `
[mcp-inspector] The OS keychain is available again. Secrets kept in ${filePath} while it was not have been moved into the keychain, and the file has been removed.`
      );
    } catch (err) {
      console.warn(
        `
[mcp-inspector] Secrets were moved into the OS keychain, but the temporary copy at ${staged} could not be removed (${err instanceof Error ? err.message : String(err)}). Delete it once you have confirmed the keychain has your secrets.`
      );
    }
    return;
  }
  try {
    await fs4.link(staged, filePath);
    await fs4.rm(staged, { force: true });
  } catch {
    console.warn(
      `
[mcp-inspector] Could not restore ${staged} to ${filePath}; both files exist and the snapshot holds the secrets that were not migrated. Merge it by hand, or delete it once you have confirmed nothing is missing.`
    );
  }
}
var isSnapshotName = (name, base) => name.startsWith(`${base}.migrating-`) && !name.endsWith(".lock");
async function anythingToMigrate(filePath) {
  const base = path4.basename(filePath);
  try {
    return (await fs4.readdir(path4.dirname(filePath))).some(
      (name) => name === base || isSnapshotName(name, base)
    );
  } catch (err) {
    const code = err.code;
    return code !== "ENOENT" && code !== "ENOTDIR";
  }
}
async function recoverOrphanedSnapshots(filePath) {
  const dir = path4.dirname(filePath);
  const base = path4.basename(filePath);
  let names;
  try {
    names = (await fs4.readdir(dir)).filter((n) => isSnapshotName(n, base));
  } catch {
    return;
  }
  for (const name of names) {
    const orphan = path4.join(dir, name);
    if (await isFileLockHeld(orphan)) continue;
    try {
      await fs4.link(orphan, filePath);
      await fs4.rm(orphan, { force: true });
      console.warn(
        `
[mcp-inspector] Recovered secrets left behind by an interrupted migration (${orphan} \u2192 ${filePath}).`
      );
    } catch {
      console.warn(
        `
[mcp-inspector] Found secrets from an interrupted migration at ${orphan}, and a current ${filePath} already exists, so it was left alone. It may hold values the current file does not.`
      );
    }
  }
}
async function handOff(file, filePath, keyring) {
  let entries;
  try {
    entries = await file.readAll();
  } catch (err) {
    console.warn(
      `
[mcp-inspector] The OS keychain is available again, but the secrets file at ${filePath} could not be read, so its contents were left there: ${err instanceof Error ? err.message : String(err)}`
    );
    return false;
  }
  if (!entries || Object.keys(entries).length === 0) return false;
  let complete = true;
  try {
    for (const [account, value] of Object.entries(entries)) {
      const parsed = parseAccount(account);
      if (!parsed) {
        complete = false;
        continue;
      }
      const existing = await secretStoreGetStrict(
        keyring,
        parsed.serverId,
        parsed.field
      );
      if (existing === null) {
        await keyring.set(parsed.serverId, parsed.field, value);
      }
    }
  } catch (err) {
    console.warn(
      `
[mcp-inspector] Could not move the secrets file at ${filePath} into the OS keychain, so it has been left in place: ${err instanceof Error ? err.message : String(err)}`
    );
    return false;
  }
  if (!complete) {
    console.warn(
      `
[mcp-inspector] Secrets from ${filePath} have been copied into the OS keychain, but at least one entry could not be read (its key is not in \`serverId:field\` form). The file has been left in place so nothing is lost.`
    );
    return false;
  }
  return true;
}
var DeferredSecretStore = class {
  async target() {
    return (await resolveSecretStore()).store;
  }
  async isDurable() {
    return secretStoreIsDurable(await this.target());
  }
  /**
   * Forwarded, and its absence was a real defect rather than an omission:
   * this is the store production actually uses, so a `secretStoreGetStrict`
   * call against it fell through to the tolerant `get` and the strictness
   * existed only in tests that injected a concrete store.
   */
  async getStrict(serverId, field) {
    return secretStoreGetStrict(await this.target(), serverId, field);
  }
  async getMany(requests) {
    return secretStoreGetMany(await this.target(), requests);
  }
  async get(serverId, field) {
    return (await this.target()).get(serverId, field);
  }
  async set(serverId, field, value) {
    await (await this.target()).set(serverId, field, value);
  }
  async setMany(serverId, values) {
    await secretStoreSetMany(await this.target(), serverId, values);
  }
  async delete(serverId, field) {
    await (await this.target()).delete(serverId, field);
  }
  async deleteAllForServer(serverId) {
    await (await this.target()).deleteAllForServer(serverId);
  }
};
function defaultSecretStore() {
  return new DeferredSecretStore();
}

// ../../core/mcp/node/server-secrets.ts
async function rehydrateMcpConfigFromKeychain(config, secretStore) {
  const entries = Object.entries(config.mcpServers);
  const secrets = await secretStoreGetMany(
    secretStore,
    entries.map(([serverId, stored]) => ({
      serverId,
      fields: expectedSecretFields(stored)
    }))
  );
  const out = { mcpServers: {} };
  for (const [id, stored] of entries) {
    out.mcpServers[id] = mergeSecretsIntoStored(stored, secrets[id] ?? {});
  }
  return out;
}

// ../../core/mcp/node/servers.ts
function headersToServerSettings(headers) {
  if (!headers || Object.keys(headers).length === 0) {
    return void 0;
  }
  return {
    headers: Object.entries(headers).map(([key, value]) => ({ key, value })),
    env: [],
    metadata: {},
    connectionTimeout: 0,
    requestTimeout: 0,
    taskTtl: DEFAULT_TASK_TTL_MS,
    maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS,
    autoRefreshOnListChanged: false,
    paginatedLists: false,
    roots: []
  };
}
function mergeSettings(base, headers) {
  const fromHeaders = headersToServerSettings(headers);
  if (!fromHeaders) return base;
  if (!base) return fromHeaders;
  return { ...base, headers: fromHeaders.headers };
}
async function loadServerEntries(serverOptions) {
  serverOptions = withDefaultCatalogPath(serverOptions);
  const conflict = serverSourceConflict({
    hasCatalog: Boolean(serverOptions.catalogPath?.trim()),
    hasConfig: Boolean(serverOptions.configPath?.trim()),
    hasAdHoc: hasAdHocServerOptions(serverOptions)
  });
  if (conflict) {
    throw new Error(conflict);
  }
  const source = resolveServerSource(serverOptions);
  if (source) {
    let config = readServerListFile(source.path, source.writable);
    const secretStore = serverOptions.secretStore ?? defaultSecretStore();
    config = await rehydrateMcpConfigFromKeychain(config, secretStore);
    const entries = mcpConfigToServerEntries(config);
    const result = {};
    for (const entry of entries) {
      result[entry.name] = {
        config: applyOverrides(entry.config, {
          env: serverOptions.env,
          cwd: serverOptions.cwd
        }),
        // Deliberate broadcast: a single `--header` set is merged into EVERY
        // server in the catalog/config (fine for the common single-server case;
        // for multi-server files, prefer per-server headers in the file itself).
        settings: mergeSettings(entry.settings, serverOptions.headers)
      };
    }
    return result;
  }
  const configs = resolveServerConfigs(serverOptions, "multi");
  if (configs.length === 0) {
    throw new Error(
      "At least one server is required. Use --catalog/--config <path> or an ad-hoc target (command/URL)."
    );
  }
  return {
    default: {
      config: configs[0],
      settings: headersToServerSettings(serverOptions.headers)
    }
  };
}
function selectServerEntry(entries, serverName) {
  const names = Object.keys(entries);
  if (serverName) {
    const entry = entries[serverName];
    if (!entry) {
      throw new Error(
        `Server '${serverName}' not found. Available servers: ${names.join(", ")}`
      );
    }
    return entry;
  }
  if (names.length === 0) {
    throw new Error("No servers found in config file");
  }
  if (names.length > 1) {
    throw new Error(
      `Multiple servers found in config file. Please specify one with --server. Available servers: ${names.join(", ")}`
    );
  }
  return entries[names[0]];
}

// ../../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";
function parseHttpUrl(value, label) {
  const trimmed = value.trim();
  try {
    return new URL(trimmed);
  } catch (err) {
    const detail = err instanceof Error ? err.message : String(err);
    throw new Error(`Invalid ${label}: "${trimmed}" (${detail})`, {
      cause: err
    });
  }
}
var parseOAuthCallbackParams = (location) => {
  const params = new URLSearchParams(location);
  const code = params.get("code");
  if (code) {
    const iss = params.get("iss");
    return iss === null ? { successful: true, code } : { successful: true, code, iss };
  }
  const error = params.get("error");
  const error_description = params.get("error_description");
  const error_uri = params.get("error_uri");
  if (error) {
    return { successful: false, error, error_description, error_uri };
  }
  return {
    successful: false,
    error: "invalid_request",
    error_description: "Missing code or error in response",
    error_uri: null
  };
};
var generateOAuthState = () => {
  if (typeof crypto === "undefined" || !crypto.getRandomValues) {
    throw new Error(
      "crypto.getRandomValues is not available; refusing to generate an OAuth state with a non-cryptographic RNG."
    );
  }
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
    ""
  );
};
var parseOAuthState = (state) => {
  if (!state || typeof state !== "string") return null;
  if (/^[a-f0-9]{64}$/i.test(state)) {
    return { authId: state };
  }
  return null;
};
var generateOAuthErrorDescription = (params) => {
  const error = params.error;
  const errorDescription = params.error_description;
  const errorUri = params.error_uri;
  return [
    `Error: ${error}.`,
    errorDescription ? `Details: ${errorDescription}.` : "",
    errorUri ? `More info: ${errorUri}.` : ""
  ].filter(Boolean).join("\n");
};
function isUnauthorizedError(err) {
  return isUnauthorizedErrorDeep(err, /* @__PURE__ */ new Set());
}
function isUnauthorizedErrorDeep(err, seen) {
  if (err == null) return false;
  if (typeof err !== "object") {
    return /\bfailed\b[^\n]*\(401\)/i.test(String(err));
  }
  if (seen.has(err)) return false;
  seen.add(err);
  if (UnauthorizedError.isInstance(err)) return true;
  const status = err.status;
  const code = err.code;
  if (status === 401 || code === 401) return true;
  if (err instanceof Error && /\bfailed\b[^\n]*\(401\)/i.test(err.message)) {
    return true;
  }
  if ("cause" in err && isUnauthorizedErrorDeep(err.cause, seen)) {
    return true;
  }
  const data = err.data;
  if (data !== null && typeof data === "object" && "cause" in data && isUnauthorizedErrorDeep(data.cause, seen)) {
    return true;
  }
  return false;
}

// ../../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;
  }
};
var AuthRecoveryRequiredError = class extends Error {
  authorizationUrl;
  authChallenge;
  /** EMA insufficient_scope awaiting user confirmation in Inspector (no redirect yet). */
  emaStepUpConfirm;
  constructor(authorizationUrl, authChallenge, options) {
    super("Interactive auth recovery required");
    this.name = "AuthRecoveryRequiredError";
    this.authorizationUrl = authorizationUrl;
    this.authChallenge = authChallenge;
    this.emaStepUpConfirm = options?.emaStepUpConfirm;
  }
};
function isConnectAuthRecoveryError(err) {
  if (err instanceof AuthRecoveryRequiredError) return true;
  return isUnauthorizedError(err);
}
function findNestedAuthError(err) {
  return findNestedAuthErrorDeep(err, /* @__PURE__ */ new Set());
}
function findNestedAuthErrorDeep(err, seen) {
  if (err === null || typeof err !== "object" || seen.has(err)) {
    return void 0;
  }
  seen.add(err);
  if (err instanceof AuthRecoveryRequiredError || err instanceof AuthChallengeError) {
    return err;
  }
  const nested = findNestedAuthErrorDeep(
    err.cause,
    seen
  );
  if (nested) {
    return nested;
  }
  const data = err.data;
  if (data !== null && typeof data === "object") {
    return findNestedAuthErrorDeep(data.cause, seen);
  }
  return void 0;
}
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 challengeResourceMetadataUrl(challenge) {
  const value = challenge.resourceMetadataUrl?.trim();
  if (!value) {
    return void 0;
  }
  try {
    return new URL(value);
  } catch {
    return void 0;
  }
}
function parseScopeString(scope) {
  if (!scope?.trim()) {
    return [];
  }
  return scope.trim().split(/\s+/).filter(Boolean);
}
function unionAuthorizationScopes(previousScope, requiredScopes) {
  const seen = /* @__PURE__ */ new Set();
  const result = [];
  for (const scope of [
    ...parseScopeString(previousScope),
    ...requiredScopes.filter(Boolean)
  ]) {
    if (!seen.has(scope)) {
      seen.add(scope);
      result.push(scope);
    }
  }
  return result;
}
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.resourceMetadata ? { resourceMetadataUrl: bearer.resourceMetadata } : {},
    ...bearer.errorDescription ? { message: bearer.errorDescription } : {},
    ...context ? { context } : {},
    raw: {
      httpStatus: status,
      ...wwwAuthenticate ? { wwwAuthenticate } : {}
    }
  };
}
function parseAuthChallengeFromError(err, context) {
  if (err instanceof AuthChallengeError) {
    return err.authChallenge;
  }
  if (typeof err !== "object" || err === null) {
    return void 0;
  }
  const authChallenge = err.authChallenge;
  if (authChallenge?.reason) {
    return {
      ...authChallenge,
      ...context ? {
        context: {
          ...authChallenge.context,
          ...context
        }
      } : {}
    };
  }
  const status = err.status ?? err.code;
  if (status !== 401 && status !== 403) {
    return void 0;
  }
  const wwwAuthenticate = authChallenge?.raw?.wwwAuthenticate ?? err.wwwAuthenticate ?? err.headers?.get?.("WWW-Authenticate") ?? void 0;
  if (!wwwAuthenticate?.length) {
    return void 0;
  }
  const bearer = parseWwwAuthenticateBearer(wwwAuthenticate);
  const requiredScopes = parseScopeString(bearer.scope);
  return {
    reason: reasonFromHttpResponse(status, bearer),
    ...requiredScopes.length > 0 ? { requiredScopes } : {},
    ...bearer.resourceMetadata ? { resourceMetadataUrl: bearer.resourceMetadata } : {},
    ...context ? { context } : {},
    raw: {
      httpStatus: status,
      wwwAuthenticate
    }
  };
}
function isAuthChallengeError(err) {
  if (err instanceof AuthChallengeError) {
    return true;
  }
  if (typeof err !== "object" || err === null) {
    return false;
  }
  const authChallenge = err.authChallenge;
  if (authChallenge?.reason) {
    return true;
  }
  const status = err.status ?? err.code;
  if (status !== 401 && status !== 403) {
    return false;
  }
  const wwwAuthenticate = authChallenge?.raw?.wwwAuthenticate ?? err.wwwAuthenticate ?? err.headers?.get?.("WWW-Authenticate") ?? void 0;
  return wwwAuthenticate !== void 0 && wwwAuthenticate.length > 0;
}

// ../../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})`
    );
  };
}
function createAuthChallengeObserverFetch(baseFetch, onChallenge) {
  return async (input, init) => {
    const response = await baseFetch(input, init);
    if (response.status === 401 || response.status === 403) {
      const challenge = parseAuthChallengeFromResponse(response);
      if (challenge) {
        onChallenge(challenge);
      }
    }
    return response;
  };
}

// ../../core/mcp/node/proxyFetch.ts
function asUndiciInit(init) {
  return init;
}
function asBodyInit(body) {
  return body;
}
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;
}
var NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
  101,
  103,
  204,
  205,
  304
]);
function toGlobalResponse(res) {
  const headers = new Headers();
  for (const [key, value] of res.headers) headers.append(key, value);
  const out = new Response(
    NULL_BODY_STATUSES.has(res.status) ? null : asBodyInit(res.body),
    {
      status: res.status,
      statusText: res.statusText,
      headers
    }
  );
  for (const [key, value] of [
    ["url", res.url],
    ["redirected", res.redirected],
    ["type", res.type]
  ]) {
    Object.defineProperty(out, key, {
      value,
      configurable: true,
      enumerable: false
    });
  }
  return out;
}
var loadedProxiedFetch;
async function loadProxiedFetch() {
  let undici;
  try {
    undici = await import("undici");
  } catch (cause) {
    throw new Error(
      "HTTPS_PROXY / HTTP_PROXY is set but the `undici` package could not be loaded, so requests cannot be routed through the proxy. This is a packaging fault rather than something to fix by installing undici; please report it, or unset the proxy env var to fall back to a direct connection.",
      { cause }
    );
  }
  const dispatcher = new undici.EnvHttpProxyAgent();
  return async (input, init) => {
    const [target, requestInit] = toUndiciRequest(input, init);
    return toGlobalResponse(
      await undici.fetch(target, { ...asUndiciInit(requestInit), dispatcher })
    );
  };
}
function toUndiciRequest(input, init) {
  if (input instanceof Request) {
    return [
      input.url,
      {
        method: input.method,
        headers: input.headers,
        // A stream body needs `duplex: "half"`; the field is undici's, not in
        // the DOM `RequestInit`, so it rides along as an extra property.
        ...input.body ? { body: input.body, duplex: "half" } : {},
        signal: input.signal,
        // Everything else the Request carries. Dropping these would silently
        // change fetch semantics under a proxy — most visibly `redirect`, where
        // a `new Request(url, { redirect: "manual" })` would start following
        // redirects instead of surfacing them.
        redirect: input.redirect,
        cache: input.cache,
        credentials: input.credentials,
        integrity: input.integrity,
        keepalive: input.keepalive,
        mode: input.mode,
        referrer: input.referrer,
        referrerPolicy: input.referrerPolicy,
        // An explicit `init` still wins, per the fetch contract.
        ...init
      }
    ];
  }
  return [input instanceof URL ? input.href : input, init ?? {}];
}
function createProxyFetch() {
  if (readProxyEnv() === void 0) return void 0;
  return async (input, init) => {
    loadedProxiedFetch ??= loadProxiedFetch();
    return (await loadedProxiedFetch)(input, init);
  };
}

// ../../core/mcp/node/transport.ts
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,
    onAuthChallengeObserved
  } = options;
  const baseFetch = optionsFetchFn ?? createProxyFetch() ?? globalThis.fetch;
  const withChallengeObserver = (inner) => onAuthChallengeObserved ? createAuthChallengeObserverFetch(inner, onAuthChallengeObserved) : inner;
  const fetchWithOptionalAuthIntercept = interceptAuthChallenges ? createAuthChallengeInterceptFetch(withChallengeObserver(baseFetch)) : withChallengeObserver(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 configuredSseFetch = sseConfig.eventSourceInit?.fetch;
    const sseFetch = configuredSseFetch ? withChallengeObserver(configuredSseFetch) : 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 };
  }
}

// src/handlers/servers-list.ts
var REDACTED = "[redacted]";
function summarizeServerConfig(config) {
  if (config.type === "sse" || config.type === "streamable-http") {
    return { type: config.type, detail: config.url ?? "" };
  }
  const args = config.args?.length ? ` ${config.args.join(" ")}` : "";
  return { type: "stdio", detail: `${config.command}${args}` };
}
async function listServerEntries(serverOptions = {}) {
  const entries = await loadServerEntries({
    ...serverOptions,
    secretStore: serverOptions.secretStore ?? new InMemorySecretStore()
  });
  return Object.entries(entries).map(([name, resolved2]) => {
    const { type, detail } = summarizeServerConfig(resolved2.config);
    return { name, type, detail };
  }).sort((a, b) => a.name.localeCompare(b.name));
}
async function showServerEntry(serverName, serverOptions = {}) {
  const name = serverName.trim();
  if (!name) {
    throw new Error("servers/show requires a server name.");
  }
  const entries = await loadServerEntries(serverOptions);
  const selected = selectServerEntry(entries, name);
  const { type, detail } = summarizeServerConfig(selected.config);
  const result = {
    name,
    type,
    detail,
    config: sanitizeServerConfig(selected.config)
  };
  if (selected.settings) {
    result.settings = sanitizeServerSettings(selected.settings);
  }
  return result;
}
function sanitizeServerConfig(config) {
  const out = { ...config };
  if ("env" in config && config.env) {
    out.env = redactStringRecord(config.env);
  }
  if ("requestInit" in config && isPlainObject(config.requestInit)) {
    out.requestInit = sanitizeInitRecord(config.requestInit);
  }
  if ("eventSourceInit" in config && isPlainObject(config.eventSourceInit)) {
    out.eventSourceInit = sanitizeInitRecord(config.eventSourceInit);
  }
  return out;
}
function sanitizeServerSettings(settings) {
  const out = {
    ...settings,
    headers: (settings.headers ?? []).map((h) => ({
      key: h.key,
      value: isSensitiveHeader(h.key) ? REDACTED : h.value
    })),
    // Metadata values may be any JSON (#1910), so the redaction has to descend:
    // a top-level key that is not itself sensitive can hold a nested one that
    // is. See `redactMetadataValue`.
    metadata: redactMetadataValue(settings.metadata ?? {}),
    env: (settings.env ?? []).map((e) => ({
      key: e.key,
      value: REDACTED
    }))
  };
  if (settings.oauthClientSecret !== void 0) {
    out.oauthClientSecret = REDACTED;
  }
  return out;
}
function redactStringRecord(record) {
  return Object.fromEntries(Object.keys(record).map((key) => [key, REDACTED]));
}
function isPlainObject(value) {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}
function sanitizeInitRecord(init) {
  const out = { ...init };
  if (isPlainObject(init.headers)) {
    out.headers = Object.fromEntries(
      Object.entries(init.headers).map(([key, value]) => [
        key,
        isSensitiveHeader(key) ? REDACTED : value
      ])
    );
  } else if (Array.isArray(init.headers)) {
    out.headers = init.headers.map((entry) => {
      if (Array.isArray(entry) && entry.length >= 2 && typeof entry[0] === "string") {
        const key = entry[0];
        const value = entry[1];
        return [key, isSensitiveHeader(key) ? REDACTED : value];
      }
      return entry;
    });
  }
  return out;
}
function redactMetadataValue(value) {
  if (Array.isArray(value)) {
    return value.map(redactMetadataValue);
  }
  if (value !== null && typeof value === "object") {
    return Object.fromEntries(
      Object.entries(value).map(([key, member]) => [
        key,
        isSensitiveHeader(key) ? REDACTED : redactMetadataValue(member)
      ])
    );
  }
  return value;
}
function isSensitiveHeader(key) {
  const k = key.toLowerCase();
  return k.includes("auth") || k.includes("cookie") || k.includes("secret") || k.includes("token") || k.includes("password") || k.includes("api-key") || k.includes("apikey");
}

// src/handlers/format-output.ts
async function writeFormattedResult(result, format = "text") {
  if (format === "json") {
    await awaitableLog(JSON.stringify({ result }) + "\n");
    return;
  }
  await awaitableLog(JSON.stringify(result, null, 2) + "\n");
}

// ../../core/auth/oauth-storage.ts
import {
  OAuthClientInformationSchema,
  OAuthTokensSchema
} from "@modelcontextprotocol/core";
function withIssuer(value, issuer) {
  return issuer === void 0 ? value : { ...value, issuer };
}
var OAuthStorageBase = class {
  loaded = false;
  loadPromise;
  /** Serializes persist writes so concurrent mutators cannot reorder POSTs. */
  persistQueue = Promise.resolve();
  memory;
  backend;
  constructor(memory, backend) {
    this.memory = memory;
    this.backend = backend;
  }
  load() {
    if (!this.loadPromise) {
      this.loadPromise = this.doLoad();
    }
    return this.loadPromise;
  }
  async doLoad() {
    if (this.loaded) {
      return;
    }
    const snapshot = await this.backend.read();
    if (snapshot) {
      this.memory.replace(snapshot);
    }
    this.loaded = true;
  }
  async ensureLoaded() {
    await this.load();
  }
  async persist() {
    const snapshot = this.memory.snapshot();
    const prior = this.persistQueue;
    const tracked = prior.catch(() => {
    }).then(() => this.backend.write(snapshot));
    this.persistQueue = tracked;
    await tracked;
  }
  /**
   * Resolve the AS-`issuer` key whose credentials answer a read. An explicit
   * `issuer` (from the SDK's `ctx.issuer`) wins; otherwise the most-recently-saved
   * `activeIssuer` answers the transport's ctx-less per-request bearer read.
   */
  resolveReadIssuer(state, issuer) {
    return issuer ?? state.activeIssuer;
  }
  /** Read the issuer-bound slot for a read, if one exists. */
  issuerSlot(state, issuer) {
    const key = this.resolveReadIssuer(state, issuer);
    return key ? state.byIssuer?.[key] : void 0;
  }
  /**
   * Read-modify-write the `byIssuer[issuer]` slot, set `activeIssuer`, and clear
   * the given legacy top-level fallback fields (lazy migration — the fallback is
   * promoted into the keyed slot on first issuer-stamped save).
   */
  updateIssuerSlot(serverUrl, issuer, updates, clearLegacy, setActive = true) {
    const state = this.memory.getState().getServerState(serverUrl);
    const byIssuer = {
      ...state.byIssuer,
      [issuer]: { ...state.byIssuer?.[issuer], ...updates }
    };
    this.memory.getState().setServerState(serverUrl, {
      byIssuer,
      ...setActive && { activeIssuer: issuer },
      ...clearLegacy
    });
  }
  /** Apply `fn` to every issuer slot (used by issuer-agnostic clears). */
  mapIssuerSlots(state, fn) {
    const byIssuer = {};
    for (const [key, slot] of Object.entries(state.byIssuer ?? {})) {
      byIssuer[key] = { ...slot, ...fn(slot) };
    }
    return byIssuer;
  }
  async getClientInformation(serverUrl, isPreregistered, issuer) {
    await this.ensureLoaded();
    const state = this.memory.getState().getServerState(serverUrl);
    if (isPreregistered) {
      const prereg = state.preregisteredClientInformation;
      return prereg ? await OAuthClientInformationSchema.parseAsync(prereg) : void 0;
    }
    const slot = this.issuerSlot(state, issuer);
    const fromSlot = slot?.clientInformation;
    const clientInfo = fromSlot ?? state.clientInformation;
    if (!clientInfo) {
      return void 0;
    }
    const parsed = await OAuthClientInformationSchema.parseAsync(clientInfo);
    return withIssuer(
      parsed,
      fromSlot ? this.resolveReadIssuer(state, issuer) : void 0
    );
  }
  async getClientRegistrationKind(serverUrl, issuer) {
    await this.ensureLoaded();
    const state = this.memory.getState().getServerState(serverUrl);
    return this.issuerSlot(state, issuer)?.clientRegistrationKind ?? state.clientRegistrationKind;
  }
  async saveClientInformation(serverUrl, clientInformation, options) {
    await this.ensureLoaded();
    if (options.issuer !== void 0) {
      this.updateIssuerSlot(
        serverUrl,
        options.issuer,
        {
          clientInformation,
          clientRegistrationKind: options.registrationKind
        },
        { clientInformation: void 0, clientRegistrationKind: void 0 }
      );
    } else {
      this.memory.getState().setServerState(serverUrl, {
        clientInformation,
        clientRegistrationKind: options.registrationKind
      });
    }
    await this.persist();
  }
  async savePreregisteredClientInformation(serverUrl, clientInformation) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, {
      preregisteredClientInformation: clientInformation,
      clientRegistrationKind: "static"
    });
    await this.persist();
  }
  async clearClientInformation(serverUrl, isPreregistered, issuer) {
    await this.ensureLoaded();
    if (isPreregistered) {
      this.memory.getState().setServerState(serverUrl, {
        preregisteredClientInformation: void 0
      });
      await this.persist();
      return;
    }
    if (issuer !== void 0) {
      this.updateIssuerSlot(
        serverUrl,
        issuer,
        { clientInformation: void 0, clientRegistrationKind: void 0 },
        {},
        false
      );
    } else {
      const state = this.memory.getState().getServerState(serverUrl);
      const byIssuer = this.mapIssuerSlots(state, () => ({
        clientInformation: void 0,
        clientRegistrationKind: void 0
      }));
      this.memory.getState().setServerState(serverUrl, {
        byIssuer,
        clientInformation: void 0,
        clientRegistrationKind: void 0
      });
    }
    await this.persist();
  }
  async getTokens(serverUrl, issuer) {
    await this.ensureLoaded();
    const state = this.memory.getState().getServerState(serverUrl);
    const slot = this.issuerSlot(state, issuer);
    const fromSlot = slot?.tokens;
    const tokens = fromSlot ?? state.tokens;
    if (!tokens) {
      return void 0;
    }
    const parsed = await OAuthTokensSchema.parseAsync(tokens);
    return withIssuer(
      parsed,
      fromSlot ? this.resolveReadIssuer(state, issuer) : void 0
    );
  }
  async saveTokens(serverUrl, tokens, options) {
    await this.ensureLoaded();
    if (options?.issuer !== void 0) {
      this.updateIssuerSlot(
        serverUrl,
        options.issuer,
        { tokens },
        {
          tokens: void 0,
          ...options.enterpriseManaged === true && {
            enterpriseManaged: true
          }
        }
      );
    } else {
      this.memory.getState().setServerState(serverUrl, {
        tokens,
        ...options?.enterpriseManaged === true && { enterpriseManaged: true }
      });
    }
    await this.persist();
  }
  async clearTokens(serverUrl, issuer) {
    await this.ensureLoaded();
    if (issuer !== void 0) {
      this.updateIssuerSlot(
        serverUrl,
        issuer,
        { tokens: void 0 },
        {},
        false
      );
    } else {
      const state = this.memory.getState().getServerState(serverUrl);
      const byIssuer = this.mapIssuerSlots(state, () => ({
        tokens: void 0
      }));
      this.memory.getState().setServerState(serverUrl, { byIssuer, tokens: void 0 });
    }
    await this.persist();
  }
  async getCodeVerifier(serverUrl) {
    await this.ensureLoaded();
    const state = this.memory.getState().getServerState(serverUrl);
    return state.codeVerifier;
  }
  async saveCodeVerifier(serverUrl, codeVerifier) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { codeVerifier });
    await this.persist();
  }
  async clearCodeVerifier(serverUrl) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { codeVerifier: void 0 });
    await this.persist();
  }
  async getScope(serverUrl) {
    await this.ensureLoaded();
    const state = this.memory.getState().getServerState(serverUrl);
    return state.scope;
  }
  async saveScope(serverUrl, scope) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { scope });
    await this.persist();
  }
  async clearScope(serverUrl) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { scope: void 0 });
    await this.persist();
  }
  async getServerMetadata(serverUrl) {
    await this.ensureLoaded();
    const state = this.memory.getState().getServerState(serverUrl);
    return state.serverMetadata || null;
  }
  async saveServerMetadata(serverUrl, metadata) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { serverMetadata: metadata });
    await this.persist();
  }
  async clearServerMetadata(serverUrl) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { serverMetadata: void 0 });
    await this.persist();
  }
  async getDiscoveryState(serverUrl) {
    await this.ensureLoaded();
    return this.memory.getState().getServerState(serverUrl).discoveryState;
  }
  async saveDiscoveryState(serverUrl, state) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { discoveryState: state });
    await this.persist();
  }
  async clearDiscoveryState(serverUrl) {
    await this.ensureLoaded();
    this.memory.getState().setServerState(serverUrl, { discoveryState: void 0 });
    await this.persist();
  }
  async clear(serverUrl) {
    await this.ensureLoaded();
    this.memory.getState().clearServerState(serverUrl);
    await this.persist();
  }
  async getIdpSession(issuer) {
    await this.ensureLoaded();
    const session = this.memory.getState().getIdpSession(issuer);
    if (!session.idToken && !session.refreshToken && session.idTokenExpiresAt === void 0) {
      return void 0;
    }
    return session;
  }
  async saveIdpSession(issuer, session) {
    await this.ensureLoaded();
    this.memory.getState().setIdpSession(issuer, session);
    await this.persist();
  }
  async clearIdpSession(issuer) {
    await this.ensureLoaded();
    this.memory.getState().clearIdpSession(issuer);
    await this.persist();
  }
  async clearEnterpriseManagedResourceServers() {
    await this.ensureLoaded();
    this.memory.getState().clearEnterpriseManagedResourceServers();
    await this.persist();
  }
};

// ../../core/auth/store.ts
var OAuthMemoryStore = class {
  servers = {};
  idpSessions = {};
  constructor(initial) {
    if (initial) {
      this.replace(initial);
    }
  }
  getState() {
    return {
      servers: this.servers,
      idpSessions: this.idpSessions,
      getServerState: (serverUrl) => {
        return this.servers[serverUrl] || {};
      },
      setServerState: (serverUrl, updates) => {
        this.servers = {
          ...this.servers,
          [serverUrl]: {
            ...this.servers[serverUrl],
            ...updates
          }
        };
      },
      clearServerState: (serverUrl) => {
        const rest = { ...this.servers };
        delete rest[serverUrl];
        this.servers = rest;
      },
      getIdpSession: (issuer) => {
        return this.idpSessions[issuer] || {};
      },
      setIdpSession: (issuer, updates) => {
        this.idpSessions = {
          ...this.idpSessions,
          [issuer]: {
            ...this.idpSessions[issuer],
            ...updates
          }
        };
      },
      clearIdpSession: (issuer) => {
        const rest = { ...this.idpSessions };
        delete rest[issuer];
        this.idpSessions = rest;
      },
      clearEnterpriseManagedResourceServers: () => {
        const rest = { ...this.servers };
        for (const [url, serverState] of Object.entries(this.servers)) {
          if (serverState.enterpriseManaged === true) {
            delete rest[url];
          }
        }
        this.servers = rest;
      }
    };
  }
  snapshot() {
    return {
      servers: { ...this.servers },
      idpSessions: { ...this.idpSessions }
    };
  }
  replace(snapshot) {
    this.servers = { ...snapshot.servers ?? {} };
    this.idpSessions = { ...snapshot.idpSessions ?? {} };
  }
};

// ../../core/auth/oauth-persist.ts
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function snapshotFromPayload(payload) {
  return {
    servers: payload.servers ?? {},
    idpSessions: payload.idpSessions ?? {}
  };
}
function parseOAuthPersistBlob(raw) {
  if (raw === null || raw === void 0) {
    return null;
  }
  const parsed = typeof raw === "string" ? raw ? parseStore(raw) : null : raw;
  if (!isRecord(parsed)) {
    return null;
  }
  if (isRecord(parsed.state) && "version" in parsed) {
    return snapshotFromPayload(parsed.state);
  }
  if ("servers" in parsed || "idpSessions" in parsed) {
    return snapshotFromPayload(parsed);
  }
  return null;
}
function serializeOAuthPersistBlob(snapshot) {
  return serializeStore(snapshot);
}

// ../../core/auth/node/oauth-persist-file.ts
function createFileOAuthPersistBackend(options) {
  return {
    async read() {
      const raw = await readStoreFile(options.filePath);
      return parseOAuthPersistBlob(raw);
    },
    async write(snapshot) {
      await writeStoreFile(
        options.filePath,
        serializeOAuthPersistBlob(snapshot)
      );
    },
    async remove() {
      await deleteStoreFile(options.filePath);
    }
  };
}

// ../../core/auth/node/storage-node.ts
var DEFAULT_STATE_PATH = getStoreFilePath(getDefaultStorageDir(), "oauth");
function getStateFilePath(customPath) {
  if (customPath) return customPath;
  if (process.env.MCP_INSPECTOR_OAUTH_STATE_PATH) {
    return process.env.MCP_INSPECTOR_OAUTH_STATE_PATH;
  }
  if (process.env.MCP_STORAGE_DIR) {
    return getStoreFilePath(process.env.MCP_STORAGE_DIR, "oauth");
  }
  return DEFAULT_STATE_PATH;
}
var memoryCache = /* @__PURE__ */ new Map();
var storageCache = /* @__PURE__ */ new Map();
function getSharedMemory(stateFilePath) {
  const key = getStateFilePath(stateFilePath);
  let memory = memoryCache.get(key);
  if (!memory) {
    memory = new OAuthMemoryStore();
    memoryCache.set(key, memory);
  }
  return memory;
}
function resetNodeOAuthStorageCache(stateFilePath) {
  const key = getStateFilePath(stateFilePath);
  memoryCache.delete(key);
  storageCache.delete(key);
}
var NodeOAuthStorage = class extends OAuthStorageBase {
  /**
   * @param storagePath - Optional path to state file. Default: ~/.mcp-inspector/storage/oauth.json
   */
  constructor(storagePath) {
    const filePath = getStateFilePath(storagePath);
    super(
      getSharedMemory(storagePath),
      createFileOAuthPersistBackend({ filePath })
    );
  }
};

// src/clear-stored-auth-for-relogin.ts
function normalizeServerUrl(serverUrl) {
  try {
    return new URL(serverUrl).href;
  } catch {
    return serverUrl;
  }
}
async function clearStoredAuthForRelogin(serverUrl) {
  if (!serverUrl?.trim()) return;
  const raw = serverUrl.trim();
  const normalized = normalizeServerUrl(raw);
  const storage = new NodeOAuthStorage();
  await storage.clear(raw);
  if (normalized !== raw) {
    await storage.clear(normalized);
  }
  resetNodeOAuthStorageCache();
}

// ../../core/mcp/inspectorClient.ts
import { Client } from "@modelcontextprotocol/client";
import {
  ProgressTokenSchema,
  RequestMetaSchema
} from "@modelcontextprotocol/core";

// ../../core/json/xMcpHeader.ts
var X_MCP_HEADER_KEY = "x-mcp-header";
var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
var RFC9110_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
var PERMITTED_X_MCP_HEADER_TYPES = /* @__PURE__ */ new Set([
  "string",
  "integer",
  "boolean",
  "number"
]);
var NON_REACHABLE_SUBSCHEMA_KEYWORDS = [
  "items",
  "prefixItems",
  "contains",
  "additionalProperties",
  "unevaluatedProperties",
  "unevaluatedItems",
  "propertyNames",
  "patternProperties",
  "dependentSchemas",
  "oneOf",
  "anyOf",
  "allOf",
  "not",
  "if",
  "then",
  "else",
  "$defs",
  "definitions"
];
var OBJECT_VALUED_SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
  "patternProperties",
  "dependentSchemas",
  "$defs",
  "definitions"
]);
function pathName(path5) {
  return path5.length === 0 ? "<root>" : path5.join(".");
}
function isRecord2(node) {
  return node !== null && typeof node === "object";
}
function scanXMcpHeaderDeclarations(inputSchema) {
  const declarations = [];
  const seenLower = /* @__PURE__ */ new Map();
  const visit = (node, path5, reachable) => {
    if (!isRecord2(node)) return void 0;
    const schema = node;
    if (X_MCP_HEADER_KEY in schema) {
      if (!reachable || path5.length === 0) {
        return `${pathName(path5)}: x-mcp-header is only permitted on properties statically reachable via a chain of 'properties' keys (not under items, additionalProperties, oneOf/anyOf/allOf/not, if/then/else, or $ref)`;
      }
      const raw = schema[X_MCP_HEADER_KEY];
      if (typeof raw !== "string" || raw.length === 0) {
        return `${pathName(path5)}: x-mcp-header MUST be a non-empty string`;
      }
      if (!RFC9110_TOKEN.test(raw)) {
        return `${pathName(path5)}: x-mcp-header '${raw}' is not a valid RFC 9110 token (no spaces, control characters or HTTP delimiters)`;
      }
      const type = typeof schema.type === "string" ? schema.type : void 0;
      if (type === void 0 || !PERMITTED_X_MCP_HEADER_TYPES.has(type)) {
        return `${pathName(path5)}: x-mcp-header is only permitted on primitive-typed properties (string, integer, boolean); got ${type ?? "<none>"}`;
      }
      const lower = raw.toLowerCase();
      const prior = seenLower.get(lower);
      if (prior !== void 0) {
        return `x-mcp-header '${raw}' is not case-insensitively unique (also declared as '${prior}')`;
      }
      seenLower.set(lower, raw);
      declarations.push({ path: path5, headerName: raw, type });
    }
    const properties = schema.properties;
    if (isRecord2(properties)) {
      for (const [key, child] of Object.entries(properties)) {
        const fault2 = visit(child, [...path5, key], reachable);
        if (fault2 !== void 0) return fault2;
      }
    }
    for (const k of NON_REACHABLE_SUBSCHEMA_KEYWORDS) {
      const sub = schema[k];
      if (sub === void 0) continue;
      const branches = Array.isArray(sub) ? sub : isRecord2(sub) && OBJECT_VALUED_SUBSCHEMA_KEYWORDS.has(k) ? Object.values(sub) : [sub];
      for (const branch of branches) {
        const fault2 = visit(branch, [...path5, `<${k}>`], false);
        if (fault2 !== void 0) return fault2;
      }
    }
    return void 0;
  };
  const fault = visit(inputSchema, [], true);
  return fault === void 0 ? { valid: true, declarations } : { valid: false, reason: fault };
}
var BASE64_SENTINEL_PREFIX = "=?base64?";
var BASE64_SENTINEL_SUFFIX = "?=";
function mcpParamPrimitiveToString(value) {
  if (typeof value === "string") return value;
  if (typeof value === "boolean") return value ? "true" : "false";
  if (typeof value === "number") {
    if (!Number.isFinite(value)) return void 0;
    if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
      return void 0;
    }
    return String(value);
  }
  return void 0;
}
function needsBase64(s) {
  if (s.length === 0) return true;
  if (s.startsWith(BASE64_SENTINEL_PREFIX) && s.endsWith(BASE64_SENTINEL_SUFFIX)) {
    return true;
  }
  if (s !== s.trim()) return true;
  for (let i = 0; i < s.length; i++) {
    const c = s.codePointAt(i);
    if (c === 9 || c >= 32 && c <= 126) continue;
    return true;
  }
  return false;
}
function utf8ToBase64(s) {
  const bytes = new TextEncoder().encode(s);
  let bin = "";
  for (const b of bytes) bin += String.fromCodePoint(b);
  return btoa(bin);
}
function encodeMcpParamValue(value) {
  return needsBase64(value) ? `${BASE64_SENTINEL_PREFIX}${utf8ToBase64(value)}${BASE64_SENTINEL_SUFFIX}` : value;
}
function valueAtPath(root, path5) {
  let node = root;
  for (const key of path5) {
    if (node === null || typeof node !== "object") return void 0;
    node = node[key];
  }
  return node;
}
function buildMcpParamHeaders(declarations, args) {
  const out = {};
  for (const decl of declarations) {
    const raw = valueAtPath(args, decl.path);
    if (raw === void 0 || raw === null) continue;
    const stringValue = mcpParamPrimitiveToString(raw);
    if (stringValue === void 0) continue;
    out[`${MCP_PARAM_HEADER_PREFIX}${decl.headerName}`] = encodeMcpParamValue(stringValue);
  }
  return out;
}
function mcpParamHeadersForTool(tool, args) {
  const scan = scanXMcpHeaderDeclarations(tool.inputSchema);
  if (!scan.valid || scan.declarations.length === 0) return {};
  return buildMcpParamHeaders(scan.declarations, args);
}

// ../../core/mcp/messageTrackingTransport.ts
var MessageTrackingTransport = class {
  baseTransport;
  callbacks;
  negotiatedProtocolVersion;
  hooks;
  constructor(baseTransport, callbacks, hooks = {}) {
    this.baseTransport = baseTransport;
    this.callbacks = callbacks;
    this.hooks = hooks;
  }
  async start() {
    return this.baseTransport.start();
  }
  async send(message, options) {
    if ("id" in message && message.id !== null && message.id !== void 0) {
      if ("result" in message || "error" in message) {
        this.callbacks.trackResponse?.(
          message,
          "client"
        );
      } else if ("method" in message) {
        this.callbacks.trackRequest?.(message, "client");
      }
    } else if ("method" in message) {
      this.callbacks.trackNotification?.(
        message,
        "client"
      );
    }
    return this.baseTransport.send(message, options);
  }
  async close() {
    return this.baseTransport.close();
  }
  get onclose() {
    return this.baseTransport.onclose;
  }
  set onclose(handler) {
    this.baseTransport.onclose = handler;
  }
  get onerror() {
    return this.baseTransport.onerror;
  }
  set onerror(handler) {
    this.baseTransport.onerror = handler;
  }
  get onmessage() {
    return this.baseTransport.onmessage;
  }
  set onmessage(handler) {
    if (handler) {
      this.baseTransport.onmessage = (message, extra) => {
        if ("id" in message && message.id !== null && message.id !== void 0) {
          if ("result" in message || "error" in message) {
            this.callbacks.trackResponse?.(
              message,
              "server"
            );
            if (this.hooks.consumeIncomingResponse?.(
              message
            )) {
              return;
            }
            if (this.hooks.rewriteIncomingResult && "result" in message) {
              const rewritten = this.hooks.rewriteIncomingResult(
                message
              );
              if (rewritten !== message) {
                handler(rewritten, extra);
                return;
              }
            }
          } else if ("method" in message) {
            this.callbacks.trackRequest?.(message, "server");
          }
        } else if ("method" in message) {
          this.callbacks.trackNotification?.(
            message,
            "server"
          );
        }
        handler(message, extra);
      };
    } else {
      this.baseTransport.onmessage = void 0;
    }
  }
  get sessionId() {
    return this.baseTransport.sessionId;
  }
  // Implemented as a concrete method (rather than delegating the base
  // transport's optional `setProtocolVersion`) so the SDK Client always
  // invokes it after the initialize handshake — including for stdio, whose
  // base transport has no `setProtocolVersion`. We capture the negotiated
  // version for the UI here, then forward to the base transport when it
  // cares (HTTP transports stamp it into subsequent request headers).
  setProtocolVersion(version) {
    this.negotiatedProtocolVersion = version;
    this.baseTransport.setProtocolVersion?.(version);
  }
  /** MCP protocol version negotiated during initialize, once connected. */
  get protocolVersion() {
    return this.negotiatedProtocolVersion;
  }
};

// ../../core/mcp/inspectorClient.ts
import { ProtocolError as ProtocolError2, ProtocolErrorCode as ProtocolErrorCode2 } from "@modelcontextprotocol/client";
import {
  isInputRequiredResult,
  withInputRequired,
  LOG_LEVEL_META_KEY,
  CLIENT_CAPABILITIES_META_KEY,
  CLIENT_INFO_META_KEY,
  PROTOCOL_VERSION_META_KEY,
  RELATED_TASK_META_KEY as RELATED_TASK_META_KEY3
} from "@modelcontextprotocol/client";

// ../../core/mcp/modernTaskSchemas.ts
import { z } from "zod/v4";
var TASKS_EXTENSION_KEY = "io.modelcontextprotocol/tasks";
var MODERN_PROTOCOL_VERSION2 = "2026-07-28";
var TASKS_EXTENSION_CLIENT_CAPABILITY = {
  extensions: { [TASKS_EXTENSION_KEY]: {} }
};
var MODERN_TASK_HANDLE_META = "io.modelcontextprotocol/inspector/modernTaskHandle";
function isModernCreateTaskResult(result) {
  return typeof result === "object" && result !== null && result.resultType === "task" && typeof result.taskId === "string";
}
var ModernTaskStatusSchema = z.enum([
  "working",
  "input_required",
  "completed",
  "failed",
  "cancelled"
]);
var ModernDetailedTaskSchema = z.looseObject({
  taskId: z.string(),
  status: ModernTaskStatusSchema,
  statusMessage: z.string().optional(),
  createdAt: z.string(),
  lastUpdatedAt: z.string(),
  ttlMs: z.number().nullable().optional(),
  pollIntervalMs: z.number().optional(),
  /** Present on `completed`: the original request's result (e.g. CallToolResult). */
  result: z.record(z.string(), z.unknown()).optional(),
  /** Present on `failed`: the JSON-RPC error that ended the task. */
  error: z.record(z.string(), z.unknown()).optional(),
  /** Present on `input_required`: embedded server→client requests, keyed by id. */
  inputRequests: z.record(z.string(), z.unknown()).optional()
});
var ModernGetTaskResultSchema = ModernDetailedTaskSchema;
var ModernUpdateTaskResultSchema = z.looseObject({});
var ModernCancelTaskResultSchema = z.looseObject({});
function normalizeModernTask(modern) {
  const { ttlMs, pollIntervalMs, ...rest } = modern;
  const normalized = { ...rest };
  normalized.ttl = ttlMs ?? null;
  if (pollIntervalMs != null) normalized.pollInterval = pollIntervalMs;
  return normalized;
}
function readInputRequests(modern) {
  return modern.inputRequests;
}

// ../../core/mcp/extensions.ts
var EMA_EXTENSION_KEY = "io.modelcontextprotocol/enterprise-managed-authorization";
var UI_EXTENSION_KEY = "io.modelcontextprotocol/ui";
var MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
var ADVERTISABLE_EXTENSIONS = [
  {
    key: TASKS_EXTENSION_KEY,
    label: "Tasks (io.modelcontextprotocol/tasks)",
    // The modern Tasks extension (SEP-2663). Advertised by default so the SDK
    // stamps it into every modern request envelope — the per-request
    // declaration a server requires before it may return a `CreateTaskResult`
    // (server-directed task creation). Harmless on legacy (extensions ignored).
    defaultAdvertised: true
  },
  {
    key: UI_EXTENSION_KEY,
    label: "MCP Apps UI (io.modelcontextprotocol/ui)",
    // The MCP Apps UI extension. The Inspector always renders App tools, so it
    // advertises this by default with the App resource MIME type it supports —
    // a conforming server checks the `mimeTypes` before serving a UI resource.
    defaultAdvertised: true,
    advertisement: { mimeTypes: [MCP_APP_MIME_TYPE] }
  }
];
function buildClientExtensions(input) {
  const map = {};
  for (const ext of ADVERTISABLE_EXTENSIONS) {
    const advertised = input.advertised?.[ext.key] ?? ext.defaultAdvertised;
    if (advertised) {
      map[ext.key] = ext.advertisement ? structuredClone(ext.advertisement) : {};
    }
  }
  const uiAdvertisement = map[UI_EXTENSION_KEY];
  if (input.appElicitation && uiAdvertisement) {
    map[UI_EXTENSION_KEY] = { ...uiAdvertisement, elicitation: {} };
  }
  if (input.enterpriseManaged) {
    map[EMA_EXTENSION_KEY] = {};
  }
  return map;
}

// ../../core/mcp/appElicitation.ts
import { ElicitResultSchema } from "@modelcontextprotocol/core";
function uiExtension(capabilities) {
  const ext = capabilities?.extensions?.[UI_EXTENSION_KEY];
  return typeof ext === "object" && ext !== null ? ext : void 0;
}
function getUiClientCapability(capabilities) {
  return uiExtension(capabilities);
}
function getUiServerCapability(capabilities) {
  return uiExtension(capabilities);
}
function supportsAppElicitation(clientCapabilities, serverCapabilities) {
  const clientUi = getUiClientCapability(clientCapabilities);
  const serverUi = getUiServerCapability(serverCapabilities);
  return Boolean(
    clientCapabilities?.elicitation?.form && clientUi?.mimeTypes?.includes(MCP_APP_MIME_TYPE) && clientUi.elicitation && serverUi?.elicitation
  );
}
var ELICITATION_UI_URI_ERROR = "Elicitation UI resourceUri must be an absolute ui:// URI";
function validateElicitationUiResourceUri(resourceUri) {
  let parsed;
  try {
    parsed = new URL(resourceUri);
  } catch {
    throw new Error(ELICITATION_UI_URI_ERROR);
  }
  if (parsed.protocol !== "ui:" || !/^ui:\/\//i.test(resourceUri) || parsed.host.length === 0) {
    throw new Error(ELICITATION_UI_URI_ERROR);
  }
}
function getElicitationUiResourceUri(params) {
  const ui = params._meta?.ui;
  if (typeof ui !== "object" || ui === null) return void 0;
  const resourceUri = ui.resourceUri;
  if (resourceUri === void 0) return void 0;
  if (typeof resourceUri !== "string") {
    throw new Error("Elicitation UI resourceUri must be a string");
  }
  validateElicitationUiResourceUri(resourceUri);
  return resourceUri;
}
function isFormElicitation(params) {
  const mode = params.mode;
  return mode === void 0 || mode === "form";
}
var ELICIT_RESULT_ERROR = "App returned an invalid elicitation result";
function validateAppElicitResult(provider, params, result) {
  const parsed = ElicitResultSchema.safeParse(result);
  if (!parsed.success) {
    return `${ELICIT_RESULT_ERROR}: ${parsed.error.issues[0]?.message ?? "does not match ElicitResult"}`;
  }
  if (parsed.data.action !== "accept") return void 0;
  const content = parsed.data.content;
  if (typeof content !== "object" || content === null || Array.isArray(content)) {
    return "App accepted the elicitation without a content object";
  }
  const schema = params.requestedSchema;
  if (typeof schema !== "object" || schema === null) return void 0;
  try {
    const validate = provider.getValidator(schema);
    const validation = validate(content);
    return validation.valid ? void 0 : `App content does not match the requested schema: ${validation.errorMessage}`;
  } catch {
    return void 0;
  }
}

// ../../core/mcp/inspectorClient.ts
import {
  EmptyResultSchema,
  CallToolResultSchema,
  GetPromptResultSchema,
  ReadResourceResultSchema,
  ListTasksRequestSchema,
  GetTaskRequestSchema,
  GetTaskPayloadRequestSchema,
  CancelTaskRequestSchema,
  TaskStatusNotificationSchema,
  CreateTaskResultSchema,
  GetTaskResultSchema,
  CancelTaskResultSchema,
  ListTasksResultSchema,
  ListToolsResultSchema,
  ListResourcesResultSchema,
  ListResourceTemplatesResultSchema,
  ListPromptsResultSchema,
  ResourceSchema,
  ResourceTemplateSchema,
  PromptSchema
} from "@modelcontextprotocol/core";
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";

// ../../core/mcp/toolOutputValidation.ts
function validateToolOutput(provider, tool, result) {
  if (!tool.outputSchema) return void 0;
  const structured = result.structuredContent;
  if (structured == null) {
    return result.isError ? void 0 : `Tool "${tool.name}" declares an output schema but returned no structured content`;
  }
  try {
    const validate = provider.getValidator(tool.outputSchema);
    const validation = validate(structured);
    return validation.valid ? void 0 : validation.errorMessage;
  } catch {
    return void 0;
  }
}

// ../../core/mcp/listSalvage.ts
import { z as z2 } from "zod/v4";
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
import { ToolSchema } from "@modelcontextprotocol/core";
var LIST_MAX_PAGES = 64;
function listPaginationExceeded(method) {
  return new Error(
    `Salvage walk for ${method} exceeded ${LIST_MAX_PAGES} pages without the server's pagination converging`
  );
}
function isClientDecodeRejection(err) {
  return SdkError.isInstance(err) && (err.code === SdkErrorCode.InvalidResult || err.code === SdkErrorCode.UnsupportedResultType);
}
function isSalvageableRejection(err) {
  return SdkError.isInstance(err) && err.code === SdkErrorCode.InvalidResult;
}
function lenientListPageSchema(resultSchema, itemsKey) {
  return resultSchema.extend({ [itemsKey]: z2.array(z2.unknown()) });
}
function nextCursorOf(page) {
  if (typeof page !== "object" || page === null) return void 0;
  const value = page.nextCursor;
  return typeof value === "string" ? value : void 0;
}
var ModernResultEnvelopeSchema = z2.looseObject({
  resultType: z2.literal("complete"),
  // A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
  // envelope violation the strict path rejects, and accepting it here on the
  // strength of an unrelated bad entry would hide it.
  ttlMs: z2.int().min(0),
  cacheScope: z2.enum(["public", "private"])
});
function rawItemsOf(page, itemsKey) {
  if (typeof page !== "object" || page === null) return void 0;
  const value = page[itemsKey];
  return Array.isArray(value) ? value : void 0;
}
function labelForRawItem(value) {
  if (typeof value !== "object" || value === null) return void 0;
  const record = value;
  for (const key of ["name", "uriTemplate", "uri", "title"]) {
    const candidate = record[key];
    if (typeof candidate === "string" && candidate.length > 0) return candidate;
  }
  return void 0;
}
function describeIssues(error) {
  const [issue] = error.issues;
  if (!issue) return "did not match the expected shape";
  const path5 = issue.path.join(".");
  const detail = expectedDetail(issue);
  const message = detail ? `${issue.message} (${detail})` : issue.message;
  return path5.length > 0 ? `${path5}: ${message}` : message;
}
function expectedDetail(issue) {
  if (!("expected" in issue) || typeof issue.expected !== "string") {
    return void 0;
  }
  const detail = `expected ${issue.expected}`;
  return issue.message.includes(detail) ? void 0 : detail;
}
function salvageListItems({
  method,
  items,
  schema,
  startIndex = 0
}) {
  const valid = [];
  const malformed = [];
  items.forEach((item, offset) => {
    const parsed = schema.safeParse(item);
    if (parsed.success) {
      valid.push(parsed.data);
      return;
    }
    const label = labelForRawItem(item);
    malformed.push({
      method,
      index: startIndex + offset,
      ...label !== void 0 && { label },
      reason: describeIssues(parsed.error)
    });
  });
  return { valid, malformed };
}
function summarizeMalformed(malformed) {
  const [first] = malformed;
  if (!first) return "";
  const where = first.label ?? `index ${first.index}`;
  const rest = malformed.length > 1 ? ` (+${malformed.length - 1} more)` : "";
  return `Dropped ${malformed.length} malformed ${malformed.length === 1 ? "entry" : "entries"} \u2014 ${where}: ${first.reason}${rest}`;
}
function toolItemSchemaForEra(isModern) {
  return isModern ? eraToolItemSchema(ModernToolWireSchema, stripExecution) : eraToolItemSchema(LegacyToolWireSchema, (tool) => tool);
}
var LegacyToolWireSchema = ToolSchema.extend({
  outputSchema: ToolSchema.shape.inputSchema.optional()
});
var ModernToolWireSchema = ToolSchema.extend({
  inputSchema: z2.looseObject({
    type: z2.literal("object"),
    $schema: z2.string().optional()
  })
});
function stripExecution(tool) {
  if (tool.execution === void 0) return tool;
  const { execution: _execution, ...rest } = tool;
  return rest;
}
function eraToolItemSchema(wire, normalize) {
  return wire.transform((parsed) => normalize(parsed));
}

// ../../core/mcp/taskNotificationSchemas.ts
import { NotificationSchema } from "@modelcontextprotocol/core";
import { z as z3 } from "zod/v4";
var TasksListChangedNotificationSchema = NotificationSchema.extend({
  method: z3.literal("notifications/tasks/list_changed"),
  params: z3.record(z3.string(), z3.unknown()).optional()
});

// ../../core/mcp/uriTemplate.ts
import { UriTemplate } from "@modelcontextprotocol/client";
var OPERATORS = ["+", "#", ".", "/", ";", "?", "&"];
var NAMED_OPERATORS = /* @__PURE__ */ new Set([";", "?", "&"]);
var VARCHAR = String.raw`(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})`;
var TOLERATED_VARCHAR = String.raw`[-~]`;
function varnameGrammar(varchar) {
  return `${varchar}(?:\\.?${varchar})*`;
}
var MODIFIER = String.raw`(?:\*|:([1-9][0-9]{0,3}))?`;
var CONFORMING_VARSPEC = new RegExp(
  `^(${varnameGrammar(VARCHAR)})${MODIFIER}$`
);
var TOLERATED_VARSPEC = new RegExp(
  `^(${varnameGrammar(`(?:${VARCHAR}|${TOLERATED_VARCHAR})`)})${MODIFIER}$`
);
function parseVarSpec(raw) {
  const conforming = CONFORMING_VARSPEC.exec(raw);
  const match = conforming ?? TOLERATED_VARSPEC.exec(raw);
  if (match === null) return "invalid";
  const [, name, maxLength] = match;
  return {
    name,
    ...maxLength === void 0 ? {} : { maxLength: +maxLength },
    ...conforming === null ? { conforming: false } : {}
  };
}
function parseUriTemplate(uriTemplate) {
  const parts = [];
  let literal = "";
  let i = 0;
  while (i < uriTemplate.length) {
    if (uriTemplate[i] !== "{") {
      literal += uriTemplate[i];
      i += 1;
      continue;
    }
    const end = uriTemplate.indexOf("}", i);
    if (end === -1) {
      literal += uriTemplate.slice(i);
      break;
    }
    if (literal) {
      parts.push({ kind: "literal", text: literal });
      literal = "";
    }
    const body = uriTemplate.slice(i + 1, end);
    const operator = OPERATORS.find((op) => body.startsWith(op)) ?? "";
    const parsed = body.slice(operator.length).split(",").map(parseVarSpec);
    const varspecs = parsed.filter(
      (spec) => spec !== "invalid"
    );
    parts.push({
      kind: "expression",
      source: uriTemplate.slice(i, end + 1),
      operator,
      varspecs,
      names: varspecs.map((spec) => spec.name),
      invalid: parsed.includes("invalid")
    });
    i = end + 1;
  }
  if (literal) parts.push({ kind: "literal", text: literal });
  return parts;
}
function readValue(values, name) {
  return Object.hasOwn(values, name) ? values[name] : void 0;
}
var ALLOW_RESERVED = /[^A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/gu;
function encodeLiteral(text) {
  return text.split(/([{}])/g).map(
    (chunk, index) => index % 2 === 1 ? chunk : encodeAllowReserved(chunk)
  ).join("");
}
function encodeAllowReserved(value) {
  return value.split(/(%[0-9A-Fa-f]{2})/g).map(
    (chunk, index) => index % 2 === 1 ? chunk : chunk.replace(ALLOW_RESERVED, (char) => encodeURIComponent(char))
  ).join("");
}
var FORCE_ENCODED = {
  "!": "%21",
  "'": "%27",
  "(": "%28",
  ")": "%29",
  "*": "%2A"
};
function encodeUnreserved(value) {
  return encodeURIComponent(value).replace(
    /[!'()*]/g,
    (char) => FORCE_ENCODED[char]
  );
}
function allowsReserved(operator) {
  return operator === "+" || operator === "#";
}
function encodeValue(value, operator) {
  return allowsReserved(operator) ? encodeAllowReserved(value) : encodeUnreserved(value);
}
var CONTINUATION = String.raw`%[89AB][0-9A-F]`;
var UTF8_SEQUENCE = [
  String.raw`(?:%F0%(?:9[0-9A-F]|[AB][0-9A-F])|%F[1-3]${CONTINUATION}|%F4%8[0-9A-F])${CONTINUATION}${CONTINUATION}`,
  String.raw`(?:%E0%[AB][0-9A-F]|%E[1-9ABCEF]${CONTINUATION}|%ED%[89][0-9A-F])${CONTINUATION}`,
  String.raw`%(?:C[2-9A-F]|D[0-9A-F])${CONTINUATION}`
].join("|");
var PREFIX_UNIT = new RegExp(`${UTF8_SEQUENCE}|%[0-9A-F]{2}|[\\s\\S]`, "giu");
function prefixUnits(value) {
  return value.match(PREFIX_UNIT) ?? [];
}
function renderValue(value, spec, operator) {
  const truncated = spec.maxLength === void 0 ? value : prefixUnits(value).slice(0, spec.maxLength).join("");
  return encodeValue(truncated, operator);
}
function expandTemplateExpression(part, values) {
  const present = part.varspecs.flatMap((spec) => {
    const value = readValue(values, spec.name);
    return value === void 0 ? [] : [{ spec, value }];
  });
  if (present.length === 0) return "";
  const { operator } = part;
  if (NAMED_OPERATORS.has(operator)) {
    const pairs = present.map(({ spec, value }) => {
      const rendered2 = renderValue(value, spec, operator);
      return operator === ";" && rendered2.length === 0 ? spec.name : `${spec.name}=${rendered2}`;
    });
    return operator === ";" ? `;${pairs.join(";")}` : `${operator}${pairs.join("&")}`;
  }
  const rendered = present.map(
    ({ spec, value }) => renderValue(value, spec, operator)
  );
  switch (operator) {
    case "#":
      return `#${rendered.join(",")}`;
    case ".":
      return `.${rendered.join(".")}`;
    case "/":
      return `/${rendered.join("/")}`;
    // "" and "+" — a bare comma-joined list, no prefix.
    default:
      return rendered.join(",");
  }
}
function expandParts(parts, values) {
  return parts.map(
    (part) => part.kind === "literal" ? encodeLiteral(part.text) : expandTemplateExpression(part, values)
  ).join("");
}
function expandUriTemplateStrict(uriTemplate, values) {
  const sdk = sdkTemplateError(uriTemplate);
  if (sdk) throw new Error(sdk);
  const parts = parseUriTemplate(uriTemplate);
  const invalid = partsError(uriTemplate, parts);
  if (invalid) throw new Error(invalid);
  const tooLong = valueLengthError(values, declaredNames(parts));
  if (tooLong) throw new Error(tooLong);
  return expandParts(parts, values);
}
function sdkTemplateError(uriTemplate) {
  try {
    new UriTemplate(uriTemplate);
    return null;
  } catch (error) {
    return error instanceof Error ? error.message : String(error);
  }
}
function partsError(uriTemplate, parts) {
  const stray = parts.find(
    (part) => part.kind === "literal" && /[{}]/.test(part.text)
  );
  if (stray) {
    return `Unmatched brace in "${uriTemplate}" \u2014 an expression must be a complete {\u2026}.`;
  }
  const bad = parts.find((part) => part.kind === "expression" && part.invalid);
  if (bad) {
    return `Invalid RFC 6570 varspec in "${uriTemplate}": ${bad.kind === "expression" ? bad.source : ""} \u2014 each varspec must name a variable, optionally with an explode (*) or a 1-9999 prefix modifier.`;
  }
  return null;
}
var MAX_VALUE_LENGTH = 1e6;
function valueLengthError(values, names) {
  const considered = names === void 0 ? Object.keys(values) : [...new Set(names)];
  for (const name of considered) {
    const value = readValue(values, name);
    if (value !== void 0 && value.length > MAX_VALUE_LENGTH) {
      return `The value for "${name}" exceeds the ${MAX_VALUE_LENGTH}-character limit.`;
    }
  }
  return null;
}
function declaredNames(parts) {
  return [
    ...new Set(
      parts.flatMap((part) => part.kind === "expression" ? part.names : [])
    )
  ];
}

// ../../core/mcp/typedEventTarget.ts
var TypedEventGeneric = class extends CustomEvent {
  constructor(type, detail) {
    super(type, { detail });
  }
};
var TypedEventTarget = class extends EventTarget {
  dispatchTypedEvent(type, ...args) {
    const detail = args[0] ?? void 0;
    this.dispatchEvent(new TypedEventGeneric(type, detail));
  }
  addEventListener(type, listener, options) {
    super.addEventListener(
      type,
      listener,
      options
    );
  }
  removeEventListener(type, listener, options) {
    super.removeEventListener(
      type,
      listener,
      options
    );
  }
};

// ../../core/mcp/inspectorClientEventTarget.ts
var InspectorClientEventTarget = class extends TypedEventTarget {
};

// ../../core/mcp/samplingCreateMessage.ts
import { RELATED_TASK_META_KEY } from "@modelcontextprotocol/client";
var SamplingCreateMessage = class {
  id;
  timestamp;
  request;
  taskId;
  /**
   * How this request reached the Inspector — a legacy server→client request or
   * a modern MRTR `input_required` round. Drives era-accurate copy in the
   * pending-request UI. Defaults to `"server-request"` so existing call sites
   * (and stories) keep the legacy semantics unchanged.
   */
  origin;
  resolvePromise;
  rejectPromise;
  onRemove;
  constructor(request, resolve7, reject, onRemove, origin = "server-request") {
    this.onRemove = onRemove;
    this.id = `sampling-${crypto.randomUUID()}`;
    this.timestamp = /* @__PURE__ */ new Date();
    this.request = request;
    const relatedTask = request.params?._meta?.[RELATED_TASK_META_KEY];
    this.taskId = relatedTask?.taskId;
    this.origin = origin;
    this.resolvePromise = resolve7;
    this.rejectPromise = reject;
  }
  /**
   * Respond to the sampling request with a result
   */
  async respond(result) {
    if (!this.resolvePromise) {
      throw new Error("Request already resolved or rejected");
    }
    this.resolvePromise(result);
    this.resolvePromise = void 0;
    this.rejectPromise = void 0;
    this.remove();
  }
  /**
   * Reject the sampling request with an error
   */
  async reject(error) {
    if (!this.rejectPromise) {
      throw new Error("Request already resolved or rejected");
    }
    this.rejectPromise(error);
    this.resolvePromise = void 0;
    this.rejectPromise = void 0;
    this.remove();
  }
  /**
   * Settle a still-pending sample as cancelled, without removing it from the
   * queue. Called from `InspectorClient`'s `clearPendingPeerRequests()`, which
   * serves every route that drops the queue wholesale — each route out of a
   * connection, and the top of `connect()` as a backstop for the one route in
   * that settles nothing. The category, not a count: that set has grown.
   *
   * Unlike an elicitation there is no internal awaiter to unblock, but on the
   * plain server-request shape the *server* is one: we accepted its
   * `sampling/createMessage`, so dropping the request without settling means no
   * response frame is ever written and it waits forever. That is reachable when
   * the transport outlives the failed attempt — `connect()` keeps it when an
   * auth provider holds it open — so settle rather than discard. Rejecting is
   * the right settle: it is what the UI's decline path sends, and the SDK turns
   * it into a JSON-RPC error response. No-op once already resolved.
   *
   * A *task-augmented* sample sits in the same queue but already answered the
   * wire request synchronously with a `CreateTaskResult`, so nothing is waiting
   * on a frame; settling there is about not leaving the task in
   * `input_required` limbo. Its reject reaches the receiver task's payload
   * promise, which `InspectorClient` marks handled at creation for exactly this
   * reason.
   *
   * Deliberately does not call `onRemove`, for the same reason as
   * `ElicitationCreateMessage.cancel()`: the caller iterates the queue and
   * clears it itself, so removing here would splice mid-iteration — skipping
   * every other entry and leaving those requests unanswered.
   */
  cancel() {
    if (this.rejectPromise) {
      this.rejectPromise(new Error("Connection torn down"));
    }
    this.resolvePromise = void 0;
    this.rejectPromise = void 0;
  }
  /**
   * Remove this pending sample from the list
   */
  remove() {
    this.onRemove(this.id);
  }
};

// ../../core/mcp/elicitationCreateMessage.ts
import { RELATED_TASK_META_KEY as RELATED_TASK_META_KEY2 } from "@modelcontextprotocol/client";
var ElicitationCreateMessage = class {
  id;
  timestamp;
  request;
  taskId;
  /**
   * How this request reached the Inspector — a legacy server→client request or
   * a modern MRTR `input_required` round. Drives era-accurate copy in the
   * pending-request UI. Defaults to `"server-request"` so existing call sites
   * (and stories) keep the legacy semantics unchanged.
   */
  origin;
  resolvePromise;
  /**
   * Rejects the originating call with an error. Set for task-augmented elicit
   * (so the server's `tasks/result` receives the error on decline) and for
   * MRTR-driven elicitations (so a genuine failure aborts the tool call).
   */
  rejectCallback;
  onRemove;
  constructor(request, resolve7, onRemove, reject, origin = "server-request") {
    this.onRemove = onRemove;
    this.id = `elicitation-${crypto.randomUUID()}`;
    this.timestamp = /* @__PURE__ */ new Date();
    this.request = request;
    const relatedTask = request.params?._meta?.[RELATED_TASK_META_KEY2];
    this.taskId = relatedTask?.taskId;
    this.origin = origin;
    this.resolvePromise = resolve7;
    this.rejectCallback = reject;
  }
  /**
   * Reject the elicitation (e.g. when user declines). Only has effect when this
   * request was task-augmented; then the server's tasks/result will receive the error.
   */
  reject(error) {
    if (this.rejectCallback) {
      this.rejectCallback(error);
      this.rejectCallback = void 0;
    }
  }
  /**
   * Respond to the elicitation request with a result
   */
  async respond(result) {
    if (!this.resolvePromise) {
      throw new Error("Request already resolved");
    }
    this.resolvePromise(result);
    this.resolvePromise = void 0;
    this.remove();
  }
  /**
   * Resolve this elicitation as accepted, but only if it is still pending.
   *
   * Used by the URL-mode `notifications/elicitation/complete` handler to
   * auto-advance an open URL elicitation when the server signals the
   * out-of-band flow finished. It is a no-op once the user has already
   * responded — that guard (plus the modal's own once-guard) keeps `respond()`
   * from throwing its "already resolved" error on a race between the manual
   * "I've completed it" click and the server's completion notification.
   */
  completeIfPending() {
    if (this.resolvePromise) {
      void this.respond({ action: "accept" });
    }
  }
  /**
   * Settle a still-pending elicitation as cancelled, without removing it from
   * the queue. Called from `InspectorClient`'s `clearPendingPeerRequests()`,
   * which serves every route that drops the queue wholesale — each route out of
   * a connection, and the top of `connect()` as a backstop for the one route in
   * that settles nothing — so an awaiting caller (notably the error-path
   * `awaitUrlElicitation` that blocks `callTool`) doesn't hang forever. The
   * category, not a count: that set has grown. No-op once already resolved.
   *
   * Deliberately does not call `onRemove`: that caller iterates the queue and
   * clears it itself, so removing here would splice mid-iteration — skipping
   * every other entry and leaving those promises unsettled, the exact hang this
   * method exists to prevent.
   */
  cancel() {
    if (this.resolvePromise) {
      this.resolvePromise({ action: "cancel" });
      this.resolvePromise = void 0;
    }
    this.rejectCallback = void 0;
  }
  /**
   * Remove this pending elicitation from the list
   */
  remove() {
    this.onRemove(this.id);
  }
};

// ../../core/mcp/urlElicitation.ts
import {
  ProtocolErrorCode,
  ProtocolError,
  UrlElicitationRequiredError
} from "@modelcontextprotocol/client";
var UrlElicitationLoopError = class extends Error {
  /** The URL the server repeated. */
  url;
  constructor(url) {
    super(
      `The server asked for the same URL elicitation again (${url}); cancelling the call to avoid a loop.`
    );
    this.name = "UrlElicitationLoopError";
    this.url = url;
  }
};
function getUrlElicitationsFromError(error) {
  if (error instanceof UrlElicitationRequiredError) {
    return error.elicitations ?? [];
  }
  if (error instanceof ProtocolError && error.code === ProtocolErrorCode.UrlElicitationRequired) {
    const data = error.data;
    return data?.elicitations ?? [];
  }
  return null;
}

// ../../core/mcp/toolCallCancelledError.ts
var ToolCallCancelledError = class extends Error {
  /** The name of the tool whose call was cancelled, when known. */
  toolName;
  constructor(toolName) {
    super(
      toolName ? `Tool call "${toolName}" was cancelled.` : "Tool call was cancelled."
    );
    this.name = "ToolCallCancelledError";
    this.toolName = toolName;
  }
};

// ../../core/auth/endpointOverrides.ts
var OVERRIDE_FIELDS = {
  authorizationUrl: "authorization_endpoint",
  tokenUrl: "token_endpoint"
};
function oauthEndpointUrlError(value) {
  const trimmed = value.trim();
  if (trimmed === "") return void 0;
  let parsed;
  try {
    parsed = new URL(trimmed);
  } catch {
    return `"${trimmed}" is not an absolute URL.`;
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
    return `"${trimmed}" is not an http(s) URL.`;
  }
  if (parsed.username || parsed.password) {
    return "URL must not contain a username or password.";
  }
  return void 0;
}
function normalizeEndpointUrl(value, field) {
  if (value === void 0) return void 0;
  const trimmed = value.trim();
  if (trimmed === "") return void 0;
  const error = oauthEndpointUrlError(trimmed);
  if (error) {
    console.warn(`[oauth] Ignoring \`${field}\`: ${error}`);
    return void 0;
  }
  return trimmed;
}
function normalizeOAuthEndpointOverrides(overrides) {
  if (!overrides) return void 0;
  const authorizationUrl = normalizeEndpointUrl(
    overrides.authorizationUrl,
    "authorizationUrl"
  );
  const tokenUrl = normalizeEndpointUrl(overrides.tokenUrl, "tokenUrl");
  if (!authorizationUrl && !tokenUrl) return void 0;
  return {
    ...authorizationUrl && { authorizationUrl },
    ...tokenUrl && { tokenUrl }
  };
}
function isAuthorizationServerMetadata(value) {
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const doc = value;
  if (typeof doc.issuer !== "string") return false;
  return typeof doc.authorization_endpoint === "string" || typeof doc.token_endpoint === "string";
}
function applyOAuthEndpointOverrides(metadata, overrides) {
  const patched = { ...metadata };
  for (const [key, field] of Object.entries(OVERRIDE_FIELDS)) {
    const override = overrides[key];
    if (override) patched[field] = override;
  }
  return patched;
}
var AS_METADATA_WELL_KNOWN_PATHS = [
  "/.well-known/oauth-authorization-server",
  "/.well-known/openid-configuration"
];
function requestUrlOf(input) {
  if (typeof input === "string") return input;
  if (input instanceof URL) return input.href;
  return input.url;
}
function isAuthorizationServerMetadataRequest(input) {
  const raw = requestUrlOf(input);
  let pathname;
  try {
    pathname = new URL(raw).pathname;
  } catch {
    pathname = raw;
  }
  return AS_METADATA_WELL_KNOWN_PATHS.some((path5) => pathname.includes(path5));
}
function isJsonDocumentResponse(contentType) {
  if (!contentType) return false;
  const mediaType = contentType.split(";")[0].trim().toLowerCase();
  return mediaType === "application/json" || mediaType.endsWith("+json");
}
function responseWithBody(response, body) {
  const headers = new Headers(response.headers);
  headers.delete("content-length");
  headers.delete("content-encoding");
  return new Response(body, {
    status: response.status,
    statusText: response.statusText,
    headers
  });
}
function withOAuthEndpointOverrides(fetchFn, getOverrides) {
  let lastKey;
  let lastNormalized;
  const resolveOverrides = () => {
    const raw = getOverrides();
    const key = JSON.stringify([raw?.authorizationUrl, raw?.tokenUrl]);
    if (key !== lastKey) {
      lastKey = key;
      lastNormalized = normalizeOAuthEndpointOverrides(raw);
    }
    return lastNormalized;
  };
  return async (input, init) => {
    const response = await fetchFn(input, init);
    const overrides = resolveOverrides();
    if (!overrides) return response;
    if (!isAuthorizationServerMetadataRequest(input)) return response;
    if (!response.ok) return response;
    if (!isJsonDocumentResponse(response.headers.get("content-type")))
      return response;
    let parsed;
    try {
      parsed = await response.clone().json();
    } catch {
      return response;
    }
    if (!isAuthorizationServerMetadata(parsed)) return response;
    return responseWithBody(
      response,
      JSON.stringify(applyOAuthEndpointOverrides(parsed, overrides))
    );
  };
}

// ../../core/logging/logger.ts
var noop = () => {
};
function createSilentLogger() {
  const logger = {
    level: "silent",
    fatal: noop,
    error: noop,
    warn: noop,
    info: noop,
    debug: noop,
    trace: noop,
    silent: noop,
    child: () => logger
  };
  return logger;
}
var silentLogger = createSilentLogger();

// ../../core/auth/authorizationParams.ts
var RESERVED_AUTHORIZATION_PARAMS = [
  "client_id",
  "code_challenge",
  "code_challenge_method",
  "redirect_uri",
  "resource",
  "response_type",
  "scope",
  "state"
];
var RESERVED = new Set(RESERVED_AUTHORIZATION_PARAMS);
function isReservedAuthorizationParam(key) {
  return RESERVED.has(key.trim().toLowerCase());
}
function applyAuthorizationParams(authorizationUrl, params) {
  if (!params) return authorizationUrl;
  const applicable = Object.entries(params).filter(([key]) => {
    if (key.trim() === "") return false;
    if (isReservedAuthorizationParam(key)) {
      console.warn(
        `[oauth] Ignoring reserved authorization parameter "${key.trim()}" \u2014 it is set by the authorization flow.`
      );
      return false;
    }
    return true;
  });
  if (applicable.length === 0) return authorizationUrl;
  const merged = new URL(authorizationUrl.href);
  for (const [key, value] of applicable) {
    merged.searchParams.set(key.trim(), value);
  }
  return merged;
}

// ../../core/auth/scopes.ts
import {
  computeScopeUnion as sdkComputeScopeUnion,
  isStrictScopeSuperset as sdkIsStrictScopeSuperset
} from "@modelcontextprotocol/client";
var computeScopeUnion = sdkComputeScopeUnion;
var isStrictScopeSuperset = sdkIsStrictScopeSuperset;
var OFFLINE_ACCESS_SCOPE = "offline_access";
function scopeForDeclinedRefreshGrant(effectiveScope, configuredScope, requiredScopes) {
  if (!effectiveScope) return effectiveScope;
  const configured = configuredScope?.trim().split(/\s+/) ?? [];
  const required = requiredScopes ?? [];
  if (configured.includes(OFFLINE_ACCESS_SCOPE) || required.includes(OFFLINE_ACCESS_SCOPE)) {
    const tokens = effectiveScope.trim().split(/\s+/).filter((token) => token !== "");
    if (!tokens.includes(OFFLINE_ACCESS_SCOPE)) {
      tokens.push(OFFLINE_ACCESS_SCOPE);
    }
    return tokens.join(" ");
  }
  const kept = effectiveScope.trim().split(/\s+/).filter((token) => token !== "" && token !== OFFLINE_ACCESS_SCOPE);
  if (kept.length > 0) return kept.join(" ");
  const fallback = configuredScope?.trim();
  if (fallback) return fallback;
  return void 0;
}
function resolvePersistedScopeAfterGrant(grantedScope, requestedScope) {
  const granted = grantedScope?.trim();
  if (granted) {
    return granted;
  }
  const requested = requestedScope?.trim();
  return requested || void 0;
}
function resolveEffectiveGrantedScope(storedScope, tokenScope) {
  const granted = tokenScope?.trim();
  if (granted) {
    return granted;
  }
  return computeScopeUnion(storedScope, tokenScope);
}

// ../../core/auth/providers.ts
var MutableRedirectUrlProvider = class {
  redirectUrl = "";
  getRedirectUrl() {
    return this.redirectUrl;
  }
};
var CallbackNavigation = class {
  authorizationUrl = null;
  callback;
  constructor(callback) {
    this.callback = callback;
  }
  navigateToAuthorization(authorizationUrl) {
    this.authorizationUrl = authorizationUrl;
    const result = this.callback(authorizationUrl);
    if (result instanceof Promise) {
      void result;
    }
  }
  getAuthorizationUrl() {
    return this.authorizationUrl;
  }
};
var BaseOAuthClientProvider = class {
  capturedAuthUrl = null;
  eventTarget = null;
  suppressAuthorizationNavigation = false;
  /** Cached after {@link prepareForAuth} for sync SDK `clientMetadata.scope`. */
  cachedScope;
  serverUrl;
  storage;
  redirectUrlProvider;
  navigation;
  clientMetadataUrl;
  /** Custom authorization-request parameters (#2018). Authorize URL only. */
  authorizationParams;
  /** Declare the `refresh_token` grant in {@link clientMetadata} (#2068). */
  requestRefreshToken;
  /** Server-configured scope, for the #2068 `offline_access` distinction. */
  configuredScope;
  constructor(serverUrl, oauthConfig) {
    this.serverUrl = serverUrl;
    this.storage = oauthConfig.storage;
    this.redirectUrlProvider = oauthConfig.redirectUrlProvider;
    this.navigation = oauthConfig.navigation;
    this.clientMetadataUrl = oauthConfig.clientMetadataUrl;
    this.authorizationParams = oauthConfig.authorizationParams;
    this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
    this.configuredScope = oauthConfig.configuredScope;
  }
  /**
   * Load persisted scope into {@link cachedScope} before SDK `auth()` (which
   * reads {@link clientMetadata.scope} synchronously).
   */
  async prepareForAuth() {
    this.cachedScope = await this.storage.getScope(this.serverUrl);
  }
  /**
   * Set the event target for dispatching oauthAuthorizationRequired events
   */
  setEventTarget(eventTarget) {
    this.eventTarget = eventTarget;
  }
  /**
   * Get the captured authorization URL (for return value)
   */
  getCapturedAuthUrl() {
    return this.capturedAuthUrl;
  }
  /**
   * Clear the captured authorization URL
   */
  clearCapturedAuthUrl() {
    this.capturedAuthUrl = null;
  }
  /** Capture authorize URL without navigating (step-up confirmation modal). */
  setSuppressAuthorizationNavigation(suppress) {
    this.suppressAuthorizationNavigation = suppress;
  }
  get scope() {
    if (!this.requestRefreshToken) {
      return scopeForDeclinedRefreshGrant(
        this.cachedScope,
        this.configuredScope
      );
    }
    return this.cachedScope;
  }
  get redirectUrl() {
    return this.redirectUrlProvider.getRedirectUrl();
  }
  get redirect_uris() {
    return [this.redirectUrl];
  }
  get clientMetadata() {
    const metadata = {
      redirect_uris: this.redirect_uris,
      token_endpoint_auth_method: "none",
      // #2068: `refresh_token` is declared by default, and dropped when the
      // server's "Request refresh token" setting is off — which stops the SDK
      // *adding* `offline_access` (and so the `prompt=consent` it forces). It
      // does not remove an `offline_access` that reaches the scope another way.
      // See `OAuthProviderConfig.requestRefreshToken`.
      grant_types: this.requestRefreshToken ? ["authorization_code", "refresh_token"] : ["authorization_code"],
      response_types: ["code"],
      client_name: "MCP Inspector",
      client_uri: "https://github.com/modelcontextprotocol/inspector",
      scope: this.scope ?? "",
      // SEP-837: the Inspector is a locally-hosted app reached over localhost, so
      // it registers as a native client. OIDC-flavored ASes default an omitted
      // `application_type` to `"web"`, which forbids loopback redirect URIs and
      // rejects DCR. (The SDK also infers `"native"` from loopback `redirect_uris`;
      // declaring it explicitly keeps the value visible and correct even when the
      // redirect host is not itself a loopback literal.)
      application_type: "native"
    };
    return metadata;
  }
  state() {
    return generateOAuthState();
  }
  async clientInformation(ctx) {
    const preregistered = await this.storage.getClientInformation(
      this.serverUrl,
      true
    );
    if (preregistered) {
      return preregistered;
    }
    return await this.storage.getClientInformation(
      this.serverUrl,
      false,
      ctx?.issuer
    );
  }
  async saveClientInformation(clientInformation, options) {
    const registrationKind = options && "registrationKind" in options ? options.registrationKind : "dcr";
    const issuer = options && "issuer" in options ? options.issuer : void 0;
    await this.storage.saveClientInformation(
      this.serverUrl,
      clientInformation,
      {
        registrationKind,
        issuer
      }
    );
  }
  async saveScope(scope) {
    await this.storage.saveScope(this.serverUrl, scope);
    this.cachedScope = scope;
  }
  async savePreregisteredClientInformation(clientInformation) {
    await this.storage.savePreregisteredClientInformation(
      this.serverUrl,
      clientInformation
    );
  }
  async tokens(ctx) {
    return await this.storage.getTokens(this.serverUrl, ctx?.issuer);
  }
  async saveTokens(tokens, ctx) {
    await this.storage.saveTokens(this.serverUrl, tokens, {
      issuer: ctx?.issuer
    });
  }
  redirectToAuthorization(authorizationUrl) {
    this.performAuthorizationRedirect(
      applyAuthorizationParams(authorizationUrl, this.authorizationParams)
    );
  }
  /**
   * Redirect to an authorize URL belonging to a **different** authorization
   * server than the one this provider was configured for, so the per-server
   * custom parameters (#2018) MUST NOT be applied.
   *
   * The only caller is the enterprise-managed (EMA) transport provider, which
   * discards the resource AS URL and sends the user to the enterprise IdP
   * instead. Parameters a user configured for their MCP server's authorization
   * server — a `kc_idp_hint`, an `audience` — are meaningless and potentially
   * flow-breaking on the IdP's, and appending them there would leak per-server
   * config across an authorization-server boundary.
   */
  redirectToExternalAuthorization(authorizationUrl) {
    this.performAuthorizationRedirect(authorizationUrl);
  }
  /** Capture, announce, and navigate to a finished authorize URL. */
  performAuthorizationRedirect(authorizationUrl) {
    this.capturedAuthUrl = authorizationUrl;
    if (!this.suppressAuthorizationNavigation) {
      if (this.eventTarget) {
        this.eventTarget.dispatchEvent(
          new CustomEvent("oauthAuthorizationRequired", {
            detail: { url: authorizationUrl }
          })
        );
      }
      this.navigation.navigateToAuthorization(authorizationUrl);
    }
  }
  async saveCodeVerifier(codeVerifier) {
    await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
  }
  async codeVerifier() {
    const verifier = await this.storage.getCodeVerifier(this.serverUrl);
    if (!verifier) {
      throw new Error("No code verifier saved for session");
    }
    return verifier;
  }
  async clear() {
    await this.storage.clear(this.serverUrl);
  }
  async getServerMetadata() {
    return this.storage.getServerMetadata(this.serverUrl);
  }
  async saveServerMetadata(metadata) {
    await this.storage.saveServerMetadata(this.serverUrl, metadata);
  }
  /**
   * SEP-2352 discovery-state round-trip. The SDK persists RFC 9728/8414 discovery
   * here (alongside the code verifier) so that on the authorization-code callback
   * leg it can compare the resolved AS `issuer` against the one recorded at
   * redirect time and reject a mismatch (`AuthorizationServerMismatchError`).
   * Without these two methods the SDK only `console.warn`s and the binding check
   * is inactive.
   */
  async saveDiscoveryState(state) {
    await this.storage.saveDiscoveryState(this.serverUrl, state);
  }
  async discoveryState() {
    return this.storage.getDiscoveryState(this.serverUrl);
  }
  /**
   * SEP-2352 credential invalidation. The SDK calls this to drop credentials the
   * server has rejected; hosts also call `'discovery'` on repeated 401s so a
   * changed `authorization_servers` list is re-fetched.
   */
  async invalidateCredentials(scope) {
    switch (scope) {
      case "all":
        await this.storage.clear(this.serverUrl);
        return;
      case "client":
        await this.storage.clearClientInformation(this.serverUrl);
        return;
      case "tokens":
        await this.storage.clearTokens(this.serverUrl);
        return;
      case "verifier":
        await this.storage.clearCodeVerifier(this.serverUrl);
        return;
      case "discovery":
        await this.storage.clearDiscoveryState(this.serverUrl);
        return;
    }
  }
};

// ../../core/auth/types.ts
function authProtocolFromEnterpriseManaged(enterpriseManaged) {
  return enterpriseManaged ? "ema" : "standard";
}
var EMPTY_OAUTH_FLOW_STATE = {
  completedAt: null,
  isInitiatingAuth: false,
  oauthTokens: null,
  oauthStep: "authorization_code",
  oauthMetadata: null,
  resourceMetadata: null,
  resourceMetadataError: null,
  resource: null,
  authServerUrl: null,
  oauthClientInfo: null,
  authorizationUrl: null,
  authorizationCode: "",
  latestError: null,
  statusMessage: null,
  validationError: null
};

// ../../core/auth/mcpAuth.ts
import { auth as sdkAuth } from "@modelcontextprotocol/client";
async function mcpAuth(provider, options) {
  if (options.forceReauthorization && options.authorizationCode !== void 0) {
    throw new Error(
      "forceReauthorization cannot be combined with authorizationCode"
    );
  }
  return sdkAuth(provider, {
    serverUrl: options.serverUrl,
    authorizationCode: options.authorizationCode,
    iss: options.iss,
    scope: options.scope,
    resourceMetadataUrl: options.resourceMetadataUrl,
    fetchFn: options.fetchFn,
    skipIssuerMetadataValidation: options.skipIssuerMetadataValidation,
    forceReauthorization: options.forceReauthorization
  });
}

// ../../core/auth/ema/clientConfigError.ts
function emaClientNotConfiguredMessage(reason) {
  if (reason === "disabled") {
    return 'This server uses enterprise-managed authorization, but Enterprise IdP is turned off in Client Settings. Open Client Settings, enable "Enterprise IdP configuration", then try connecting again.';
  }
  return "This server uses enterprise-managed authorization, but the Inspector client IdP is not configured. Open Client Settings, enable Enterprise IdP, and set issuer, client ID, and client secret.";
}
var EmaClientNotConfiguredError = class extends Error {
  reason;
  constructor(reason) {
    super(emaClientNotConfiguredMessage(reason));
    this.name = "EmaClientNotConfiguredError";
    this.reason = reason;
  }
};

// ../../core/auth/ema/idpOidc.ts
import {
  discoverAuthorizationServerMetadata,
  exchangeAuthorization,
  startAuthorization
} from "@modelcontextprotocol/client";
import { OAuthMetadataSchema } from "@modelcontextprotocol/core";

// ../../core/auth/ema/constants.ts
var GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var IDP_OIDC_SCOPES = "openid offline_access";

// ../../core/auth/ema/jwt.ts
function decodeBase64Url(segment) {
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(
    base64.length + (4 - base64.length % 4) % 4,
    "="
  );
  const binary = atob(padded);
  const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}
function jwtExpiresAtMs(token) {
  const parts = token.split(".");
  if (parts.length < 2) return void 0;
  try {
    const payload = JSON.parse(decodeBase64Url(parts[1]));
    if (typeof payload.exp === "number" && Number.isFinite(payload.exp)) {
      return payload.exp * 1e3;
    }
  } catch {
    return void 0;
  }
  return void 0;
}
function isJwtExpired(token, skewMs = 6e4, nowMs = Date.now()) {
  const exp = jwtExpiresAtMs(token);
  if (exp === void 0) return false;
  return nowMs >= exp - skewMs;
}
function isJwtFormat(token) {
  const parts = token.split(".");
  return parts.length === 3 && parts[0].length > 0 && parts[1].length > 0 && parts[2] !== void 0;
}

// ../../core/auth/ema/storage.ts
function normalizeIdpIssuer(issuer) {
  return issuer.replace(/\/$/, "");
}
function idpOAuthStorageKey(issuer) {
  return `ema-idp:${normalizeIdpIssuer(issuer)}`;
}

// ../../core/auth/ema/tokenEndpoint.ts
import { selectClientAuthMethod } from "@modelcontextprotocol/client";
async function parseOAuthTokenErrorResponse(response, step) {
  let body;
  try {
    body = await response.json();
  } catch {
    return new Error(`${step}: token request failed (HTTP ${response.status})`);
  }
  if (typeof body === "object" && body !== null) {
    const record = body;
    const parts = [step];
    if (record.error) parts.push(`error=${record.error}`);
    if (record.error_description) parts.push(record.error_description);
    if (parts.length > 1) {
      return new Error(parts.join(": "));
    }
  }
  return new Error(`${step}: token request failed (HTTP ${response.status})`);
}
function applyClientAuth(metadata, clientInformation, headers, body) {
  const supported = metadata?.token_endpoint_auth_methods_supported ?? [];
  const method = selectClientAuthMethod(clientInformation, supported);
  if (method === "client_secret_basic" && clientInformation.client_secret) {
    const credentials = btoa(
      `${clientInformation.client_id}:${clientInformation.client_secret}`
    );
    headers.set("Authorization", `Basic ${credentials}`);
    return;
  }
  if (method === "client_secret_post" && clientInformation.client_secret) {
    body.set("client_id", clientInformation.client_id);
    body.set("client_secret", clientInformation.client_secret);
    return;
  }
  body.set("client_id", clientInformation.client_id);
}
async function postOAuthTokenRequest(tokenUrl, body, metadata, clientInformation, fetchFn) {
  const headers = new Headers({
    "Content-Type": "application/x-www-form-urlencoded",
    Accept: "application/json"
  });
  applyClientAuth(metadata, clientInformation, headers, body);
  return (fetchFn ?? fetch)(tokenUrl, {
    method: "POST",
    headers,
    body
  });
}

// ../../core/auth/ema/idpOidc.ts
function idpClientInformation(idp) {
  return {
    client_id: idp.clientId,
    client_secret: idp.clientSecret,
    token_endpoint_auth_method: "client_secret_post"
  };
}
async function resolveIdpMetadata(issuer, storage, fetchFn) {
  const storageKey = idpOAuthStorageKey(issuer);
  const cached = await storage.getServerMetadata(storageKey);
  if (cached?.token_endpoint) {
    return cached;
  }
  return discoverIdpMetadata(issuer, fetchFn);
}
async function discoverIdpMetadata(issuer, fetchFn) {
  const issuerUrl = parseHttpUrl(issuer, "EMA IdP issuer (Client Settings)");
  const metadata = await discoverAuthorizationServerMetadata(issuerUrl, {
    fetchFn
  });
  if (!metadata) {
    throw new Error(
      `Failed to discover OIDC metadata for IdP issuer ${issuer}`
    );
  }
  return OAuthMetadataSchema.parse(metadata);
}
async function startIdpOidcAuthorization(params) {
  const issuer = normalizeIdpIssuer(params.idp.issuer);
  const metadata = await resolveIdpMetadata(
    issuer,
    params.storage,
    params.fetchFn
  );
  const clientInformation = idpClientInformation(params.idp);
  const storageKey = idpOAuthStorageKey(issuer);
  const state = generateOAuthState();
  const issuerUrl = parseHttpUrl(issuer, "EMA IdP issuer (Client Settings)");
  const { authorizationUrl, codeVerifier } = await startAuthorization(
    issuerUrl,
    {
      metadata,
      clientInformation,
      redirectUrl: params.redirectUrl,
      scope: IDP_OIDC_SCOPES,
      state
    }
  );
  await params.storage.saveCodeVerifier(storageKey, codeVerifier);
  await params.storage.savePreregisteredClientInformation(
    storageKey,
    clientInformation
  );
  await params.storage.saveServerMetadata(storageKey, metadata);
  return { authorizationUrl };
}
async function completeIdpOidcAuthorization(params) {
  const issuer = normalizeIdpIssuer(params.idp.issuer);
  const storageKey = idpOAuthStorageKey(issuer);
  const metadata = await params.storage.getServerMetadata(storageKey);
  if (!metadata) {
    throw new Error("IdP OAuth metadata not found \u2014 restart EMA IdP login");
  }
  const clientInformation = await params.storage.getClientInformation(storageKey, true) ?? await params.storage.getClientInformation(storageKey);
  if (!clientInformation) {
    throw new Error("IdP client information not found \u2014 restart EMA IdP login");
  }
  const codeVerifier = await params.storage.getCodeVerifier(storageKey);
  if (!codeVerifier) {
    throw new Error("IdP PKCE verifier not found \u2014 restart EMA IdP login");
  }
  const issuerUrl = parseHttpUrl(issuer, "EMA IdP issuer (Client Settings)");
  const tokens = await exchangeAuthorization(issuerUrl, {
    metadata,
    clientInformation,
    authorizationCode: params.authorizationCode,
    iss: params.iss,
    codeVerifier,
    redirectUri: params.redirectUrl,
    fetchFn: params.fetchFn
  });
  const idToken = tokens.id_token;
  if (!idToken) {
    throw new Error("IdP token response did not include an ID Token");
  }
  await params.storage.clearCodeVerifier(storageKey);
  const idTokenExpiresAt = jwtExpiresAtMs(idToken);
  await params.storage.saveIdpSession(issuer, {
    idToken,
    refreshToken: tokens.refresh_token,
    idTokenExpiresAt
  });
  return {
    idToken,
    refreshToken: tokens.refresh_token,
    idTokenExpiresAt
  };
}
async function refreshIdpOidcSession(params) {
  const issuer = normalizeIdpIssuer(params.idp.issuer);
  const session = await params.storage.getIdpSession(issuer);
  if (!session?.refreshToken) {
    throw new Error("IdP refresh token not available");
  }
  const metadata = await resolveIdpMetadata(
    issuer,
    params.storage,
    params.fetchFn
  );
  if (!metadata.token_endpoint) {
    throw new Error("IdP metadata missing token_endpoint");
  }
  const body = new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: session.refreshToken
  });
  const response = await postOAuthTokenRequest(
    parseHttpUrl(
      metadata.token_endpoint,
      "IdP token_endpoint (from OIDC discovery)"
    ),
    body,
    metadata,
    idpClientInformation(params.idp),
    params.fetchFn
  );
  if (!response.ok) {
    throw await parseOAuthTokenErrorResponse(
      response,
      "EMA IdP refresh (OIDC refresh_token grant)"
    );
  }
  const json = await response.json();
  const idToken = json.id_token;
  if (!idToken) {
    throw new Error("IdP refresh did not return an ID Token");
  }
  await params.storage.saveIdpSession(issuer, {
    idToken,
    refreshToken: json.refresh_token ?? session.refreshToken,
    idTokenExpiresAt: jwtExpiresAtMs(idToken)
  });
  return idToken;
}
async function getValidIdToken(params) {
  const issuer = normalizeIdpIssuer(params.idp.issuer);
  const session = await params.storage.getIdpSession(issuer);
  if (!session?.idToken) {
    return void 0;
  }
  if (!isJwtExpired(session.idToken)) {
    return session.idToken;
  }
  if (!session.refreshToken) {
    return void 0;
  }
  try {
    return await refreshIdpOidcSession(params);
  } catch {
    return void 0;
  }
}

// ../../core/auth/ema/resourceContext.ts
import {
  discoverAuthorizationServerMetadata as discoverAuthorizationServerMetadata3,
  discoverOAuthProtectedResourceMetadata,
  selectResourceURL
} from "@modelcontextprotocol/client";

// ../../core/auth/discovery.ts
import { discoverAuthorizationServerMetadata as discoverAuthorizationServerMetadata2 } from "@modelcontextprotocol/client";
function serverUrlAsAuthorizationServer(serverUrl) {
  try {
    const url = new URL(serverUrl);
    url.search = "";
    url.hash = "";
    return url;
  } catch (err) {
    const detail = err instanceof Error ? err.message : String(err);
    throw new Error(`Invalid MCP server URL: "${serverUrl}" (${detail})`, {
      cause: err
    });
  }
}
function getAuthorizationServerUrl(serverUrl, resourceMetadata) {
  const first = resourceMetadata?.authorization_servers?.[0];
  if (first) {
    return parseHttpUrl(first, "protected resource authorization_servers[0]");
  }
  return serverUrlAsAuthorizationServer(serverUrl);
}
function getAuthorizationServerUrlCandidates(serverUrl, resourceMetadata) {
  const primary = getAuthorizationServerUrl(serverUrl, resourceMetadata);
  const namedByMetadata = Boolean(resourceMetadata?.authorization_servers?.[0]);
  if (namedByMetadata || primary.pathname === "/") return [primary];
  return [primary, new URL("/", primary)];
}
async function discoverAuthorizationServerMetadataFromCandidates(candidates, discover) {
  let firstError;
  let sawError = false;
  for (const candidate of candidates) {
    try {
      const metadata = await discover(candidate);
      if (metadata) return { authorizationServerUrl: candidate, metadata };
    } catch (err) {
      if (!sawError) {
        firstError = err;
        sawError = true;
      }
    }
  }
  if (sawError) throw firstError;
  return void 0;
}
async function discoverAuthorizationServerMetadataForServer(serverUrl, resourceMetadata, fetchFn) {
  const found = await discoverAuthorizationServerMetadataFromCandidates(
    getAuthorizationServerUrlCandidates(serverUrl, resourceMetadata),
    (candidate) => discoverAuthorizationServerMetadata2(candidate, { fetchFn })
  );
  return found?.metadata;
}

// ../../core/auth/ema/resourceContext.ts
function resolveEmaScopes(resourceMetadata, configuredScope) {
  const trimmed = configuredScope?.trim();
  if (trimmed) return trimmed;
  const supported = resourceMetadata?.scopes_supported;
  if (supported && supported.length > 0) {
    return supported.join(" ");
  }
  return void 0;
}
async function discoverEmaResourceContext(serverUrl, configuredScope, fetchFn) {
  const resourceMetadata = await discoverOAuthProtectedResourceMetadata(
    serverUrl,
    void 0,
    fetchFn
  );
  if (!resourceMetadata.resource) {
    throw new Error(
      "EMA requires protected resource metadata with a resource identifier"
    );
  }
  const resourceAsUrl = getAuthorizationServerUrl(serverUrl, resourceMetadata);
  const resourceUrl = await selectResourceURL(
    serverUrl,
    // `selectResourceURL` only reads `clientMetadata.scope` off the provider, so
    // we pass a minimal stub carrying just that. A partial literal has too little
    // overlap with the full `OAuthClientProvider` interface for a single cast, so
    // the double cast bridges the deliberately-incomplete shape here.
    {
      clientMetadata: {
        scope: configuredScope ?? "",
        redirect_uris: []
      }
    },
    resourceMetadata
  );
  const scope = resolveEmaScopes(resourceMetadata, configuredScope);
  return {
    resourceMetadata,
    resourceAsUrl,
    resourceUrl: resourceUrl ?? void 0,
    scope
  };
}
async function discoverResourceAsMetadata(resourceAsUrl, fetchFn) {
  const metadata = await discoverAuthorizationServerMetadata3(resourceAsUrl, {
    fetchFn
  });
  if (!metadata) {
    throw new Error(
      "Failed to discover resource authorization server metadata"
    );
  }
  return metadata;
}

// ../../core/auth/ema/wire.ts
import { discoverAndRequestJwtAuthGrant } from "@modelcontextprotocol/client";
import { OAuthTokensSchema as OAuthTokensSchema2 } from "@modelcontextprotocol/core";
async function exchangeIdJag(params) {
  const issuer = normalizeIdpIssuer(params.idp.issuer);
  const resource = params.resource.trim();
  if (!resource) {
    throw new Error("EMA leg 2 requires a resource identifier");
  }
  let result;
  try {
    result = await discoverAndRequestJwtAuthGrant({
      idpUrl: issuer,
      audience: params.audience,
      resource,
      idToken: params.idToken,
      clientId: params.idp.clientId,
      clientSecret: params.idp.clientSecret,
      scope: params.scope,
      fetchFn: params.fetchFn
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    if (/Failed to discover token endpoint/i.test(message)) {
      throw new Error("IdP metadata missing token_endpoint", { cause: err });
    }
    if (/Invalid token exchange response/i.test(message)) {
      throw new Error("IdP token exchange did not return an ID-JAG", {
        cause: err
      });
    }
    throw new Error(`EMA leg 2 (IdP token exchange for ID-JAG): ${message}`, {
      cause: err
    });
  }
  if (!result.jwtAuthGrant) {
    throw new Error("IdP token exchange did not return an ID-JAG");
  }
  return result.jwtAuthGrant;
}
async function redeemIdJagForAccessToken(params) {
  const asMetadata = await discoverResourceAsMetadata(
    params.resourceAsUrl,
    params.fetchFn
  );
  if (!asMetadata.token_endpoint) {
    throw new Error("Resource AS metadata missing token_endpoint");
  }
  const clientInformation = {
    client_id: params.resourceClientId,
    ...params.resourceClientSecret && {
      client_secret: params.resourceClientSecret
    },
    token_endpoint_auth_method: params.resourceClientSecret ? "client_secret_post" : "none"
  };
  const body = new URLSearchParams({
    grant_type: GRANT_TYPE_JWT_BEARER,
    assertion: params.idJag
  });
  if (params.scope) {
    body.set("scope", params.scope);
  }
  if (params.resource) {
    body.set("resource", params.resource);
  }
  const response = await postOAuthTokenRequest(
    parseHttpUrl(
      asMetadata.token_endpoint,
      "resource authorization server token_endpoint (from AS discovery)"
    ),
    body,
    asMetadata,
    clientInformation,
    params.fetchFn
  );
  if (!response.ok) {
    throw await parseOAuthTokenErrorResponse(
      response,
      "EMA leg 3 (resource AS JWT bearer grant)"
    );
  }
  return OAuthTokensSchema2.parse(await response.json());
}

// ../../core/auth/ema/emaFlow.ts
function wrapEmaMintError(err) {
  const message = err instanceof Error ? err.message : String(err);
  return new Error(`EMA legs 2\u20133 (resource token mint): ${message}`, {
    cause: err
  });
}
async function mintEmaResourceTokens(config, resourceContext) {
  const ctx = resourceContext ?? await discoverEmaResourceContext(
    config.serverUrl,
    config.scope,
    config.fetchFn
  );
  if (!config.resourceClientId) {
    throw new Error(
      "EMA requires resource authorization server clientId (per-server oauth.clientId)"
    );
  }
  if (!config.resourceClientSecret?.trim()) {
    throw new Error(
      "EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
    );
  }
  const idToken = await getValidIdToken({
    idp: config.idp,
    storage: config.storage,
    fetchFn: config.fetchFn
  });
  if (!idToken) {
    throw new Error("Valid IdP ID Token required for EMA token mint");
  }
  const audience = ctx.resourceAsUrl.href.replace(/\/$/, "");
  const resource = ctx.resourceUrl?.href ?? ctx.resourceMetadata.resource;
  const idJag = await exchangeIdJag({
    idp: config.idp,
    idToken,
    audience,
    resource,
    scope: ctx.scope,
    fetchFn: config.fetchFn
  });
  const tokens = await redeemIdJagForAccessToken({
    resourceAsUrl: ctx.resourceAsUrl,
    idJag,
    resourceClientId: config.resourceClientId,
    resourceClientSecret: config.resourceClientSecret,
    resource: ctx.resourceUrl?.href,
    scope: ctx.scope,
    fetchFn: config.fetchFn
  });
  return { tokens, requestedScope: ctx.scope };
}
async function saveMintedTokens(config, { tokens, requestedScope }) {
  await config.storage.saveTokens(config.serverUrl, tokens, {
    enterpriseManaged: true
  });
  const scopeToPersist = resolvePersistedScopeAfterGrant(
    tokens.scope,
    requestedScope
  );
  if (scopeToPersist) {
    await config.storage.saveScope(config.serverUrl, scopeToPersist);
  }
}
async function trySilentEmaAuth(config) {
  const idToken = await getValidIdToken({
    idp: config.idp,
    storage: config.storage,
    fetchFn: config.fetchFn
  });
  if (!idToken) {
    return { status: "no_idp_session" };
  }
  try {
    await saveMintedTokens(config, await mintEmaResourceTokens(config));
    return { status: "success" };
  } catch (err) {
    return { status: "mint_failed", error: wrapEmaMintError(err) };
  }
}
async function startEmaIdpAuthorization(config) {
  const { authorizationUrl } = await startIdpOidcAuthorization({
    idp: config.idp,
    redirectUrl: config.redirectUrl,
    storage: config.storage,
    fetchFn: config.fetchFn
  });
  return authorizationUrl;
}
async function completeEmaIdpAuthorizationAndMint(config, authorizationCode, iss) {
  try {
    await completeIdpOidcAuthorization({
      idp: config.idp,
      authorizationCode,
      iss,
      redirectUrl: config.redirectUrl,
      storage: config.storage,
      fetchFn: config.fetchFn
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    throw new Error(`EMA leg 1 (IdP authorization code exchange): ${message}`, {
      cause: err
    });
  }
  let minted;
  try {
    minted = await mintEmaResourceTokens(config);
  } catch (err) {
    throw wrapEmaMintError(err);
  }
  await saveMintedTokens(config, minted);
  return minted;
}
async function refreshEmaResourceTokens(config) {
  const idToken = await getValidIdToken({
    idp: config.idp,
    storage: config.storage,
    fetchFn: config.fetchFn
  });
  if (!idToken) {
    return void 0;
  }
  const minted = await mintEmaResourceTokens(config);
  await saveMintedTokens(config, minted);
  return minted.tokens;
}

// ../../core/auth/ema/idpSession.ts
async function getEmaIdpLoginState(storage, issuer) {
  const normalized = normalizeIdpIssuer(issuer);
  if (!normalized) return "none";
  const session = await storage.getIdpSession(normalized);
  if (!session?.idToken) return "none";
  if (!isJwtExpired(session.idToken)) return "logged_in";
  if (session.refreshToken) return "logged_in";
  return "expired";
}

// ../../core/auth/connection-state.ts
function isAccessTokenUsable(tokens) {
  if (!tokens?.access_token) return false;
  return !isJwtExpired(tokens.access_token);
}
function isAccessTokenProvablyUnexpired(tokens) {
  if (!tokens?.access_token) return false;
  if (!isJwtFormat(tokens.access_token)) return false;
  if (jwtExpiresAtMs(tokens.access_token) === void 0) return false;
  return !isJwtExpired(tokens.access_token);
}
function resolveClient(preregistered, dynamic, dynamicRegistrationKind) {
  if (preregistered?.client_id) {
    return {
      registrationKind: "static",
      clientId: preregistered.client_id,
      hasClientSecret: preregistered.client_secret !== void 0 && preregistered.client_secret !== ""
    };
  }
  if (!dynamic?.client_id) return void 0;
  return {
    ...dynamicRegistrationKind && {
      registrationKind: dynamicRegistrationKind
    },
    clientId: dynamic.client_id,
    hasClientSecret: dynamic.client_secret !== void 0 && dynamic.client_secret !== ""
  };
}
function resolveGrantedScope(tokens, storageScope) {
  const tokenScope = typeof tokens?.scope === "string" ? tokens.scope.trim() : void 0;
  if (tokenScope) return tokenScope;
  const stored = typeof storageScope === "string" ? storageScope.trim() : void 0;
  return stored || void 0;
}
async function buildOAuthConnectionState(params) {
  const { serverUrl, storage, flowState } = params;
  const protocol = params.protocol;
  const storedTokens = await storage.getTokens(serverUrl);
  const flowTokens = flowState?.oauthTokens ?? void 0;
  const tokens = flowTokens && isAccessTokenUsable(flowTokens) ? flowTokens : storedTokens && isAccessTokenUsable(storedTokens) ? storedTokens : flowTokens ?? storedTokens;
  const preregistered = await storage.getClientInformation(serverUrl, true);
  const dynamic = await storage.getClientInformation(serverUrl);
  const storageScope = await storage.getScope(serverUrl);
  const serverMetadata = await storage.getServerMetadata(serverUrl);
  const authorized = isAccessTokenUsable(tokens);
  const grantedScope = resolveGrantedScope(tokens, storageScope);
  const registrationKind = await storage.getClientRegistrationKind(serverUrl);
  const dynamicRegistrationKind = registrationKind === "dcr" || registrationKind === "cimd" ? registrationKind : void 0;
  const client = resolveClient(
    preregistered,
    dynamic,
    preregistered?.client_id ? void 0 : dynamicRegistrationKind
  );
  const state = {
    authorized,
    protocol,
    serverUrl,
    ...params.configuredScope?.trim() && {
      configuredScope: params.configuredScope.trim()
    },
    ...grantedScope && { grantedScope },
    ...tokens && { tokens },
    ...client && { client },
    ...serverMetadata && { authorizationServerMetadata: serverMetadata }
  };
  if (protocol === "ema" && params.enterpriseManagedAuth?.idp) {
    const idp = params.enterpriseManagedAuth.idp;
    const issuer = normalizeIdpIssuer(idp.issuer);
    const idpSession = await getEmaIdpLoginState(storage, issuer);
    const idpMetadata = await storage.getServerMetadata(
      idpOAuthStorageKey(issuer)
    );
    state.ema = {
      idpIssuer: issuer,
      idpClientId: idp.clientId,
      idpSession,
      ...idpMetadata && { idpMetadata }
    };
    state.enterpriseManaged = true;
  }
  return state;
}
function isServerOAuthConfigured(config) {
  return config.enterpriseManaged === true || !!config.clientId?.trim() || !!config.clientSecret?.trim() || !!config.scope?.trim() || !!config.clientMetadataUrl?.trim();
}
async function hasPersistedOAuthServerState(storage, serverUrl) {
  const [tokens, preregistered, dynamic] = await Promise.all([
    storage.getTokens(serverUrl),
    storage.getClientInformation(serverUrl, true),
    storage.getClientInformation(serverUrl)
  ]);
  return !!(tokens?.access_token || preregistered?.client_id || dynamic?.client_id);
}
function protocolFromOAuthConfig(config) {
  return authProtocolFromEnterpriseManaged(config.enterpriseManaged);
}

// ../../core/auth/cimd.ts
import { discoverOAuthProtectedResourceMetadata as discoverOAuthProtectedResourceMetadata2 } from "@modelcontextprotocol/client";
async function ensureCimdClientRegistration(params) {
  const clientMetadataUrl = params.provider.clientMetadataUrl?.trim();
  if (!clientMetadataUrl) return;
  const existing = await params.provider.clientInformation();
  if (existing?.client_id) return;
  let resourceMetadata;
  try {
    resourceMetadata = await discoverOAuthProtectedResourceMetadata2(
      params.serverUrl,
      { resourceMetadataUrl: params.resourceMetadataUrl },
      // The same fetch the AS-metadata leg below uses. On web that is
      // `createRemoteFetch`, which proxies through the backend to sidestep
      // CORS — on the global `fetch` this leg would fail in the browser, be
      // swallowed by the catch, and leave CIMD probing the wrong
      // authorization server (Copilot).
      params.fetchFn
    );
  } catch {
    resourceMetadata = void 0;
  }
  const metadata = await discoverAuthorizationServerMetadataForServer(
    params.serverUrl,
    resourceMetadata,
    params.fetchFn
  );
  if (!metadata?.client_id_metadata_document_supported) return;
  const clientInformation = {
    client_id: clientMetadataUrl
  };
  await params.provider.saveClientInformation(clientInformation, {
    registrationKind: "cimd"
  });
}

// ../../core/auth/ema/transportProvider.ts
function isAccessTokenUsable2(tokens, skewMs = 6e4) {
  if (!tokens.access_token) return false;
  return !isJwtExpired(tokens.access_token, skewMs);
}
var EmaTransportOAuthProvider = class {
  inner;
  emaConfig;
  constructor(inner, emaConfig) {
    this.inner = inner;
    this.emaConfig = emaConfig;
  }
  get redirectUrl() {
    return this.inner.redirectUrl;
  }
  get clientMetadataUrl() {
    return this.inner.clientMetadataUrl;
  }
  get clientMetadata() {
    return this.inner.clientMetadata;
  }
  state() {
    return this.inner.state();
  }
  clientInformation() {
    return this.inner.clientInformation();
  }
  saveClientInformation(clientInformation) {
    return this.inner.saveClientInformation(clientInformation);
  }
  async tokens() {
    const stored = await this.inner.tokens();
    if (stored && isAccessTokenUsable2(stored)) {
      return stored;
    }
    const refreshed = await refreshEmaResourceTokens(this.emaConfig);
    if (refreshed) {
      return refreshed;
    }
    return void 0;
  }
  saveTokens(tokens) {
    return this.emaConfig.storage.saveTokens(this.emaConfig.serverUrl, tokens, {
      enterpriseManaged: true
    });
  }
  async redirectToAuthorization(_authorizationUrl) {
    const idpAuthorizationUrl = await startEmaIdpAuthorization(this.emaConfig);
    this.inner.clearCapturedAuthUrl();
    this.inner.redirectToExternalAuthorization(idpAuthorizationUrl);
  }
  saveCodeVerifier(codeVerifier) {
    return this.inner.saveCodeVerifier(codeVerifier);
  }
  codeVerifier() {
    return this.inner.codeVerifier();
  }
};

// ../../core/auth/oauthUx.ts
function isStandardOAuthStepUp(challenge, options) {
  return challenge.reason === "insufficient_scope" && !options?.enterpriseManaged;
}
function stepUpConfirmMessage(challenge, options) {
  const toolName = challenge.context?.toolName?.trim();
  if (toolName) {
    return options?.enterpriseManaged ? `Tool "${toolName}" needs additional permissions from your organization before it can continue.` : `Tool "${toolName}" needs additional OAuth scopes before it can continue.`;
  }
  const additional = challenge.requiredScopes?.filter(Boolean);
  if (additional?.length) {
    const label = additional.length === 1 ? "scope" : "scopes";
    return options?.enterpriseManaged ? `This operation needs additional organization ${label}: ${additional.join(", ")}.` : `This operation needs additional ${label}: ${additional.join(", ")}.`;
  }
  return options?.enterpriseManaged ? "This operation needs additional permissions from your organization before it can continue." : "This operation needs additional OAuth scopes before it can continue.";
}
function stepUpInsufficientScopeMessage(challenge) {
  const toolName = challenge.context?.toolName?.trim();
  if (toolName) {
    return `Authorization completed, but required permissions for tool "${toolName}" were not granted. Grant the requested scopes on the authorization server, then retry.`;
  }
  const missing = challenge.authorizationScopes?.filter(Boolean) ?? challenge.requiredScopes?.filter(Boolean);
  if (missing?.length) {
    return `Authorization completed, but required scopes were not granted (${missing.join(", ")}). Grant the requested permissions on the authorization server, then retry your action.`;
  }
  return "Authorization completed, but the required permissions were not granted. Grant the requested scopes on the authorization server, then retry your action.";
}
function lostAuthorizationStateTitle() {
  return "Authorization state was lost";
}
function lostAuthorizationStateMessage(options) {
  const target = options?.serverName ? `"${options.serverName}"` : "this server";
  return `The stored authorization state for ${target} was lost before the authorization server sent you back, so the sign-in could not be completed. This usually means a new browser session, a different tab, or authorization state that was cleared while the flow was in progress. Authorize again to reconnect.`;
}
function issuerMismatchTitle() {
  return "Authorization server mismatch";
}
var MAX_DISPLAYED_ISSUER_LENGTH = 120;
function truncateIssuerForDisplay(issuer) {
  return issuer.length > MAX_DISPLAYED_ISSUER_LENGTH ? `${issuer.slice(0, MAX_DISPLAYED_ISSUER_LENGTH)}\u2026` : issuer;
}
function issuerMismatchMessage(options) {
  const target = options.serverName ? `"${options.serverName}"` : "this server";
  return `Authorization for ${target} was stopped: the flow started at ${truncateIssuerForDisplay(options.recordedIssuer)} but the callback resolved ${truncateIssuerForDisplay(options.currentIssuer)}. The authorization code was not exchanged. Verify the server's authorization configuration before trying again.`;
}
function issuerBindingFailureCopy(failure, options) {
  if (failure.kind === "lost_authorization_state") {
    return {
      title: lostAuthorizationStateTitle(),
      message: lostAuthorizationStateMessage(options)
    };
  }
  return {
    title: issuerMismatchTitle(),
    message: issuerMismatchMessage({
      recordedIssuer: failure.recordedIssuer,
      currentIssuer: failure.currentIssuer,
      serverName: options?.serverName
    })
  };
}

// ../../core/mcp/oauthManager.ts
var OAuthManager = class {
  params;
  oauthConfig;
  oauthFlowState = null;
  /** SEP-2350 union scope pending until interactive step-up completes. */
  pendingAuthorizationScope;
  /**
   * The RFC 9728 metadata URL from the last 401/403 the transport observed.
   * See {@link noteObservedAuthChallenge}.
   */
  observedResourceMetadataUrl;
  authChallengeMutex = Promise.resolve();
  constructor(params) {
    this.params = params;
    this.oauthConfig = { ...params.initialConfig };
    this.pendingAuthorizationScope = void 0;
  }
  setOAuthConfig(config) {
    this.oauthConfig = {
      ...this.oauthConfig,
      ...config
    };
  }
  getServerUrl() {
    return this.params.getServerUrl();
  }
  /**
   * The per-server authorization/token endpoint overrides, read live so a
   * `setOAuthConfig` between requests takes effect without rebuilding the
   * client's fetch. `InspectorClient` reads this from inside its fetch wrapper.
   * (#1906)
   *
   * Returns nothing under enterprise-managed authorization, for the same reason
   * `redirectToExternalAuthorization` skips the custom authorization parameters
   * (#2018): the EMA leg authorizes against the enterprise IdP, a *different*
   * authorization server, and its OIDC discovery runs through this same fetch
   * (`emaFlow` → `idpOidc`). Rewriting that document would point the IdP login —
   * or the IdP code/refresh-token exchange — at the resource server's
   * authorization server, which is both wrong and a way to leak an IdP
   * credential across an authorization-server boundary.
   */
  getEndpointOverrides() {
    if (this.isEnterpriseManaged()) return void 0;
    const { authorizationUrl, tokenUrl } = this.oauthConfig;
    if (!authorizationUrl && !tokenUrl) return void 0;
    return {
      ...authorizationUrl && { authorizationUrl },
      ...tokenUrl && { tokenUrl }
    };
  }
  /**
   * Return the configured OAuth storage, throwing a clear error if it is
   * absent. Used on the EMA re-mint/persist paths, which are only reached for
   * a fully-configured OAuth-capable server (storage present).
   */
  requireStorage() {
    const storage = this.oauthConfig.storage;
    if (!storage) {
      throw new Error("OAuth storage is required for this operation.");
    }
    return storage;
  }
  async createOAuthProvider() {
    if (!this.oauthConfig.storage || !this.oauthConfig.redirectUrlProvider || !this.oauthConfig.navigation) {
      throw new Error(
        "OAuth environment components (storage, navigation, redirectUrlProvider) are required."
      );
    }
    const serverUrl = this.getServerUrl();
    const provider = new BaseOAuthClientProvider(serverUrl, {
      storage: this.oauthConfig.storage,
      redirectUrlProvider: this.oauthConfig.redirectUrlProvider,
      navigation: this.oauthConfig.navigation,
      clientMetadataUrl: this.oauthConfig.clientMetadataUrl,
      // #2018: per-server custom authorization-request parameters. Carried on
      // the provider (not in OAuth storage like `scope`) — they are pure config
      // read from mcp.json, and nothing in the flow needs to persist or union
      // them.
      authorizationParams: this.oauthConfig.authorizationParams,
      // #2068: per-server opt-out of the `refresh_token` grant. Like
      // `authorizationParams` this is pure config off mcp.json, so it lives on
      // the provider rather than in OAuth storage.
      requestRefreshToken: this.oauthConfig.requestRefreshToken,
      // #2068: lets the provider tell an `offline_access` the user asked for
      // from one inherited from a previous grant's persisted scope.
      configuredScope: this.oauthConfig.scope
    });
    provider.setEventTarget(this.params.getEventTarget());
    await provider.prepareForAuth();
    const storedScope = provider.scope;
    if (storedScope === void 0 && this.oauthConfig.scope) {
      await provider.saveScope(this.oauthConfig.scope);
    }
    if (this.oauthConfig.clientId) {
      const clientInfo = {
        client_id: this.oauthConfig.clientId,
        ...this.oauthConfig.clientSecret && {
          client_secret: this.oauthConfig.clientSecret
        }
      };
      await provider.savePreregisteredClientInformation(clientInfo);
    }
    return provider;
  }
  /** Whether this connection uses enterprise-managed authorization (EMA). */
  isEnterpriseManaged() {
    return this.oauthConfig.enterpriseManaged === true;
  }
  async getEmaFlowConfig() {
    if (!this.oauthConfig.storage || !this.oauthConfig.redirectUrlProvider) {
      throw new Error(
        "OAuth environment components (storage, redirectUrlProvider) are required."
      );
    }
    const idp = this.params.enterpriseManagedAuth?.idp;
    if (!idp) {
      const install = this.params.installEnterpriseManagedAuth;
      throw new EmaClientNotConfiguredError(
        install?.enabled === false && install.idp ? "disabled" : "not_configured"
      );
    }
    const storedScope = await this.oauthConfig.storage.getScope(
      this.getServerUrl()
    );
    return {
      serverUrl: this.getServerUrl(),
      idp,
      resourceClientId: this.oauthConfig.clientId,
      resourceClientSecret: this.oauthConfig.clientSecret,
      scope: computeScopeUnion(this.oauthConfig.scope, storedScope) || this.oauthConfig.scope,
      redirectUrl: this.oauthConfig.redirectUrlProvider.getRedirectUrl(),
      storage: this.oauthConfig.storage,
      fetchFn: this.params.effectiveAuthFetch
    };
  }
  /** Attempt silent EMA (cached IdP session + legs 2–3). */
  async trySilentEnterpriseManagedAuth() {
    if (!this.isEnterpriseManaged()) return false;
    const result = await trySilentEmaAuth(await this.getEmaFlowConfig());
    if (result.status === "success") return true;
    if (result.status === "mint_failed") throw result.error;
    return false;
  }
  async authenticateEnterpriseManaged() {
    const config = await this.getEmaFlowConfig();
    const silent = await trySilentEmaAuth(config);
    if (silent.status === "success") {
      return void 0;
    }
    if (silent.status === "mint_failed") {
      throw silent.error;
    }
    const authorizationUrl = await startEmaIdpAuthorization(config);
    const stateParam = authorizationUrl.searchParams.get("state");
    if (stateParam && this.params.onBeforeOAuthRedirect) {
      const parsedState = parseOAuthState(stateParam);
      if (parsedState?.authId) {
        await this.params.onBeforeOAuthRedirect(parsedState.authId);
      }
    }
    this.requireNavigation().navigateToAuthorization(authorizationUrl);
    await this.recordAuthorizationCodeFlowState(authorizationUrl);
    return authorizationUrl;
  }
  /**
   * Record a challenge the transport saw but did not raise as one.
   *
   * The legacy first-authorization path connects with no `authProvider` and no
   * challenge interception, so the client calls {@link authenticate} off a
   * headerless SDK `UnauthorizedError` — this is what carries the advertised
   * RFC 9728 metadata URL across that gap (#2071). Latest-wins: every
   * challenge comes from the same server, so the most recent one is what that
   * server currently says, including when it says nothing.
   */
  noteObservedAuthChallenge(challenge) {
    this.observedResourceMetadataUrl = challengeResourceMetadataUrl(challenge);
  }
  async authenticate() {
    if (this.isEnterpriseManaged()) {
      return this.authenticateEnterpriseManaged();
    }
    const provider = await this.createOAuthProvider();
    const serverUrl = this.getServerUrl();
    provider.clearCapturedAuthUrl();
    const resourceMetadataUrl = this.observedResourceMetadataUrl;
    await ensureCimdClientRegistration({
      serverUrl,
      provider,
      fetchFn: this.params.effectiveAuthFetch,
      resourceMetadataUrl
    });
    const requestedScope = provider.scope;
    const result = await mcpAuth(provider, {
      serverUrl,
      scope: requestedScope,
      fetchFn: this.params.effectiveAuthFetch,
      resourceMetadataUrl
    });
    if (result === "AUTHORIZED") {
      throw new Error(
        "Unexpected: auth() returned AUTHORIZED without authorization code"
      );
    }
    const capturedUrl = provider.getCapturedAuthUrl();
    if (!capturedUrl) {
      throw new Error("Failed to capture authorization URL");
    }
    this.recordAuthorizationRequestScope(capturedUrl, requestedScope);
    const stateParam = capturedUrl.searchParams.get("state");
    if (stateParam && this.params.onBeforeOAuthRedirect) {
      const parsedState = parseOAuthState(stateParam);
      if (parsedState?.authId) {
        await this.params.onBeforeOAuthRedirect(parsedState.authId);
      }
    }
    const clientInfo = await provider.clientInformation();
    this.oauthFlowState = {
      ...EMPTY_OAUTH_FLOW_STATE,
      oauthStep: "authorization_code",
      authorizationUrl: capturedUrl,
      oauthClientInfo: clientInfo ?? null
    };
    return capturedUrl;
  }
  /**
   * Remember what an interactive authorization asked for, so the callback can
   * apply RFC 6749 §5.1 — a token response that omits `scope` granted exactly
   * the requested scope (#2117). Without it, an ordinary grant whose AS echoes
   * no scope leaves the previous stored scope standing as though it were what
   * the current token carries.
   *
   * The authorize URL is the authority, not the scope handed to `auth()`: the
   * SDK augments the request with `offline_access` when the AS advertises it
   * and the client asks for a refresh token, so the input can be a strict
   * subset of what was actually requested. The fallback covers an authorize
   * URL that carries no `scope` parameter at all.
   *
   * This composes with the #2068 refresh-token opt-out rather than defeating
   * it. The SDK's augmentation is gated on `clientMetadata.grant_types`
   * including `refresh_token`, which the provider drops when the opt-out is
   * on — so with the grant declined the URL carries the *filtered* scope, and
   * that is what gets persisted. #2068 recorded the same value here behind a
   * `requestRefreshToken === false` gate; reading the URL makes the gate
   * unnecessary, so one wire case now has one persistence behavior instead of
   * two selected by a checkbox (#2117).
   */
  recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
    this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
  }
  async completeOAuthFlow(authorizationCode, iss) {
    try {
      if (this.isEnterpriseManaged()) {
        const emaConfig = await this.getEmaFlowConfig();
        const scopeForMint = this.pendingAuthorizationScope ?? emaConfig.scope;
        const config = scopeForMint ? { ...emaConfig, scope: scopeForMint } : emaConfig;
        const { tokens: tokens2 } = await completeEmaIdpAuthorizationAndMint(
          config,
          authorizationCode,
          iss
        );
        this.pendingAuthorizationScope = void 0;
        const completedAt2 = Date.now();
        this.oauthFlowState = {
          ...EMPTY_OAUTH_FLOW_STATE,
          oauthStep: "complete",
          oauthTokens: tokens2,
          completedAt: completedAt2
        };
        this.params.dispatchOAuthComplete({ tokens: tokens2 });
        return;
      }
      const provider = await this.createOAuthProvider();
      const serverUrl = this.getServerUrl();
      const result = await mcpAuth(provider, {
        serverUrl,
        authorizationCode,
        iss,
        fetchFn: this.params.effectiveAuthFetch
      });
      if (result !== "AUTHORIZED") {
        throw new Error(
          `Expected AUTHORIZED after providing authorization code, got: ${result}`
        );
      }
      const tokens = await provider.tokens();
      if (!tokens) {
        throw new Error("Failed to retrieve tokens after authorization");
      }
      const requestedScope = this.pendingAuthorizationScope;
      const scopeToPersist = resolvePersistedScopeAfterGrant(
        tokens.scope,
        requestedScope
      );
      if (scopeToPersist) {
        await provider.saveScope(scopeToPersist);
      }
      this.pendingAuthorizationScope = void 0;
      const clientInfo = await provider.clientInformation();
      const completedAt = Date.now();
      this.oauthFlowState = this.oauthFlowState ? {
        ...this.oauthFlowState,
        oauthStep: "complete",
        oauthTokens: tokens,
        oauthClientInfo: clientInfo ?? null,
        completedAt
      } : {
        ...EMPTY_OAUTH_FLOW_STATE,
        oauthStep: "complete",
        oauthTokens: tokens,
        oauthClientInfo: clientInfo ?? null,
        completedAt
      };
      this.params.dispatchOAuthComplete({ tokens });
    } catch (error) {
      this.pendingAuthorizationScope = void 0;
      this.params.dispatchOAuthError({
        error: error instanceof Error ? error : new Error(String(error))
      });
      throw error;
    }
  }
  async getOAuthTokens() {
    if (this.oauthFlowState?.oauthTokens) {
      return this.oauthFlowState.oauthTokens;
    }
    const provider = await this.createOAuthProvider();
    try {
      return await provider.tokens();
    } catch {
      return void 0;
    }
  }
  async clearOAuthTokens() {
    if (!this.oauthConfig?.storage) {
      return;
    }
    const serverUrl = this.getServerUrl();
    await this.oauthConfig.storage.clear(serverUrl);
    this.oauthFlowState = null;
    this.pendingAuthorizationScope = void 0;
  }
  async isOAuthAuthorized() {
    const tokens = await this.getOAuthTokens();
    return tokens !== void 0;
  }
  getOAuthFlowState() {
    return this.oauthFlowState ? { ...this.oauthFlowState } : void 0;
  }
  getOAuthFlowStep() {
    return this.oauthFlowState?.oauthStep;
  }
  /**
   * Persisted OAuth authorization snapshot for this server (storage + config).
   * Returns undefined when OAuth is not configured for the server.
   */
  async getOAuthState() {
    const storage = this.oauthConfig.storage;
    if (!storage) {
      return void 0;
    }
    const serverUrl = this.getServerUrl();
    const hasConfiguredOptions = isServerOAuthConfigured(this.oauthConfig);
    if (!hasConfiguredOptions && !await hasPersistedOAuthServerState(storage, serverUrl)) {
      return void 0;
    }
    return buildOAuthConnectionState({
      serverUrl: this.getServerUrl(),
      protocol: protocolFromOAuthConfig(this.oauthConfig),
      configuredScope: this.oauthConfig.scope,
      enterpriseManagedAuth: this.params.enterpriseManagedAuth,
      storage,
      flowState: this.oauthFlowState ?? void 0
    });
  }
  /**
   * Re-run EMA legs 2–3 when resource tokens expire but IdP session remains valid.
   */
  async refreshEnterpriseManagedTokens() {
    if (!this.isEnterpriseManaged()) return false;
    const tokens = await refreshEmaResourceTokens(
      await this.getEmaFlowConfig()
    );
    return tokens !== void 0;
  }
  /**
   * Re-read persisted OAuth state and determine whether `challenge` is already
   * satisfied without an authorization-server round-trip.
   *
   * Returns `true` for `insufficient_scope` when stored + token scope cover the
   * SEP-2350 union. For `token_expired`, returns `true` only when storage holds
   * a *provably* unexpired token — a JWT whose `exp` is still in the future. An
   * opaque token carries no local expiry evidence, so the resource server's
   * verdict stands and re-authorization proceeds. `invalid_token` and
   * `unauthorized` always return `false` — the resource server explicitly
   * rejected the credential.
   */
  async checkAuthChallengeSatisfied(challenge) {
    const storage = this.oauthConfig.storage;
    if (!storage) {
      return false;
    }
    const serverUrl = this.getServerUrl();
    const tokens = await storage.getTokens(serverUrl);
    if (!tokens?.access_token) {
      return false;
    }
    if (challenge.reason !== "insufficient_scope") {
      return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
    }
    const enriched = await this.enrichChallengeWithAuthorizationScopes(
      challenge,
      tokens.scope
    );
    const authorizationScopesJoined = enriched.authorizationScopes?.join(" ");
    const scopeForAuth = authorizationScopesJoined ?? enriched.requiredScopes?.join(" ");
    if (!scopeForAuth?.trim()) {
      return false;
    }
    const effectiveScope = resolveEffectiveGrantedScope(
      await storage.getScope(serverUrl),
      tokens.scope
    );
    return !isStrictScopeSuperset(scopeForAuth, effectiveScope);
  }
  /**
   * Satisfy a mid-session auth challenge when possible (silent refresh/re-mint or
   * interactive redirect).
   *
   * Only `insufficient_scope` short-circuits on {@link checkAuthChallengeSatisfied}
   * here — `token_expired` / `unauthorized` still attempt silent refresh even when
   * storage holds a locally-valid token (the resource server may have invalidated it).
   * Callers use {@link checkAuthChallengeSatisfied} directly before visible OAuth.
   *
   * Recovery is serialized per server so parallel challenges cannot race on
   * `pendingAuthorizationScope` or OAuth provider state.
   */
  async handleAuthChallenge(challenge, options) {
    if (challenge.reason === "insufficient_scope" && await this.checkAuthChallengeSatisfied(challenge)) {
      return { kind: "satisfied" };
    }
    const prior = this.authChallengeMutex;
    let release;
    const gate = new Promise((resolve7) => {
      release = resolve7;
    });
    this.authChallengeMutex = gate;
    await prior;
    try {
      if (challenge.reason === "insufficient_scope" && await this.checkAuthChallengeSatisfied(challenge)) {
        return { kind: "satisfied" };
      }
      return await this.runHandleAuthChallenge(challenge, options);
    } finally {
      release();
    }
  }
  async runHandleAuthChallenge(challenge, options) {
    if (this.isEnterpriseManaged()) {
      return this.handleEnterpriseManagedAuthChallenge(challenge, options);
    }
    return this.handleStandardAuthChallenge(challenge);
  }
  async enrichChallengeWithAuthorizationScopes(challenge, grantedTokenScope) {
    if (challenge.reason !== "insufficient_scope") {
      return challenge;
    }
    const storage = this.oauthConfig.storage;
    const serverUrl = this.getServerUrl();
    if (grantedTokenScope === void 0 && storage) {
      const tokens = await storage.getTokens(serverUrl);
      grantedTokenScope = tokens?.scope;
    }
    const previousScope = computeScopeUnion(
      storage ? await storage.getScope(serverUrl) : void 0,
      grantedTokenScope
    );
    const requiredFromChallenge = challenge.requiredScopes?.filter(Boolean) ?? [];
    const grantedSet = new Set(parseScopeString(previousScope));
    const missingRequired = requiredFromChallenge.filter(
      (scope) => !grantedSet.has(scope)
    );
    const requiredScopes = missingRequired.length > 0 ? missingRequired : requiredFromChallenge;
    const authorizationScopes = unionAuthorizationScopes(
      previousScope,
      requiredFromChallenge
    );
    return {
      ...challenge,
      requiredScopes,
      authorizationScopes
    };
  }
  resolveEmaScopeForChallenge(challenge) {
    if (challenge.reason === "insufficient_scope") {
      const fromChallenge = challenge.requiredScopes?.join(" ").trim();
      if (fromChallenge) {
        return fromChallenge;
      }
    }
    return this.oauthConfig.scope?.trim() || void 0;
  }
  async emaFlowConfigForChallenge(challenge) {
    const base = await this.getEmaFlowConfig();
    const enriched = challenge.authorizationScopes;
    if (enriched && enriched.length > 0) {
      return { ...base, scope: enriched.join(" ") };
    }
    const scope = this.resolveEmaScopeForChallenge(challenge);
    return scope ? { ...base, scope } : base;
  }
  async handleEnterpriseManagedAuthChallenge(challenge, options) {
    const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
    if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
      return { kind: "step_up_confirm", challenge: enriched };
    }
    const config = await this.emaFlowConfigForChallenge(enriched);
    if (enriched.reason === "insufficient_scope") {
      const silent = await trySilentEmaAuth(config);
      if (silent.status === "success") {
        if (await this.checkAuthChallengeSatisfied(enriched)) {
          const minted = await this.requireStorage().getTokens(
            this.getServerUrl()
          );
          const scopeToPersist = resolvePersistedScopeAfterGrant(
            minted?.scope,
            enriched.authorizationScopes?.join(" ")
          );
          if (scopeToPersist) {
            await this.requireStorage().saveScope(
              this.getServerUrl(),
              scopeToPersist
            );
          }
          return { kind: "satisfied" };
        }
      }
      if (silent.status === "mint_failed") {
        return { kind: "failed", error: silent.error };
      }
    } else {
      const tokens = await refreshEmaResourceTokens(config);
      if (tokens) {
        return { kind: "satisfied" };
      }
    }
    try {
      const authorizationUrl = await startEmaIdpAuthorization(config);
      if (enriched.reason === "insufficient_scope" && enriched.authorizationScopes?.length) {
        this.pendingAuthorizationScope = enriched.authorizationScopes.join(" ");
      }
      return { kind: "interactive", authorizationUrl, challenge: enriched };
    } catch (error) {
      return {
        kind: "failed",
        error: error instanceof Error ? error : new Error(String(error))
      };
    }
  }
  async handleStandardAuthChallenge(challenge) {
    const provider = await this.createOAuthProvider();
    const serverUrl = this.getServerUrl();
    const tokens = await provider.tokens();
    const enriched = await this.enrichChallengeWithAuthorizationScopes(
      challenge,
      tokens?.scope
    );
    provider.clearCapturedAuthUrl();
    const resourceMetadataUrl = challengeResourceMetadataUrl(enriched);
    await ensureCimdClientRegistration({
      serverUrl,
      provider,
      fetchFn: this.params.effectiveAuthFetch,
      resourceMetadataUrl
    });
    const requestedScopeForAuth = enriched.reason === "insufficient_scope" ? enriched.authorizationScopes?.join(" ") : this.oauthConfig.scope?.trim() || (enriched.requiredScopes?.length ? enriched.requiredScopes.join(" ") : void 0);
    const scopeForAuth = this.oauthConfig.requestRefreshToken === false ? scopeForDeclinedRefreshGrant(
      requestedScopeForAuth,
      this.oauthConfig.scope,
      // The RAW challenge scopes, not `enriched.requiredScopes`:
      // `enrichChallengeWithScopes` narrows that to the *missing* subset,
      // so a challenge requiring `offline_access tools:write` against a
      // stored scope that already has `offline_access` arrives here as
      // just `["tools:write"]`. Passing the narrowed list would read the
      // server's explicit requirement as inherited and strip it — then
      // re-authorization earns the same challenge forever, which is the
      // loop this argument exists to prevent.
      challenge.requiredScopes
    ) : requestedScopeForAuth;
    provider.setSuppressAuthorizationNavigation(true);
    let result;
    try {
      result = await mcpAuth(provider, {
        serverUrl,
        scope: scopeForAuth,
        fetchFn: this.params.effectiveAuthFetch,
        resourceMetadataUrl,
        ...enriched.reason === "insufficient_scope" && {
          forceReauthorization: isStrictScopeSuperset(
            scopeForAuth,
            tokens?.scope
          )
        }
      });
    } finally {
      provider.setSuppressAuthorizationNavigation(false);
    }
    if (result === "AUTHORIZED") {
      if (enriched.reason === "insufficient_scope") {
        if (await this.checkAuthChallengeSatisfied(enriched)) {
          const freshTokens = await provider.tokens();
          const scopeToPersist = resolvePersistedScopeAfterGrant(
            freshTokens?.scope,
            scopeForAuth
          );
          if (scopeToPersist) {
            await provider.saveScope(scopeToPersist);
          }
          return { kind: "satisfied" };
        }
        const forced = await this.tryForceReauthorizationForStepUp(
          provider,
          serverUrl,
          scopeForAuth,
          enriched
        );
        if (forced) {
          return forced;
        }
        return {
          kind: "failed",
          error: new Error(stepUpInsufficientScopeMessage(enriched))
        };
      } else {
        return { kind: "satisfied" };
      }
    }
    const capturedUrl = provider.getCapturedAuthUrl();
    if (!capturedUrl) {
      return {
        kind: "failed",
        error: new Error("Failed to capture authorization URL")
      };
    }
    this.recordAuthorizationRequestScope(capturedUrl, scopeForAuth);
    const clientInfo = await provider.clientInformation();
    await this.recordAuthorizationCodeFlowState(
      capturedUrl,
      clientInfo,
      // SEP-2350: label a step-up redirect distinctly from a first-time login.
      enriched.reason === "insufficient_scope" ? "scope_step_up" : "authorization_code"
    );
    return {
      kind: "interactive",
      authorizationUrl: capturedUrl,
      challenge: enriched
    };
  }
  /** Start interactive OAuth after handleAuthChallenge returns `interactive`. */
  async beginInteractiveAuthorization(authorizationUrl) {
    const stateParam = authorizationUrl.searchParams.get("state");
    if (stateParam && this.params.onBeforeOAuthRedirect) {
      const parsedState = parseOAuthState(stateParam);
      if (parsedState?.authId) {
        await this.params.onBeforeOAuthRedirect(parsedState.authId);
      }
    }
    this.requireNavigation().navigateToAuthorization(authorizationUrl);
    const provider = await this.createOAuthProvider();
    const clientInfo = await provider.clientInformation();
    await this.recordAuthorizationCodeFlowState(authorizationUrl, clientInfo);
    this.params.dispatchOAuthAuthorizationRequired({ url: authorizationUrl });
  }
  requireNavigation() {
    const navigation = this.oauthConfig.navigation;
    if (!navigation) {
      throw new Error("OAuth navigation is required.");
    }
    return navigation;
  }
  async recordAuthorizationCodeFlowState(authorizationUrl, oauthClientInfo, step = "authorization_code") {
    this.oauthFlowState = {
      ...EMPTY_OAUTH_FLOW_STATE,
      oauthStep: step,
      authorizationUrl,
      oauthClientInfo: oauthClientInfo ?? null
    };
  }
  /**
   * Silent refresh returned AUTHORIZED but token scope still lacks the step-up
   * union — force a fresh authorization redirect.
   */
  async tryForceReauthorizationForStepUp(provider, serverUrl, scopeForAuth, enriched) {
    provider.clearCapturedAuthUrl();
    provider.setSuppressAuthorizationNavigation(true);
    let result;
    try {
      result = await mcpAuth(provider, {
        serverUrl,
        scope: scopeForAuth,
        fetchFn: this.params.effectiveAuthFetch,
        resourceMetadataUrl: challengeResourceMetadataUrl(enriched),
        forceReauthorization: true
      });
    } finally {
      provider.setSuppressAuthorizationNavigation(false);
    }
    if (result !== "AUTHORIZED") {
      return null;
    }
    if (await this.checkAuthChallengeSatisfied(enriched)) {
      const freshTokens = await provider.tokens();
      const scopeToPersist = resolvePersistedScopeAfterGrant(
        freshTokens?.scope,
        scopeForAuth
      );
      if (scopeToPersist) {
        await provider.saveScope(scopeToPersist);
      }
      return { kind: "satisfied" };
    }
    return null;
  }
  /**
   * Create an OAuth provider for transport auth (connect()).
   * Used only when isHttpOAuthConfig() is true.
   */
  async createOAuthProviderForTransport() {
    const provider = await this.createOAuthProvider();
    if (this.isEnterpriseManaged()) {
      return new EmaTransportOAuthProvider(
        provider,
        await this.getEmaFlowConfig()
      );
    }
    return provider;
  }
};

// ../../core/mcp/remote/types.ts
function oauthTokensToRemoteAuthState(tokens) {
  return {
    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
    }
  };
}

// ../../core/mcp/remote/remoteClientTransport.ts
var DEFAULT_SSE_RESPONSE_TIMEOUT_MS = 6e4;
function requestIdForMessage(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 isConnectAuthChallenge(json) {
  return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "auth_challenge";
}
function isConnectTransportError(json) {
  return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
}
function legacySessionId(json) {
  if (typeof json === "object" && json !== null && "sessionId" in json && typeof json.sessionId === "string" && !("ok" in json)) {
    return json.sessionId;
  }
  if (typeof json === "object" && json !== null && "ok" in json && json.ok === true && "sessionId" in json) {
    return json.sessionId;
  }
  return void 0;
}
async function* parseSSE(reader) {
  const decoder = new TextDecoder();
  let buffer = "";
  let currentEvent = "message";
  let currentData = [];
  const consume = (line) => {
    if (line.startsWith("event:")) {
      currentEvent = line.slice(6).trim();
    } else if (line.startsWith("data:")) {
      currentData.push(line.slice(5).trimStart());
    } else if (line === "") {
      const complete = currentData.length > 0 ? { event: currentEvent, data: currentData.join("\n") } : null;
      currentEvent = "message";
      currentData = [];
      return complete;
    }
    return null;
  };
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop() ?? "";
    for (const line of lines) {
      const complete = consume(line);
      if (complete) yield complete;
    }
  }
  if (buffer) {
    const complete = consume(buffer);
    if (complete) yield complete;
  }
  if (currentData.length > 0) {
    yield { event: currentEvent, data: currentData.join("\n") };
  }
}
var RemoteClientTransport = class {
  _sessionId = void 0;
  _protocolVersion = void 0;
  eventStreamReader = null;
  eventStreamAbort = null;
  eventStreamConsumeTask = null;
  restartingEventStream = false;
  closed = false;
  sseResponseWaits = /* @__PURE__ */ new Map();
  options;
  config;
  /**
   * Intentionally returns undefined. The MCP Client checks transport.sessionId to detect
   * reconnects and skip initialize. Our _sessionId is the remote server's session ID, not
   * the MCP protocol's initialization state. Exposing it would cause the MCP Client to
   * skip initialize and send tools/list first, which fails on streamable-http (and any
   * transport requiring initialize before other requests).
   */
  get sessionId() {
    return void 0;
  }
  /** Remote Hono session id (distinct from MCP protocol session). */
  getRemoteBackendSessionId() {
    return this._sessionId;
  }
  /**
   * The SDK Client calls this with the negotiated protocol version as soon as
   * `initialize` resolves — before it sends `notifications/initialized` — so an
   * HTTP transport can stamp `Mcp-Protocol-Version` on every later request.
   *
   * Here the real upstream transport lives on the backend, so we can only
   * record the version and forward it on the next `/api/mcp/send`, which
   * applies it to the upstream transport *before* sending. Because
   * `notifications/initialized` is that next send, it and everything after it
   * (including the standalone SSE GET and the session DELETE, both issued by
   * the upstream transport itself) carry the header (#1935).
   */
  setProtocolVersion(version) {
    this._protocolVersion = version;
  }
  /**
   * Reattach to an existing remote backend session after a full-page OAuth
   * redirect. Opens the SSE event stream without POST /connect.
   */
  async attachToSession(sessionId) {
    if (this.closed) {
      this.closed = false;
    }
    await this.stopEventStream();
    this._sessionId = sessionId;
    await this.openEventStream();
  }
  constructor(options, config) {
    this.options = options;
    this.config = config;
  }
  setAuthRecovery(handlers) {
    this.options.authRecovery = handlers;
  }
  setOnAuthChallenge(handler) {
    this.options.onAuthChallenge = handler;
  }
  get fetchFn() {
    return this.options.fetchFn ?? globalThis.fetch;
  }
  get baseUrl() {
    return this.options.baseUrl.replace(/\/$/, "");
  }
  get headers() {
    const h = {
      "Content-Type": "application/json"
    };
    if (this.options.authToken) {
      h["x-mcp-remote-auth"] = `Bearer ${this.options.authToken}`;
    }
    return h;
  }
  async recoverFromAuthChallenge(challenge, retry) {
    const recovery = this.options.authRecovery;
    if (!recovery) {
      throw new AuthChallengeError(challenge, challenge.raw?.httpStatus ?? 401);
    }
    const outcome = await recovery.handleAuthChallenge(challenge);
    if (outcome.kind === "satisfied") {
      await retry();
      return;
    }
    if (outcome.kind === "step_up_confirm") {
      throw new AuthRecoveryRequiredError(
        EMA_STEP_UP_PENDING_URL,
        outcome.challenge,
        { emaStepUpConfirm: true }
      );
    }
    if (outcome.kind === "interactive") {
      throw new AuthRecoveryRequiredError(
        outcome.authorizationUrl,
        outcome.challenge
      );
    }
    throw outcome.error;
  }
  async start() {
    if (this._sessionId && !this.closed) {
      return;
    }
    return this.startWithRecovery(0);
  }
  async startWithRecovery(retryCount) {
    if (this.sessionId) return;
    if (this.closed) throw new Error("Transport is closed");
    let authState;
    if (this.options.authProvider) {
      const tokens = await this.options.authProvider.tokens();
      if (tokens) {
        authState = oauthTokensToRemoteAuthState(tokens);
      }
    }
    const body = {
      config: this.config,
      ...authState && { authState },
      ...this.options.settings && { settings: this.options.settings }
    };
    const res = await this.fetchFn(`${this.baseUrl}/api/mcp/connect`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify(body)
    });
    if (!res.ok) {
      const text = await res.text();
      const error = new Error(`Remote connect failed (${res.status}): ${text}`);
      error.status = res.status;
      throw error;
    }
    const json = await res.json();
    if (isConnectAuthChallenge(json)) {
      if (retryCount >= 1) {
        throw new AuthChallengeError(
          json.authChallenge,
          json.authChallenge.raw?.httpStatus ?? 401
        );
      }
      await this.recoverFromAuthChallenge(
        json.authChallenge,
        () => this.startWithRecovery(retryCount + 1)
      );
      return;
    }
    if (isConnectTransportError(json)) {
      throw new Error(`Remote connect failed: ${json.error}`);
    }
    const sessionId = legacySessionId(json);
    if (!sessionId) {
      throw new Error("Remote did not return sessionId");
    }
    this._sessionId = sessionId;
    await this.openEventStream();
  }
  async openEventStream() {
    this.eventStreamAbort = new AbortController();
    const eventRes = await this.fetchFn(
      `${this.baseUrl}/api/mcp/events?sessionId=${encodeURIComponent(this._sessionId)}`,
      {
        headers: this.options.authToken ? { "x-mcp-remote-auth": `Bearer ${this.options.authToken}` } : {},
        signal: this.eventStreamAbort.signal
      }
    );
    if (!eventRes.ok) {
      this._sessionId = void 0;
      throw new Error(
        `Remote events stream failed (${eventRes.status}): ${await eventRes.text()}`
      );
    }
    const bodyStream = eventRes.body;
    if (!bodyStream) {
      throw new Error("Remote events stream has no body");
    }
    this.eventStreamReader = bodyStream.getReader();
    this.eventStreamConsumeTask = this.consumeEventStream();
  }
  /** Stop the SSE consumer and release the reader before opening a new stream. */
  async stopEventStream() {
    this.restartingEventStream = true;
    try {
      this.eventStreamAbort?.abort();
      const reader = this.eventStreamReader;
      if (reader) {
        try {
          await reader.cancel();
        } catch {
        }
      }
      this.eventStreamReader = null;
      if (this.eventStreamConsumeTask) {
        await this.eventStreamConsumeTask;
        this.eventStreamConsumeTask = null;
      }
    } finally {
      this.restartingEventStream = false;
    }
  }
  async consumeEventStream() {
    if (!this.eventStreamReader) return;
    try {
      for await (const { data } of parseSSE(this.eventStreamReader)) {
        if (this.closed) break;
        try {
          const parsed = JSON.parse(data);
          if (parsed.type === "message") {
            const msg = parsed.data;
            this.settleSseResponseWait(msg);
            this.onmessage?.(msg, void 0);
          } else if (parsed.type === "fetch_request" && this.options.onFetchRequest) {
            const entry = parsed.data;
            this.options.onFetchRequest({
              ...entry,
              timestamp: typeof entry.timestamp === "string" ? new Date(entry.timestamp) : entry.timestamp
            });
          } else if (parsed.type === "fetch_request_body_update" && this.options.onFetchResponseBody) {
            this.options.onFetchResponseBody(
              parsed.data.id,
              parsed.data.responseBody
            );
          } else if (parsed.type === "stdio_log" && this.options.onStderr) {
            this.options.onStderr({
              timestamp: new Date(parsed.data.timestamp),
              message: parsed.data.message
            });
          } else if (parsed.type === "auth_challenge") {
            this.options.onAuthChallenge?.(parsed.data);
          } else if (parsed.type === "transport_error") {
            const error = new Error(parsed.data.error);
            if (parsed.data.code !== void 0) {
              error.code = parsed.data.code;
            }
            if (!this.restartingEventStream) {
              this.onerror?.(error);
              if (!this.closed) {
                this.closed = true;
                this.onclose?.();
              }
            }
          }
        } catch (err) {
          this.onerror?.(err instanceof Error ? err : new Error(String(err)));
        }
      }
    } catch (err) {
      if (!this.closed && err instanceof Error && err.name !== "AbortError") {
        this.onerror?.(err);
      }
    } finally {
      this.eventStreamReader = null;
      if (!this.closed && !this.restartingEventStream) {
        this.closed = true;
        this.onclose?.();
      }
    }
  }
  async send(message, options) {
    return this.postSend(message, options, 0);
  }
  /**
   * Push auth state to the remote backend without tearing down the session.
   * Used after mid-session OAuth recovery in the web client.
   */
  async pushAuthState(authState) {
    if (!this._sessionId) {
      throw new Error("Transport not started");
    }
    if (this.closed) {
      throw new Error("Transport is closed");
    }
    const state = authState ?? await this.buildAuthStateFromProvider();
    if (!state.oauthTokens && !state.oauthClient) {
      throw new Error("No auth state to push");
    }
    const res = await this.fetchFn(`${this.baseUrl}/api/mcp/auth-state`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify({ sessionId: this._sessionId, authState: state })
    });
    if (!res.ok) {
      const text = await res.text();
      throw new Error(
        `Remote auth-state update failed (${res.status}): ${text}`
      );
    }
    const json = await res.json();
    if (!json.ok) {
      throw new Error(json.error ?? "Remote auth-state update failed");
    }
  }
  async buildAuthStateFromProvider() {
    if (!this.options.authProvider) {
      throw new Error("No auth provider configured");
    }
    const tokens = await this.options.authProvider.tokens();
    if (!tokens) {
      throw new Error("No OAuth tokens available");
    }
    return oauthTokensToRemoteAuthState(tokens);
  }
  get sseResponseTimeoutMs() {
    return this.options.sseResponseTimeoutMs ?? DEFAULT_SSE_RESPONSE_TIMEOUT_MS;
  }
  settleSseResponseWait(message) {
    if (!("id" in message) || message.id === null || message.id === void 0 || !("result" in message) && !("error" in message)) {
      return;
    }
    const wait = this.sseResponseWaits.get(message.id);
    if (!wait) {
      return;
    }
    this.sseResponseWaits.delete(message.id);
    wait.resolve();
  }
  cancelSseResponseWait(requestId) {
    const wait = this.sseResponseWaits.get(requestId);
    if (!wait) {
      return;
    }
    this.sseResponseWaits.delete(requestId);
    wait.reject(new Error("SSE response wait cancelled"));
  }
  cancelAllSseWaits(error) {
    for (const wait of this.sseResponseWaits.values()) {
      wait.reject(error);
    }
    this.sseResponseWaits.clear();
  }
  waitForSseResponse(requestId) {
    return new Promise((resolve7, reject) => {
      const timer = setTimeout(() => {
        this.sseResponseWaits.delete(requestId);
        reject(
          new Error(
            `Timed out waiting for MCP response on SSE (${this.sseResponseTimeoutMs}ms)`
          )
        );
      }, this.sseResponseTimeoutMs);
      this.sseResponseWaits.set(requestId, {
        resolve: () => {
          clearTimeout(timer);
          resolve7();
        },
        reject: (error) => {
          clearTimeout(timer);
          reject(error);
        }
      });
    });
  }
  async postSend(message, options, retryCount) {
    const requestId = requestIdForMessage(message);
    let sseWait;
    if (requestId !== void 0) {
      sseWait = this.waitForSseResponse(requestId);
      void sseWait.catch(() => {
      });
    }
    let json;
    try {
      json = await this.requestSend(message, options);
    } catch (error) {
      if (requestId !== void 0) {
        this.cancelSseResponseWait(requestId);
      }
      throw error;
    }
    if (json.ok) {
      if (sseWait) {
        await sseWait;
      }
      return;
    }
    if (requestId !== void 0) {
      this.cancelSseResponseWait(requestId);
    }
    if (json.kind === "auth_challenge") {
      if (retryCount >= 1 || !this.options.authRecovery?.pushAuthState) {
        throw new AuthChallengeError(
          json.authChallenge,
          json.authChallenge.raw?.httpStatus ?? 401
        );
      }
      await this.recoverFromAuthChallenge(json.authChallenge, async () => {
        await this.options.authRecovery.pushAuthState();
        return this.postSend(message, options, retryCount + 1);
      });
      return;
    }
    throw new Error(json.error);
  }
  async requestSend(message, options) {
    if (!this._sessionId) {
      throw new Error("Transport not started");
    }
    if (this.closed) {
      throw new Error("Transport is closed");
    }
    const body = {
      sessionId: this._sessionId,
      message,
      ...options?.relatedRequestId != null && {
        relatedRequestId: options.relatedRequestId
      },
      // Forward per-send `Mcp-Param-*` headers (SEP-2243) for the backend to
      // apply to the upstream send; the browser can't set them cross-origin.
      ...options?.headers != null && { headers: options.headers },
      // Forward the negotiated protocol version so the backend's transport can
      // stamp `Mcp-Protocol-Version` on this and every later request (#1935).
      ...this._protocolVersion !== void 0 && {
        protocolVersion: this._protocolVersion
      }
    };
    const res = await this.fetchFn(`${this.baseUrl}/api/mcp/send`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify(body)
    });
    if (!res.ok) {
      const text = await res.text();
      const error = new Error(`Remote send failed (${res.status}): ${text}`);
      error.status = res.status;
      throw error;
    }
    return await res.json();
  }
  async close() {
    if (this.closed) return;
    this.closed = true;
    this.cancelAllSseWaits(new Error("Transport closed"));
    await this.stopEventStream();
    if (this._sessionId) {
      try {
        await this.fetchFn(`${this.baseUrl}/api/mcp/disconnect`, {
          method: "POST",
          headers: this.headers,
          body: JSON.stringify({ sessionId: this._sessionId })
        });
      } catch {
      }
      this._sessionId = void 0;
    }
    this.onclose?.();
  }
  onclose;
  onerror;
  onmessage;
};

// ../../core/mcp/inspectorClient.ts
var corePackageJson = {
  name: "mcp-inspector",
  version: "0.0.0"
};
var MAX_URL_ELICITATION_RETRIES = 5;
function createPendingAbortError() {
  return new Error("Pending request aborted");
}
var TOOL_CALL_CANCELLED_REASON = "Tool call cancelled by user";
var DEFAULT_TASK_POLL_INTERVAL_MS = 500;
async function closeSubscriptionBestEffort(subscription) {
  try {
    await subscription.close();
  } catch {
  }
}
function notificationMethodFromSchema(schema) {
  if (schema !== null && typeof schema === "object") {
    const literal = schema.shape?.method?.value;
    if (typeof literal === "string") return literal;
  }
  return void 0;
}
function describeJsonType2(value) {
  if (value === null) return "null";
  if (Array.isArray(value)) return "array";
  return typeof value;
}
function isProgressToken(value) {
  return ProgressTokenSchema.safeParse(value).success;
}
function dropInvalidReservedMeta(meta, logger) {
  const result = RequestMetaSchema.safeParse(meta);
  if (result.success) return meta;
  const cleaned = { ...meta };
  for (const issue of result.error.issues) {
    const key = issue.path[0];
    if (typeof key !== "string" || !(key in cleaned)) continue;
    logger.warn(
      { key, received: describeJsonType2(cleaned[key]) },
      "Dropping reserved `_meta` member \u2014 it does not match the protocol schema."
    );
    delete cleaned[key];
  }
  return cleaned;
}
var MODERN_RECONNECT_BASE_MS = 500;
var MODERN_RECONNECT_MAX_MS = 15e3;
var MODERN_RECONNECT_MAX_ATTEMPTS = 8;
var InspectorClient = class _InspectorClient extends InspectorClientEventTarget {
  /**
   * Upper bound on MRTR (`input_required`) rounds for a single logical request
   * before {@link requestWithInputRequired} gives up. We drive the loop
   * ourselves (`inputRequired: { autoFulfill: false }`), so this is the manual
   * counterpart to the SDK auto-driver's default `maxRounds` (10) and guards
   * against a server that keeps returning `input_required` forever.
   */
  static MRTR_MAX_ROUNDS = 10;
  client = null;
  appRendererClientProxy = null;
  // Lazily-built validator used only on the skipOutputValidation path to detect
  // (non-fatally) when a delivered result violates the tool's outputSchema.
  outputValidator = null;
  transport = null;
  baseTransport = null;
  // Correlation for `markResponseRejected` (#1953): the method of each
  // outbound request still awaiting a response, and — once one is answered —
  // the id of the most recently answered request per method. Entries are
  // dropped as responses arrive, so this holds at most one id per method
  // rather than growing with the session.
  outboundRequestMethods = /* @__PURE__ */ new Map();
  lastAnsweredRequestByMethod = /* @__PURE__ */ new Map();
  /** True when the cached transport was built with an OAuth authProvider attached. */
  transportHasAuthProvider = false;
  /** Dedupes concurrent ambient auth challenges (reason + scopes). */
  ambientAuthChallengeInFlight = /* @__PURE__ */ new Map();
  pipeStderr;
  initialLoggingLevel;
  // Modern-era per-request log level (#1629). On 2026-07-28 servers
  // `logging/setLevel` is gone; the client opts into logs per request via the
  // `io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not attach
  // it automatically. When set, `mergeMeta` stamps this level on every outgoing
  // request so server logs arrive on each request's stream; `undefined` means
  // "don't opt in" (logs stay silently absent). Only honored on the modern era.
  modernLogLevel;
  sample;
  elicit;
  progress;
  resetTimeoutOnProgress;
  requestTimeout;
  defaultMetadata;
  serverSettings;
  versionNegotiation;
  status = "disconnected";
  // True only while an explicit disconnect() owns the teardown. close() can
  // trigger the transport's onclose synchronously, so this lets onclose defer
  // the canonical status set + `disconnect` event to disconnect() and fire it
  // exactly once (see onclose / disconnect()).
  disconnecting = false;
  // Server data (resources, resourceTemplates, prompts are in state managers)
  capabilities;
  serverInfo;
  instructions;
  protocolVersion;
  // Era model (SEP §7.8). Populated after connect from the SDK Client's
  // negotiation accessors. `protocolEra` is the negotiated era ("legacy" |
  // "modern"); `discoverResult` is the `server/discover` payload on a
  // probed/pinned connection (undefined on a plain legacy connect).
  protocolEra;
  discoverResult;
  // Tools the SDK excludes from `tools/list` for invalid `x-mcp-header`
  // annotations (SEP-2243), recomputed on every aggregate tools refresh and
  // surfaced so the Tools tab can show why a tool vanished (#1632).
  excludedTools = [];
  // Entries the Inspector dropped from a list result because they failed the
  // spec schema for that primitive, keyed by list method. Populated only by the
  // salvage fallback in `listAllSalvaging` — empty against a conforming server
  // (#1909).
  malformedListItems = /* @__PURE__ */ new Map();
  // The capabilities this Inspector client advertises to the server during the
  // initialize handshake. Built once in setupClient() and snapshotted here so
  // UI surfaces (Server Info modal) can display them without poking at the
  // SDK Client's private state.
  clientCapabilities = {};
  // The client identity ({name, version}) passed to the SDK Client. Reused to
  // build the modern per-request envelope for raw tasks/* requests.
  clientInfo;
  // Sampling requests
  pendingSamples = [];
  // Elicitation requests
  pendingElicitations = [];
  // Roots (undefined means roots capability not enabled, empty array means enabled but no roots)
  roots;
  /**
   * Whether `capabilities.roots` was advertised at `initialize`, read off the
   * capability object actually sent rather than re-derived from the constructor
   * option. Fixed for the client's lifetime, because the capability is
   * negotiated at construction and the SDK refuses `registerCapabilities` after
   * connect.
   *
   * The `roots/list` registration gates on *this*, not on `this.roots`, which
   * `setRoots()` can make defined later: the SDK throws "Client does not support
   * roots capability" from `setRequestHandler` when the capability was never
   * advertised, and that throw would land before the handshake on every
   * subsequent `connect()` — wedging the client permanently (#1797).
   */
  rootsCapabilityAdvertised;
  /**
   * Whether `capabilities.elicitation` was advertised, on the same terms as
   * {@link rootsCapabilityAdvertised}. Not the same question as `this.elicit`
   * being truthy: `{}` / `{ form: false, url: false }` are valid options that
   * enable no mode, so nothing is advertised — and registering
   * `elicitation/create` anyway throws "Client does not support elicitation
   * capability" before the handshake, leaving the client unable to connect
   * at all (#1797).
   */
  elicitationCapabilityAdvertised;
  /** As above, for `capabilities.sampling`. */
  samplingCapabilityAdvertised;
  /** As above, for `capabilities.tasks` (the receiver-side `tasks/*` polls). */
  tasksCapabilityAdvertised;
  /** As above, for `capabilities.elicitation.url` (the URL-mode completion). */
  urlElicitationCapabilityAdvertised;
  /**
   * As above, for `capabilities.roots.listChanged` — the predicate the SDK
   * asserts before letting us *send* `roots/list_changed`, which is narrower
   * than the `capabilities.roots` presence it asserts before letting us
   * *register* `roots/list`. They coincide today (roots are only ever
   * advertised as `{ listChanged: true }`), but reading each assertion's own
   * predicate is what keeps a future conditional advertisement from silently
   * un-gating one of them.
   */
  rootsListChangedCapabilityAdvertised;
  // Content cache
  // ListChanged notification configuration
  listChangedNotifications;
  // Resource subscriptions. The set of subscribed URIs is the era-agnostic
  // source of truth for the UI (the `resourceSubscriptionsChange` list). How a
  // subscription reaches the wire forks by era: legacy sends one
  // `resources/subscribe` per URI; modern (2026-07-28) has no such method — all
  // subscriptions are a filter over one long-lived `subscriptions/listen` stream
  // (#1630, SEP §7.4).
  subscribedResources = /* @__PURE__ */ new Set();
  // Modern-era listen stream backing the subscriptions above. A single
  // `McpSubscription` whose filter's `resourceSubscriptions` mirrors
  // `subscribedResources`; mutating the set re-lists (close old, open new), and
  // an unexpected `"remote"` close re-lists (reconnect-by-re-listen — the stream
  // is not resumable). `null` on the legacy era, and on the modern era whenever
  // the filter is empty — which is *not* the same as "no URI subscribed": the
  // stream also carries the list-change opt-ins, so a tools-only server opens it
  // with no subscriptions at all (#1920).
  modernSubscription = null;
  // Monotonic guard so a stale re-list/reconnect (whose `listen()` or `closed`
  // resolves after a newer refresh already started) can detect it lost the race
  // and bail without clobbering the current stream.
  modernListenGeneration = 0;
  // Last dispatched modern stream state; `active: false` on the legacy era.
  modernStreamState = INACTIVE_SUBSCRIPTION_STREAM_STATE;
  // Reconnect-by-re-listen backoff state (#1630): the count of consecutive
  // *failed* re-lists (reset to 0 on any successful acknowledgement) and the
  // pending re-listen timer.
  modernReconnectAttempts = 0;
  modernReconnectTimer;
  // Task ids the user explicitly cancelled. A cancel makes the in-flight
  // `callToolStream` reject with a generic -32603 error, which the stream's
  // error path would otherwise report as a *failed* task — flashing "failed"
  // in the UI until a refresh fetches the server's true "cancelled" state.
  // Recording the id lets that path label the terminal task "cancelled"
  // instead, so it lands in the right state immediately (#1455). Cleared on
  // disconnect.
  cancelledTaskIds = /* @__PURE__ */ new Set();
  // Per-task abort controllers for a modern task paused at `input_required`.
  // While the poll loop blocks on the pending elicitation (the modal), the tool
  // call's own abort path isn't in play — so `cancelRequestorTask` aborts this
  // controller to reject the pending request, close the modal, and let the poll
  // observe the cancellation. Keyed by taskId; created/removed by the poll loops.
  taskInputAbortControllers = /* @__PURE__ */ new Map();
  // Pending raw-wire requests (modern tasks/* — see rawWireRequest). Keyed by a
  // string JSON-RPC id we mint; the SDK Client only mints numeric ids, so ours
  // never collide with (or reach) it. Resolved by the transport's
  // consume-response hook and rejected on disconnect.
  pendingRawWireRequests = /* @__PURE__ */ new Map();
  rawWireRequestCounter = 0;
  // Abort controller for the in-flight ordinary (non-task) tool call. Aborting
  // it makes the SDK send a `notifications/cancelled` for that request (the MCP
  // cancellation flow) and reject the pending call, which `callTool` surfaces as
  // a `ToolCallCancelledError`. Undefined when no ordinary call is in flight.
  // Task-augmented calls have a server-side task and are cancelled via
  // `cancelRequestorTask` instead, so they don't use this (#1458).
  activeToolCallAbortController;
  // Receiver tasks (server-initiated: server sends createMessage/elicit with params.task, server polls us)
  receiverTasks;
  // Per-extension advertise overrides (#1738); undefined key falls back to the
  // registry default in ADVERTISABLE_EXTENSIONS.
  advertisedExtensions;
  /**
   * Host-supplied renderer for app-rendered form elicitations (#1854), or
   * undefined on a client that cannot host MCP Apps. Its presence is what
   * advertises the nested MCP Apps `elicitation` capability, so this is the one
   * fact both the advertisement and the routing gate read.
   */
  appElicitationRenderer;
  /**
   * Monotonic counter behind the request-scoped id handed to the renderer. The
   * association MUST be per request (a map keyed by this id owns the resource
   * URI, renderer instance and promise on the host side) so two concurrent
   * elicitations cannot resolve through each other's bridges.
   */
  appElicitationSeq = 0;
  /**
   * Per-instance prefix for {@link appElicitationSeq}.
   *
   * A counter alone is not enough: a host may hold ONE renderer across
   * replacement clients (the web client rebuilds its `InspectorClient` on a
   * settings change), and every fresh instance would otherwise mint
   * `app-elicitation-1` for its first request. Settling that id would then
   * resolve — or discard — whichever connection's request the host happened to
   * be keying.
   */
  appElicitationPrefix = `app-elicitation-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
  /**
   * Abort controllers for app-rendered elicitations still awaiting an answer.
   * Aborted alongside the native pending queue on disconnect, so a rendered app
   * cannot outlive the connection that asked for it.
   */
  activeAppElicitations = /* @__PURE__ */ new Set();
  receiverTaskTtlMs;
  receiverTaskRecords = /* @__PURE__ */ new Map();
  // OAuth support (config owned by oauthManager; client delegates and uses !!oauthManager for "is OAuth configured")
  oauthManager = null;
  logger;
  transportClientFactory;
  fetchFn;
  effectiveAuthFetch;
  // Session ID (for OAuth state and saveSession event; persistence is in FetchRequestLogState)
  sessionId;
  transportConfig;
  /** null until first transport is built; then true for in-process OAuth runners. */
  directAuthRecoveryActive = null;
  /**
   * Nesting depth of in-flight silent auth recoveries. `withDirectAuthRecovery`
   * bounds retries per call (`attempt >= 1`), but a satisfied outcome recovers
   * by running a *nested* `connect()`, which starts its own recovery with a
   * fresh counter — so a server that keeps answering 401 with freshly refreshed
   * tokens would recurse without bound. Counted across the nesting boundary and
   * capped at {@link InspectorClient.MAX_NESTED_AUTH_RECOVERIES}.
   */
  authRecoveryDepth = 0;
  /**
   * Cap on nested silent recoveries. One is the normal case (challenge →
   * refresh → reconnect); a couple more absorb a legitimately re-challenged
   * reconnect. Beyond that the credentials are not fixing the challenge, so the
   * challenge is surfaced to the caller instead of recovered again.
   */
  static MAX_NESTED_AUTH_RECOVERIES = 3;
  /**
   * Opt-in from {@link InspectorClientOptions.directAuthRecovery}: when true and
   * the live transport is direct (not {@link RemoteClientTransport}), RPCs use
   * fetch intercept + {@link withDirectAuthRecovery}.
   */
  directAuthRecovery;
  constructor(transportConfig, options) {
    super();
    this.transportConfig = transportConfig;
    this.transportClientFactory = options.environment.transport;
    this.fetchFn = options.environment.fetch;
    this.logger = options.environment.logger ?? silentLogger;
    this.pipeStderr = options.pipeStderr ?? false;
    this.initialLoggingLevel = options.initialLoggingLevel;
    this.sample = options.sample ?? true;
    this.elicit = options.elicit ?? true;
    this.receiverTasks = options.receiverTasks ?? false;
    this.advertisedExtensions = options.advertisedExtensions;
    this.appElicitationRenderer = options.appElicitation;
    this.receiverTaskTtlMs = options.receiverTaskTtlMs ?? 6e4;
    this.progress = options.progress ?? true;
    this.resetTimeoutOnProgress = options.resetTimeoutOnProgress ?? true;
    this.requestTimeout = options.timeout;
    this.defaultMetadata = options.defaultMetadata && Object.keys(options.defaultMetadata).length > 0 ? options.defaultMetadata : void 0;
    this.serverSettings = options.serverSettings;
    this.modernLogLevel = resolveModernLogLevel(options.serverSettings);
    this.versionNegotiation = options.versionNegotiation ?? { mode: "legacy" };
    this.directAuthRecovery = options.directAuthRecovery ?? false;
    this.roots = options.roots !== void 0 ? cleanRoots(options.roots) : void 0;
    this.listChangedNotifications = {
      tools: options.listChangedNotifications?.tools ?? true,
      resources: options.listChangedNotifications?.resources ?? true,
      prompts: options.listChangedNotifications?.prompts ?? true
    };
    this.fetchFn = withOAuthEndpointOverrides(
      this.fetchFn ?? fetch,
      () => this.oauthManager?.getEndpointOverrides()
    );
    this.effectiveAuthFetch = this.buildEffectiveAuthFetch();
    this.sessionId = options.sessionId;
    if (options.oauth || options.environment.oauth) {
      const oauthConfig = {
        // Environment components (storage, navigation, redirectUrlProvider)
        ...options.environment.oauth,
        // Config values (clientId, clientSecret, clientMetadataUrl, scope)
        ...options.oauth
      };
      this.oauthManager = new OAuthManager({
        getServerUrl: () => this.getServerUrl(),
        effectiveAuthFetch: this.effectiveAuthFetch,
        getEventTarget: () => this,
        onBeforeOAuthRedirect: (sessionId) => {
          this.sessionId = sessionId;
          this.saveSession();
          return Promise.resolve();
        },
        initialConfig: oauthConfig,
        enterpriseManagedAuth: options.enterpriseManagedAuth,
        installEnterpriseManagedAuth: options.installEnterpriseManagedAuth,
        dispatchOAuthComplete: (detail) => this.dispatchTypedEvent("oauthComplete", detail),
        dispatchOAuthAuthorizationRequired: (detail) => this.dispatchTypedEvent("oauthAuthorizationRequired", detail),
        dispatchOAuthError: (detail) => this.dispatchTypedEvent("oauthError", detail)
      });
    }
    const clientOptions = {
      // Bound the SDK's auto-aggregate with the SAME constant the #1909 salvage
      // re-walks use, so the strict path and its fallback cannot drift apart.
      // This is the SDK's own default today; setting it explicitly is what makes
      // the shared value a fact rather than a copied guess.
      listMaxPages: LIST_MAX_PAGES,
      // Per-server protocol era (SEP §7.8), threaded from config via
      // `eraToVersionNegotiation` and defaulted to `{ mode: "legacy" }` in the
      // constructor. "legacy" keeps the wire byte-identical to a 2025 client;
      // "auto"/"modern" opt into 2026-era negotiation (#1626).
      versionNegotiation: this.versionNegotiation,
      // Drive MRTR (SEP-2322) manually instead of letting the SDK auto-fulfil
      // and hide the retry loop (#1704). Unconditional and safe on every era:
      // legacy servers never return `input_required`, so this is a no-op there;
      // on modern connections the three multi-round-trip methods opt in via
      // `allowInputRequired` in `requestWithInputRequired`, and no other method
      // can receive an `input_required` result. The negotiated era is unknown
      // at construction time, so gating here is impossible anyway.
      inputRequired: { autoFulfill: false }
    };
    const capabilities = {};
    if (this.sample) {
      capabilities.sampling = {};
    }
    if (this.elicit) {
      const elicitationCap = {};
      if (this.elicit === true) {
        elicitationCap.form = {};
      } else {
        if (this.elicit.form) {
          elicitationCap.form = {};
        }
        if (this.elicit.url) {
          elicitationCap.url = {};
        }
      }
      if (Object.keys(elicitationCap).length > 0) {
        capabilities.elicitation = elicitationCap;
      }
    }
    if (this.roots !== void 0) {
      capabilities.roots = { listChanged: true };
    }
    if (this.receiverTasks) {
      const taskRequests = {};
      if (capabilities.sampling) {
        taskRequests.sampling = { createMessage: {} };
      }
      if (capabilities.elicitation) {
        taskRequests.elicitation = { create: {} };
      }
      capabilities.tasks = {
        list: {},
        cancel: {},
        ...Object.keys(taskRequests).length > 0 && { requests: taskRequests }
      };
    }
    const advertisedExtensions = buildClientExtensions({
      enterpriseManaged: options.oauth?.enterpriseManaged ?? false,
      advertised: this.advertisedExtensions,
      // Read off the built `capabilities.elicitation.form` rather than
      // re-deriving from `options.elicit`: the nested MCP Apps `elicitation`
      // setting must never be advertised without the core form capability it
      // extends, and two derivations of the same fact can drift. Disabling form
      // elicitation therefore drops both, as the contract requires. (#1854)
      appElicitation: this.appElicitationRenderer !== void 0 && capabilities.elicitation?.form !== void 0
    });
    if (Object.keys(advertisedExtensions).length > 0) {
      capabilities.extensions = {
        ...capabilities.extensions,
        ...advertisedExtensions
      };
    }
    clientOptions.capabilities = capabilities;
    this.clientCapabilities = capabilities;
    this.rootsCapabilityAdvertised = capabilities.roots !== void 0;
    this.elicitationCapabilityAdvertised = capabilities.elicitation !== void 0;
    this.samplingCapabilityAdvertised = capabilities.sampling !== void 0;
    this.tasksCapabilityAdvertised = capabilities.tasks !== void 0;
    this.urlElicitationCapabilityAdvertised = capabilities.elicitation?.url !== void 0;
    this.rootsListChangedCapabilityAdvertised = capabilities.roots?.listChanged === true;
    this.appRendererClientProxy = null;
    this.clientInfo = options.clientIdentity ?? {
      name: corePackageJson.name.split("/")[1] ?? corePackageJson.name,
      version: corePackageJson.version
    };
    this.client = new Client(
      this.clientInfo,
      Object.keys(clientOptions).length > 0 ? clientOptions : void 0
    );
  }
  buildEffectiveAuthFetch() {
    const base = this.fetchFn ?? fetch;
    return createFetchTracker(base, {
      trackRequest: (entry) => this.dispatchFetchRequest({ ...entry, category: "auth" }),
      updateResponseBody: (id, body) => this.dispatchFetchRequestBodyUpdate(id, body)
    });
  }
  createMessageTrackingCallbacks() {
    return {
      trackRequest: (message, origin) => {
        if (origin === "client") {
          this.outboundRequestMethods.set(message.id, message.method);
        }
        const entry = {
          id: crypto.randomUUID(),
          timestamp: /* @__PURE__ */ new Date(),
          direction: "request",
          origin,
          message
        };
        this.dispatchTypedEvent("message", entry);
      },
      trackResponse: (message, origin) => {
        const responseId = message.id;
        if (origin === "server" && responseId !== void 0) {
          const method = this.outboundRequestMethods.get(responseId);
          this.outboundRequestMethods.delete(responseId);
          if (method !== void 0) {
            this.lastAnsweredRequestByMethod.set(method, responseId);
          }
        }
        const entry = {
          id: crypto.randomUUID(),
          timestamp: /* @__PURE__ */ new Date(),
          direction: "response",
          origin,
          message
        };
        this.dispatchTypedEvent("message", entry);
      },
      trackNotification: (message, origin) => {
        const entry = {
          id: crypto.randomUUID(),
          timestamp: /* @__PURE__ */ new Date(),
          direction: "notification",
          origin,
          message
        };
        this.dispatchTypedEvent("message", entry);
      }
    };
  }
  attachTransportListeners(baseTransport) {
    baseTransport.onclose = () => {
      if (this.disconnecting) return;
      if (this.status === "disconnected") return;
      if (this.status !== "error") {
        this.status = "disconnected";
        this.dispatchTypedEvent("statusChange", this.status);
      }
      this.clearAndAnnouncePendingPeerRequests();
      this.rejectPendingRawWireRequests("Connection closed");
      this.dispatchTypedEvent("disconnect");
    };
    baseTransport.onerror = (error) => {
      if (this.status === "connecting") return;
      this.status = "error";
      this.dispatchTypedEvent("statusChange", this.status);
      this.dispatchTypedEvent("error", error);
    };
  }
  /**
   * Build RequestOptions for SDK client calls (timeout, resetTimeoutOnProgress, onprogress).
   * When timeout is unset, SDK uses DEFAULT_REQUEST_TIMEOUT_MSEC (60s).
   *
   * When progress is enabled, we pass a per-request onprogress so the SDK routes progress and
   * runs timeout reset. The SDK injects progressToken: messageId; we do not expose the caller's
   * token to the server. We collect it from metadata and inject it into dispatched progressNotification
   * events only, so listeners can correlate progress with the request that triggered it.
   *
   * @param progressToken Optional token from request metadata; injected into progressNotification
   * events when provided (not sent to server).
   */
  /**
   * Merge per-call metadata with this client's `defaultMetadata` (from
   * `InspectorClientOptions.defaultMetadata`, set from
   * `InspectorServerSettings.metadata`). Call-time keys override defaults.
   * Returns `undefined` when the combined map is empty so callers can skip
   * injecting an empty `_meta` field.
   */
  mergeMeta(callMetadata) {
    const defaults = this.defaultMetadata;
    const logMeta = this.protocolEra === "modern" && this.modernLogLevel ? { [LOG_LEVEL_META_KEY]: this.modernLogLevel } : void 0;
    const merged = {
      ...defaults ?? {},
      ...logMeta ?? {},
      ...callMetadata ?? {}
    };
    const sanitized = dropInvalidReservedMeta(merged, this.logger);
    return Object.keys(sanitized).length > 0 ? sanitized : void 0;
  }
  /**
   * The `progressToken` a caller stamped on its request metadata, when it is
   * one the SDK will accept.
   *
   * This governs only **callback correlation** — whether the client wires up an
   * `onprogress` for the request. Keeping an invalid value off the **wire** is
   * a separate concern handled in {@link mergeMeta}, which deletes the member
   * from the outgoing `_meta` so a conforming server is never sent something
   * `ProgressTokenSchema` rejects. The two use the same {@link isProgressToken}
   * predicate, so they cannot disagree about what counts as valid.
   */
  progressTokenOf(metadata) {
    const token = metadata?.progressToken;
    return isProgressToken(token) ? token : void 0;
  }
  getRequestOptions(progressToken, signal) {
    const opts = {
      resetTimeoutOnProgress: this.resetTimeoutOnProgress
    };
    if (this.requestTimeout !== void 0) {
      opts.timeout = this.requestTimeout;
    }
    if (signal) {
      opts.signal = signal;
    }
    if (this.progress) {
      const token = progressToken;
      const onprogress = (progress) => {
        const payload = {
          ...progress,
          ...token != null && { progressToken: token }
        };
        this.dispatchTypedEvent("progressNotification", payload);
      };
      opts.onprogress = onprogress;
    }
    return opts;
  }
  /**
   * {@link getRequestOptions} plus the per-call `cacheMode` for the SDK's
   * cacheable verbs (the high-level `client.listTools()` / `listPrompts()` /
   * `listResources()` / `listResourceTemplates()` used by the `listAll*`
   * aggregate methods below). `cacheMode` is only honored by those wrappers —
   * the single-page `client.request` path ignores it — so it lives here rather
   * than in `getRequestOptions`. Omitted when unset so the SDK default
   * (`'use'`) applies.
   */
  getCacheableRequestOptions(cacheMode) {
    const opts = this.getRequestOptions();
    if (cacheMode !== void 0) {
      opts.cacheMode = cacheMode;
    }
    return opts;
  }
  /**
   * Build the `params` for the aggregate `listAll*` verbs: merge call metadata
   * with `defaultMetadata` and wrap as `{ _meta }`, or `undefined` when empty
   * (so the SDK skips an empty `_meta`). Shared by all four `listAll*` methods
   * so the merge/omit branch is defined once.
   */
  aggregateListParams(metadata) {
    const effectiveMeta = this.mergeMeta(metadata);
    return effectiveMeta ? { _meta: effectiveMeta } : void 0;
  }
  isHttpOAuthConfig() {
    const serverType = getServerType(this.transportConfig);
    return (serverType === "sse" || serverType === "streamable-http") && !!this.oauthManager;
  }
  /**
   * True when task status is completed, failed, or cancelled.
   * We use this private helper instead of the SDK's experimental isTerminal()
   * to avoid depending on experimental API and to get a type predicate so
   * TypeScript narrows status to "completed" | "failed" | "cancelled" after the check.
   */
  static isTerminalTaskStatus(status) {
    return status === "completed" || status === "failed" || status === "cancelled";
  }
  /**
   * Route a receiver (server-initiated) task-augmented `sampling/createMessage`
   * or `elicitation/create` response around the v2 Client's result validation.
   *
   * SDK v2's `Client` wraps every spec request handler (`_wrapHandler`) to
   * validate the result it returns — for sampling/elicitation it checks the
   * value against `CreateMessageResult` / `ElicitResult` and rejects anything
   * else with a `-32602`. The 2025-11-25 task flow answers a task-augmented
   * request with a `CreateTaskResult` (`{ task }`), which that validation
   * rejects — breaking server-initiated tasks that worked on the legacy client.
   *
   * There is no public seam to opt a handler out of result validation, so we
   * swap the wrapped entry in the Protocol's private `_requestHandlers` map for
   * one that dispatches the task-augmented branch straight through the raw
   * handler (whose `{ task }` return then rides the legacy codec's pass-through
   * `encodeResult` to the wire), while ordinary (non-task) requests keep the
   * validating path. Mirrors the bypass a legacy server needs to emit `{ task }`.
   * Delete once the SDK models task-augmented results natively (see #1624 stack).
   */
  installReceiverTaskResponseBypass(method, rawHandler) {
    if (!this.client) return;
    const internal = this.client;
    const validating = internal._requestHandlers.get(method);
    if (!validating) return;
    internal._requestHandlers.set(method, (request, ctx) => {
      const task = request?.params?.task;
      if (this.tasksCapabilityAdvertised && task != null) {
        return rawHandler(request);
      }
      return validating(request, ctx);
    });
  }
  createReceiverTask(opts) {
    const taskId = crypto.randomUUID();
    const ttlMs = opts.ttl ?? (typeof this.receiverTaskTtlMs === "function" ? this.receiverTaskTtlMs() : this.receiverTaskTtlMs);
    const now = (/* @__PURE__ */ new Date()).toISOString();
    const task = {
      taskId,
      status: opts.initialStatus,
      ttl: ttlMs,
      createdAt: now,
      lastUpdatedAt: now,
      ...opts.pollInterval != null && { pollInterval: opts.pollInterval },
      ...opts.statusMessage != null && { statusMessage: opts.statusMessage }
    };
    let resolvePayload;
    let rejectPayload;
    const payloadPromise = new Promise((resolve7, reject) => {
      resolvePayload = resolve7;
      rejectPayload = reject;
    });
    void payloadPromise.catch(() => {
    });
    const record = {
      task,
      payloadPromise,
      resolvePayload,
      rejectPayload,
      abort: new AbortController()
    };
    record.cleanupTimeoutId = setTimeout(() => {
      record.cleanupTimeoutId = void 0;
      this.receiverTaskRecords.delete(taskId);
    }, ttlMs);
    this.receiverTaskRecords.set(taskId, record);
    return record;
  }
  emitReceiverTaskStatus(task) {
    if (!this.client) return;
    try {
      const notification = TaskStatusNotificationSchema.parse({
        method: "notifications/tasks/status",
        params: task
      });
      this.client.notification(notification).catch((err) => {
        this.logger.warn(
          { err, taskId: task.taskId },
          "receiver task status notification failed"
        );
      });
    } catch (err) {
      this.logger.warn(
        { err, taskId: task.taskId },
        "receiver task status notification failed"
      );
    }
  }
  upsertReceiverTask(updatedTask) {
    const record = this.receiverTaskRecords.get(updatedTask.taskId);
    if (record) {
      record.task = updatedTask;
      this.emitReceiverTaskStatus(updatedTask);
    }
  }
  getReceiverTask(taskId) {
    return this.receiverTaskRecords.get(taskId);
  }
  listReceiverTasks() {
    return Array.from(this.receiverTaskRecords.values()).map((r) => r.task);
  }
  async getReceiverTaskPayload(taskId) {
    const record = this.receiverTaskRecords.get(taskId);
    if (!record) {
      throw new ProtocolError2(
        ProtocolErrorCode2.InvalidParams,
        `Unknown taskId: ${taskId}`
      );
    }
    return record.payloadPromise;
  }
  cancelReceiverTask(taskId) {
    const record = this.receiverTaskRecords.get(taskId);
    if (!record) {
      throw new ProtocolError2(
        ProtocolErrorCode2.InvalidParams,
        `Unknown taskId: ${taskId}`
      );
    }
    if (_InspectorClient.isTerminalTaskStatus(record.task.status)) {
      return record.task;
    }
    const now = (/* @__PURE__ */ new Date()).toISOString();
    const updatedTask = {
      ...record.task,
      status: "cancelled",
      lastUpdatedAt: now
    };
    record.task = updatedTask;
    record.rejectPayload(new Error("Task cancelled"));
    record.abort.abort();
    if (record.cleanupTimeoutId != null) {
      clearTimeout(record.cleanupTimeoutId);
      record.cleanupTimeoutId = void 0;
    }
    this.emitReceiverTaskStatus(updatedTask);
    return updatedTask;
  }
  /**
   * Drop the cached MCP transport without a full disconnect() teardown.
   * Used when a pre-auth connect failed or tokens arrived after an unauthenticated
   * transport was created, so the next connect() can attach authProvider.
   */
  async dropCachedTransport() {
    if (!this.baseTransport && !this.transport) {
      this.transportHasAuthProvider = false;
      return;
    }
    try {
      await this.client?.close();
    } catch {
    }
    this.baseTransport = null;
    this.transport = null;
    this.transportHasAuthProvider = false;
  }
  /**
   * Register the handlers for requests the *server* makes of *us* —
   * `roots/list`, `sampling/createMessage`, `elicitation/create`, and the
   * receiver-side `tasks/*` polls.
   *
   * MUST be called before `client.connect()`. The matching capabilities are
   * advertised on the `Client` at construction time, so from the moment
   * `connect()` sends `notifications/initialized` the server is entitled to
   * issue any of these requests. Registering afterwards leaves a window in
   * which the SDK `Client` has no handler and answers `-32601 Method not
   * found` — which is exactly what a server that asks for roots the instant it
   * is initialized (e.g. `server-filesystem`, which learns its allowed
   * directories that way) hits, while a server that asks later does not (#1797).
   *
   * Nothing here depends on the server's capabilities — only on constructor-set
   * state — so there is nothing to wait for. Its sibling
   * {@link registerPeerNotificationHandlers} does the same for the one
   * notification handler in that position; the notification handlers that *do*
   * gate on `this.capabilities` stay in `connect()`, after the handshake.
   */
  registerPeerRequestHandlers() {
    if (this.samplingCapabilityAdvertised && this.client) {
      const samplingHandler = (request) => {
        const paramsTask = request.params?.task;
        if (this.tasksCapabilityAdvertised && paramsTask != null) {
          const record = this.createReceiverTask({
            ttl: paramsTask.ttl,
            initialStatus: "input_required",
            statusMessage: "Awaiting user input"
          });
          void (async () => {
            const samplingRequest = new SamplingCreateMessage(
              request,
              (result) => {
                record.resolvePayload(result);
                const now = (/* @__PURE__ */ new Date()).toISOString();
                const updated = {
                  ...record.task,
                  status: "completed",
                  lastUpdatedAt: now
                };
                record.task = updated;
                this.upsertReceiverTask(updated);
              },
              (error) => {
                record.rejectPayload(error);
                const now = (/* @__PURE__ */ new Date()).toISOString();
                const updated = {
                  ...record.task,
                  status: "failed",
                  lastUpdatedAt: now,
                  statusMessage: error instanceof Error ? error.message : String(error)
                };
                record.task = updated;
                this.upsertReceiverTask(updated);
              },
              (id) => this.removePendingSample(id)
            );
            this.addPendingSample(samplingRequest);
          })();
          const taskResult = { task: record.task };
          return Promise.resolve(taskResult);
        }
        return this.enqueuePendingSample(request, "server-request");
      };
      this.client.setRequestHandler("sampling/createMessage", samplingHandler);
      if (this.tasksCapabilityAdvertised) {
        this.installReceiverTaskResponseBypass(
          "sampling/createMessage",
          samplingHandler
        );
      }
    }
    if (this.elicitationCapabilityAdvertised && this.client) {
      const elicitHandler = (request, ctx) => {
        const paramsTask = request.params?.task;
        if (this.tasksCapabilityAdvertised && paramsTask != null) {
          const record = this.createReceiverTask({
            ttl: paramsTask.ttl,
            initialStatus: "input_required",
            statusMessage: "Awaiting user input"
          });
          const completeTask = (result) => {
            if (_InspectorClient.isTerminalTaskStatus(record.task.status))
              return;
            record.resolvePayload(result);
            const updated = {
              ...record.task,
              status: "completed",
              lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
            };
            record.task = updated;
            this.upsertReceiverTask(updated);
          };
          const failTask = (error) => {
            if (_InspectorClient.isTerminalTaskStatus(record.task.status))
              return;
            record.rejectPayload(error);
            const updated = {
              ...record.task,
              status: "failed",
              lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
              statusMessage: error.message
            };
            record.task = updated;
            this.upsertReceiverTask(updated);
          };
          void (async () => {
            let appResult;
            try {
              appResult = await this.tryAppElicitation(
                request,
                record.abort.signal
              );
            } catch (error) {
              failTask(
                error instanceof Error ? error : new Error(String(error))
              );
              return;
            }
            if (appResult) {
              completeTask(appResult);
              return;
            }
            const elicitationRequest = new ElicitationCreateMessage(
              request,
              completeTask,
              (id) => this.removePendingElicitation(id),
              failTask
            );
            this.addPendingElicitation(elicitationRequest);
            this.wirePendingAbort(
              record.abort.signal,
              () => this.removePendingElicitation(elicitationRequest.id)
            );
          })();
          const taskResult = { task: record.task };
          return Promise.resolve(taskResult);
        }
        return this.enqueuePendingElicitation(
          request,
          "server-request",
          ctx?.mcpReq?.signal
        );
      };
      this.client.setRequestHandler("elicitation/create", elicitHandler);
      if (this.tasksCapabilityAdvertised) {
        this.installReceiverTaskResponseBypass(
          "elicitation/create",
          elicitHandler
        );
      }
    }
    if (this.rootsCapabilityAdvertised && this.client) {
      this.client.setRequestHandler("roots/list", async () => {
        return { roots: this.roots ?? [] };
      });
    }
    if (this.tasksCapabilityAdvertised && this.client) {
      this.client.setRequestHandler(
        "tasks/list",
        { params: ListTasksRequestSchema.shape.params },
        async () => ({ tasks: this.listReceiverTasks() })
      );
      this.client.setRequestHandler(
        "tasks/get",
        { params: GetTaskRequestSchema.shape.params },
        async (params) => {
          const record = this.getReceiverTask(params.taskId);
          if (!record) {
            throw new ProtocolError2(
              ProtocolErrorCode2.InvalidParams,
              `Unknown taskId: ${params.taskId}`
            );
          }
          return record.task;
        }
      );
      this.client.setRequestHandler(
        "tasks/result",
        { params: GetTaskPayloadRequestSchema.shape.params },
        async (params) => this.getReceiverTaskPayload(params.taskId)
      );
      this.client.setRequestHandler(
        "tasks/cancel",
        { params: CancelTaskRequestSchema.shape.params },
        async (params) => this.cancelReceiverTask(params.taskId)
      );
    }
  }
  /**
   * Register the inbound *notification* handlers that depend on nothing but
   * constructor-set state, and so belong before the handshake for the same
   * reason as {@link registerPeerRequestHandlers} (#1797).
   *
   * `notifications/roots/list_changed` is the only one — and note it is a
   * **client**→server notification in the spec (`ClientNotification`): we send
   * it from {@link setRoots}, servers do not normally send it to us. This
   * inbound handler is defensive coverage for a non-conformant or experimental
   * server, and its body dispatches `rootsChange` with our own already-known
   * roots, i.e. a refresh signal carrying no new data. It sits here for
   * consistency with the request handlers — it gates on no server capability,
   * so there is nothing to wait for, and an unhandled notification is dropped
   * silently by the SDK (no wire error) rather than answered `-32601`.
   *
   * The remaining listChanged handlers gate on `this.capabilities`, which is
   * not populated until `fetchServerInfo()` runs, so they stay in `connect()`.
   */
  registerPeerNotificationHandlers() {
    if (!this.client) return;
    this.client.setNotificationHandler(
      "notifications/roots/list_changed",
      async () => {
        this.dispatchTypedEvent("rootsChange", [...this.roots ?? []]);
      }
    );
  }
  /**
   * Stop the receiver tasks' TTL timers and drop the records.
   *
   * These are tasks a *server* created with us, so they belong to the session
   * that created them: `listReceiverTasks()` is what the `tasks/list` handler
   * answers with, and a record surviving into the next session would report a
   * task the new server never created. `disconnect()` clears them, and so does
   * `connect()` — the auth-recovery retry reconnects the *same* client
   * instance, so ending the session isn't the only way a new one begins
   * (#1797).
   */
  clearReceiverTasks() {
    for (const record of this.receiverTaskRecords.values()) {
      if (record.cleanupTimeoutId != null) {
        clearTimeout(record.cleanupTimeoutId);
      }
      record.abort.abort();
    }
    this.receiverTaskRecords.clear();
  }
  /**
   * Reset the modern listen-stream cluster: the subscribed set, the stream
   * state derived from it, and the reconnect machinery that reports on it.
   *
   * One helper because these move together — the rest of the file derives the
   * stream's `active` from `subscribedResources.size > 0`, so clearing one
   * without the other leaves a combination those readers treat as impossible,
   * and a surviving `modernReconnectAttempts` makes a *new* session's first
   * drop back off as if it were the old session's nth. Both axes are announced:
   * every other mutation of the set dispatches, and so does the stream state.
   *
   * Closes the stream itself, best-effort. `disconnect()` is the obvious caller
   * with a live one, but not the only one: an `onerror` without an `onclose`
   * leaves the transport up, and `connect()` then reuses it — so the reference
   * dropped here can be the last one to a stream still open on the server.
   */
  resetSubscriptionStream() {
    const closing = this.modernSubscription;
    this.subscribedResources.clear();
    this.modernListenGeneration++;
    this.clearModernReconnectTimer();
    this.modernReconnectAttempts = 0;
    this.modernSubscription = null;
    this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
    this.dispatchSubscriptionsChange();
    if (closing) void closeSubscriptionBestEffort(closing);
  }
  /**
   * Drop the session-scoped state a *new* session would misread — anything the
   * next server could be told about, or that would change how we treat its
   * traffic. State that only needs settling on the way out (the peer-request
   * queues, the raw-wire map, the in-flight tool call) is not reset here — the
   * in-flight tool call because `callTool`'s own `finally` releases it once the
   * SDK rejects it, the other two because they are settled *end*-clean, by
   * `disconnect()` and the crash path, so that a consumer handling the
   * `disconnect` event already sees them empty. That is a difference in when,
   * not in whether: the routes out do not cover every route *in* (an `onerror`
   * without an `onclose` runs neither), so `connect()` sweeps those two beside
   * this call as a backstop — see the comment there.
   *
   * `disconnect()` touches all of this on the way out too — two members through
   * the same helpers, three hand-rolled in both places — so a sixth member
   * added here has to be added there as well. One of the three is *paired*
   * rather than duplicated: `modernLogLevel` is re-derived here and blanked
   * there, deliberately (see the comment at that site). It is not the only
   * way a session ends — a crash, or a failed connect the caller retries on
   * this same instance (the auth-recovery path), both leave it behind. Called
   * start-clean from `connect()` so every route in is covered.
   *
   * Each member has a symptom, not just untidiness: a stale `subscribedResources`
   * entry makes the modern `subscribeToResource` early-return, so the user's
   * Subscribe click silently sends nothing to the new server; a stale
   * `cancelledTaskIds` entry mislabels a *new* task sharing the id as
   * `cancelled` rather than `failed`; a stale subscription stream state reads
   * `active` for a set that is now empty, which every reader of it treats as
   * impossible; a receiver-task record is reported to the new server by
   * `tasks/list`; and an un-aborted `taskInputAbortControllers`
   * entry delays a paused poll loop unwinding — both registration sites release
   * in a `finally`, so nothing leaks permanently; the abort just closes the
   * window between the crash and the unwind (#1797).
   */
  resetSessionState() {
    this.clearReceiverTasks();
    this.resetSubscriptionStream();
    this.cancelledTaskIds.clear();
    this.outboundRequestMethods.clear();
    this.lastAnsweredRequestByMethod.clear();
    if (this.malformedListItems.size > 0) {
      this.malformedListItems.clear();
      this.dispatchTypedEvent("malformedListItemsChange", []);
    }
    if (this.excludedTools.length > 0) {
      this.excludedTools = [];
      this.dispatchTypedEvent("excludedToolsChange", []);
    }
    for (const [, controller] of this.taskInputAbortControllers) {
      controller.abort(new Error("Connection ended"));
    }
    this.taskInputAbortControllers.clear();
    this.modernLogLevel = resolveModernLogLevel(this.serverSettings);
  }
  /**
   * Settle and drop the queued peer requests (sampling / elicitation).
   *
   * Every entry is settled before being dropped rather than discarded: an
   * elicitation so an error-path `awaitUrlElicitation` — which blocks
   * `callTool` — doesn't hang forever, and a sample so the *server* gets a
   * response frame for the request we accepted (the transport can outlive a
   * failed attempt; see the `connect()` catch). Callers dispatch the change
   * events themselves:
   * `disconnect()` batches them with its other teardown dispatches, and
   * {@link clearAndAnnouncePendingPeerRequests} emits them immediately
   * everywhere else.
   */
  clearPendingPeerRequests() {
    for (const sample of this.pendingSamples) {
      sample.cancel();
    }
    this.pendingSamples = [];
    for (const elicitation of this.pendingElicitations) {
      elicitation.cancel();
    }
    this.pendingElicitations = [];
    for (const controller of this.activeAppElicitations) {
      controller.abort();
    }
    this.activeAppElicitations.clear();
  }
  /**
   * {@link clearPendingPeerRequests} plus the change events, for every route
   * that drops a queue without going through `disconnect()`: the routes *out*
   * that end a connection some other way, plus the top of `connect()` as a
   * backstop for the one route in that settles nothing (an `onerror` without an
   * `onclose` — see the comment at that call). Named as a category rather than
   * counted, because the set has grown before. (One of the routes out doesn't
   * always end the connection: when a `connect()` failure leaves an auth
   * provider holding the transport open, the caller re-authenticates and
   * retries over it, and what's dropped is the queue left by the attempt that
   * failed.)
   *
   * The events are the load-bearing half: `usePendingClientRequests` tracks its
   * own state off them, so clearing the arrays without dispatching leaves the
   * web pending-request modal on screen for a connection that is gone. Guarded
   * on a non-empty queue so the paths that overlap (a `connect()` failure whose
   * `dropCachedTransport` also fires `onclose`) announce it once.
   */
  clearAndAnnouncePendingPeerRequests() {
    if (this.pendingSamples.length === 0 && this.pendingElicitations.length === 0 && // App-rendered elicitations (#1854) are not in either array — they live in
    // the host's renderer — so an emptiness check that ignores them lets a
    // mid-session close (the `onclose` route, which reaches teardown only
    // through here) leave a modal open for a connection that is gone.
    this.activeAppElicitations.size === 0) {
      return;
    }
    this.clearPendingPeerRequests();
    this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
    this.dispatchTypedEvent(
      "pendingElicitationsChange",
      this.pendingElicitations
    );
  }
  /**
   * Connect to the MCP server
   */
  async connect() {
    if (!this.client) {
      throw new Error("Client not initialized");
    }
    if (this.status === "connected") {
      return;
    }
    this.resetSessionState();
    this.clearAndAnnouncePendingPeerRequests();
    this.rejectPendingRawWireRequests("Connection ended");
    const oauthManager = this.oauthManager;
    if (this.baseTransport && this.isHttpOAuthConfig() && oauthManager && !this.transportHasAuthProvider && !oauthManager.isEnterpriseManaged() && await oauthManager.isOAuthAuthorized()) {
      await this.dropCachedTransport();
    }
    if (!this.baseTransport) {
      const transportOptions = {
        fetchFn: this.fetchFn,
        pipeStderr: this.pipeStderr,
        onStderr: (entry) => {
          this.dispatchStderrLog(entry);
        },
        onFetchRequest: (entry) => {
          this.dispatchFetchRequest({ ...entry, category: "transport" });
        },
        onFetchResponseBody: (id, body) => {
          this.dispatchFetchRequestBodyUpdate(id, body);
        },
        ...this.serverSettings && { settings: this.serverSettings }
      };
      if (this.isHttpOAuthConfig() && oauthManager) {
        const manager = oauthManager;
        transportOptions.onAuthChallengeObserved = (challenge) => {
          manager.noteObservedAuthChallenge(challenge);
        };
        if (oauthManager.isEnterpriseManaged()) {
          await oauthManager.trySilentEnterpriseManagedAuth();
          const provider = await oauthManager.createOAuthProviderForTransport();
          const tokens = await provider.tokens();
          if (!tokens?.access_token) {
            const err = new Error(
              "Unauthorized: EMA resource access token unavailable"
            );
            err.status = 401;
            err.code = 401;
            throw err;
          }
          transportOptions.authProvider = provider;
        } else if (await oauthManager.isOAuthAuthorized()) {
          transportOptions.authProvider = await oauthManager.createOAuthProviderForTransport();
        }
      }
      if (this.directAuthRecovery && this.directAuthRecoveryActive !== false && this.isHttpOAuthConfig() && oauthManager && // No stored tokens means no authProvider (see above), and then a 401 on
      // the era-negotiation probe reaches the SDK as a raw `SdkHttpError`.
      // The probe's classifier ignores the HTTP status — it only looks for a
      // JSON-RPC error body — so it verdicts "not a modern server", and pin
      // ("modern") mode rethrows that as ERA_NEGOTIATION_FAILED with the 401
      // discarded entirely: no status, not even a cause. Intercepting makes
      // the 401 a typed AuthChallengeError, which survives the probe as
      // `data.cause` for `findNestedAuthError` to recover (#1805).
      //
      // WORKAROUND (#1807, upstream modelcontextprotocol/typescript-sdk#2561):
      // remove this clause once the SDK classifies a probe 401/403 as
      // auth-required. `findNestedAuthError` is the permanent fix; the
      // `|| this.probesProtocolEra()` clause below exists only to compensate
      // for that upstream gap and should be deleted with it.
      //
      // Known, accepted side effect of turning intercept on with no stored
      // tokens: `parseAuthChallengeFromResponse` treats 403 as a challenge
      // too, so a probe answered 403 for a *non-auth* reason (a gateway
      // rejecting the unknown `server/discover` method, say) now starts OAuth
      // discovery instead of letting "auto" fall back to the legacy
      // `initialize`. The outcome is a surfaced `oauthError`, not a hang, and
      // it goes away with this clause.
      (transportOptions.authProvider || this.probesProtocolEra())) {
        transportOptions.interceptAuthChallenges = true;
      }
      this.transportHasAuthProvider = !!transportOptions.authProvider;
      const { transport: baseTransport } = this.transportClientFactory(
        this.transportConfig,
        transportOptions
      );
      this.baseTransport = baseTransport;
      if (this.directAuthRecovery) {
        this.directAuthRecoveryActive = !(baseTransport instanceof RemoteClientTransport);
      }
      if (baseTransport instanceof RemoteClientTransport && oauthManager && this.isHttpOAuthConfig()) {
        baseTransport.setAuthRecovery({
          handleAuthChallenge: (challenge, options) => oauthManager.handleAuthChallenge(challenge, options),
          pushAuthState: () => this.pushRemoteAuthState()
        });
        baseTransport.setOnAuthChallenge((challenge) => {
          void this.handleAmbientAuthChallenge(challenge);
        });
      }
      const messageTracking = this.createMessageTrackingCallbacks();
      this.transport = new MessageTrackingTransport(
        baseTransport,
        messageTracking,
        {
          rewriteIncomingResult: (message) => this.rewriteModernTaskResult(message),
          consumeIncomingResponse: (message) => this.consumeRawWireResponse(message)
        }
      );
      this.attachTransportListeners(this.baseTransport);
    }
    if (!this.transport) {
      throw new Error("Transport not initialized");
    }
    try {
      this.status = "connecting";
      this.dispatchTypedEvent("statusChange", this.status);
      this.registerPeerRequestHandlers();
      this.registerPeerNotificationHandlers();
      const connectTimeoutMs = this.serverSettings?.connectionTimeout ?? 0;
      const connectPromise = this.client.connect(this.transport).catch((err) => {
        throw findNestedAuthError(err) ?? err;
      });
      let recoveredByNestedConnect = false;
      const runConnect = async (attempt) => {
        if (attempt > 0) {
          recoveredByNestedConnect = true;
          if (this.status !== "connected") {
            throw new Error(
              "Connection closed during authorization recovery, after re-authorizing successfully"
            );
          }
          return;
        }
        if (connectTimeoutMs > 0) {
          connectPromise.catch(() => {
          });
          let timer;
          const timeoutPromise = new Promise((_, reject) => {
            timer = setTimeout(
              () => reject(
                new Error(
                  `Connection timed out after ${connectTimeoutMs} ms`
                )
              ),
              connectTimeoutMs
            );
          });
          try {
            await Promise.race([connectPromise, timeoutPromise]);
          } finally {
            if (timer) clearTimeout(timer);
          }
        } else {
          await connectPromise;
        }
      };
      try {
        await this.invokeMcpClient(runConnect);
      } catch (err) {
        if (connectTimeoutMs > 0) {
          await this.disconnect().catch(() => {
          });
        }
        throw err;
      }
      if (recoveredByNestedConnect) {
        return;
      }
      this.status = "connected";
      this.dispatchTypedEvent("statusChange", this.status);
      await this.fetchServerInfo();
      if (this.initialLoggingLevel && this.capabilities?.logging && this.protocolEra !== "modern") {
        await this.client.setLoggingLevel(
          this.initialLoggingLevel,
          this.getRequestOptions()
        );
      }
      if (this.client) {
        if (this.listChangedNotifications.tools && this.capabilities?.tools?.listChanged) {
          this.client.setNotificationHandler(
            "notifications/tools/list_changed",
            async () => {
              this.dispatchTypedEvent("toolsListChanged");
            }
          );
        }
        if (this.listChangedNotifications.resources && this.capabilities?.resources?.listChanged) {
          this.client.setNotificationHandler(
            "notifications/resources/list_changed",
            async () => {
              this.dispatchTypedEvent("resourcesListChanged");
              this.dispatchTypedEvent("resourceTemplatesListChanged");
            }
          );
        }
        if (this.listChangedNotifications.prompts && this.capabilities?.prompts?.listChanged) {
          this.client.setNotificationHandler(
            "notifications/prompts/list_changed",
            async () => {
              this.dispatchTypedEvent("promptsListChanged");
            }
          );
        }
        if (this.capabilities?.tasks) {
          this.client.setNotificationHandler(
            "notifications/tasks/list_changed",
            { params: TasksListChangedNotificationSchema.shape.params },
            async () => {
              this.dispatchTypedEvent("tasksListChanged");
            }
          );
          this.client.setNotificationHandler(
            "notifications/tasks/status",
            { params: TaskStatusNotificationSchema.shape.params },
            async (params) => {
              const task = params;
              this.dispatchTypedEvent("taskStatusChange", {
                taskId: task.taskId,
                task
              });
            }
          );
        }
        if (this.capabilities?.resources?.subscribe === true) {
          this.client.setNotificationHandler(
            "notifications/resources/updated",
            async (notification) => {
              const uri = notification.params.uri;
              if (this.subscribedResources.has(uri)) {
                this.dispatchTypedEvent("resourceUpdated", { uri });
              }
            }
          );
        }
        if (this.urlElicitationCapabilityAdvertised) {
          this.client.setNotificationHandler(
            "notifications/elicitation/complete",
            async (notification) => {
              const { elicitationId } = notification.params;
              const pending = this.pendingElicitations.find(
                (e) => e.request.params?.mode === "url" && e.request.params?.elicitationId === elicitationId
              );
              if (pending) {
                pending.completeIfPending();
              }
            }
          );
        }
      }
      await this.openModernListenStreamOnConnect();
      if (this.status === "connected" && !this.disconnecting) {
        this.dispatchTypedEvent("connect");
      }
    } catch (error) {
      if (!isConnectAuthRecoveryError(error)) {
        this.status = "error";
        this.dispatchTypedEvent("statusChange", this.status);
      }
      if (this.baseTransport && !this.transportHasAuthProvider) {
        await this.dropCachedTransport();
      }
      this.clearAndAnnouncePendingPeerRequests();
      throw error;
    }
  }
  /**
   * Disconnect from the MCP server.
   * @param safeDisconnectTimeout If > 0, poll every 10ms until SDK _responseHandlers is empty or this many ms have elapsed, then close. Default 0 = close immediately.
   */
  async disconnect(safeDisconnectTimeout = 0) {
    this.disconnecting = true;
    try {
      if (this.client) {
        if (safeDisconnectTimeout > 0) {
          const protocol = this.client;
          const handlers = protocol._responseHandlers;
          const deadline = Date.now() + safeDisconnectTimeout;
          while (handlers?.size !== void 0 && handlers.size > 0 && Date.now() < deadline) {
            await new Promise((r) => setTimeout(r, 10));
          }
        }
        try {
          await this.client.close();
        } catch {
        }
      }
    } finally {
      this.disconnecting = false;
    }
    this.baseTransport = null;
    this.transport = null;
    this.transportHasAuthProvider = false;
    this.clearPendingPeerRequests();
    if (this.status !== "disconnected") {
      this.status = "disconnected";
      this.dispatchTypedEvent("statusChange", this.status);
      this.dispatchTypedEvent("disconnect");
    }
    this.resetSubscriptionStream();
    this.cancelledTaskIds.clear();
    this.rejectPendingRawWireRequests("Disconnected");
    for (const [, controller] of this.taskInputAbortControllers) {
      controller.abort(new Error("Disconnected"));
    }
    this.taskInputAbortControllers.clear();
    this.activeToolCallAbortController?.abort("Disconnected");
    this.activeToolCallAbortController = void 0;
    this.clearReceiverTasks();
    this.appRendererClientProxy = null;
    this.capabilities = void 0;
    this.serverInfo = void 0;
    this.instructions = void 0;
    this.protocolVersion = void 0;
    this.protocolEra = void 0;
    this.discoverResult = void 0;
    this.excludedTools = [];
    this.malformedListItems.clear();
    this.modernLogLevel = void 0;
    this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
    this.dispatchTypedEvent(
      "pendingElicitationsChange",
      this.pendingElicitations
    );
    this.dispatchTypedEvent("capabilitiesChange", this.capabilities);
    this.dispatchTypedEvent("serverInfoChange", this.serverInfo);
    this.dispatchTypedEvent("instructionsChange", this.instructions);
    this.dispatchTypedEvent("protocolVersionChange", this.protocolVersion);
    this.dispatchTypedEvent("protocolEraChange", this.protocolEra);
    this.dispatchTypedEvent("discoverResultChange", this.discoverResult);
    this.dispatchTypedEvent("excludedToolsChange", this.excludedTools);
    this.dispatchTypedEvent("malformedListItemsChange", []);
  }
  /**
   * Returns a client proxy for use by AppRenderer / @mcp-ui. Delegates to the
   * internal MCP Client. Returns null when not connected. Use this instead of
   * accessing the raw client so behavior can be adapted here later if needed.
   */
  getAppRendererClient() {
    if (!this.client || this.status !== "connected") return null;
    if (this.appRendererClientProxy !== null)
      return this.appRendererClientProxy;
    const target = this.client;
    this.appRendererClientProxy = new Proxy(this.client, {
      get(proxyTarget, prop, receiver) {
        const value = Reflect.get(proxyTarget, prop, receiver);
        if (prop === "setNotificationHandler" && typeof value === "function") {
          return (schemaOrMethod, ...rest) => {
            const method = typeof schemaOrMethod === "string" ? schemaOrMethod : notificationMethodFromSchema(schemaOrMethod) ?? schemaOrMethod;
            return value.apply(target, [
              method,
              ...rest
            ]);
          };
        }
        return value;
      }
    });
    return this.appRendererClientProxy;
  }
  /**
   * Send a ping request to the server. Resolves when the server responds.
   */
  async ping() {
    if (!this.client) {
      throw new Error("Client not initialized");
    }
    await this.client.request(
      { method: "ping" },
      EmptyResultSchema,
      this.getRequestOptions()
    );
  }
  /**
   * Get the current connection status
   */
  getStatus() {
    return this.status;
  }
  /**
   * Get the MCP server configuration used to create this client
   */
  getTransportConfig() {
    return this.transportConfig;
  }
  /**
   * Get the server type (stdio, sse, or streamable-http)
   */
  getServerType() {
    return getServerType(this.transportConfig);
  }
  /**
   * Get task capabilities from server
   * @returns Task capabilities or undefined if not supported
   */
  getTaskCapabilities() {
    if (!this.capabilities?.tasks) {
      return void 0;
    }
    return {
      list: !!this.capabilities.tasks.list,
      cancel: !!this.capabilities.tasks.cancel
    };
  }
  /**
   * True when the connection is modern (2026-07-28) AND the server advertised
   * the `io.modelcontextprotocol/tasks` extension (SEP-2663) in its
   * `server/discover` capabilities. Modern task methods (`tasks/get`,
   * `tasks/update`, `tasks/cancel`) and the "unsolicited task handle" behavior
   * are gated on this — legacy servers use `capabilities.tasks` and the
   * `tasks/list`-backed flow instead. Exposed so the Tasks tab and the modern
   * task store gate on the extension rather than the legacy capability.
   */
  isTasksExtensionNegotiated() {
    return this.isModernEra() && this.capabilities?.extensions?.[TASKS_EXTENSION_KEY] !== void 0;
  }
  /**
   * Build the full modern (2026-07-28) per-request envelope for a RAW tasks/*
   * request. The SDK's codec normally stamps this envelope, but raw requests
   * bypass the codec, and the modern server rejects a request whose
   * `MCP-Protocol-Version` header names 2026-07-28 but omits the required
   * envelope `_meta` keys (`protocolVersion`, `clientInfo`, plus
   * `clientCapabilities` carrying the tasks extension). We reproduce it here.
   */
  withModernTaskEnvelope(params) {
    const clientCapabilities = {
      ...this.clientCapabilities,
      // Force-stamp the tasks extension regardless of what the client
      // advertised at construction: the raw `tasks/*` channel requires it, and
      // a user may disable general tasks advertisement via `advertisedExtensions`
      // (#1738). So this stamp is load-bearing, not a redundant re-add.
      extensions: {
        ...this.clientCapabilities.extensions,
        [TASKS_EXTENSION_KEY]: {}
      }
    };
    return this.withModernEnvelope(params, clientCapabilities);
  }
  /**
   * Stamp the `_meta` envelope every raw-wire request needs on the modern leg:
   * the negotiated protocol version, the client identity, and the client
   * capabilities.
   *
   * Split out of {@link withModernTaskEnvelope} so the list-salvage re-walk
   * (#1909) can reuse it WITHOUT that method's force-stamped tasks extension —
   * a `tools/list` has no business claiming an extension the user may have
   * deliberately turned off (#1738).
   *
   * The NEGOTIATED protocol version is used so the envelope agrees with the
   * `MCP-Protocol-Version` header the transport stamps from the same source — a
   * future modern-family revision would negotiate a different string, and the
   * two must not disagree. The raw channel only runs on a connected modern
   * session, so this is always set; the constant is a defensive fallback.
   */
  withModernEnvelope(params, clientCapabilities = this.clientCapabilities) {
    const existingMeta = params._meta ?? {};
    const protocolVersion = this.getProtocolVersion() ?? MODERN_PROTOCOL_VERSION2;
    return {
      ...params,
      _meta: {
        ...existingMeta,
        [PROTOCOL_VERSION_META_KEY]: protocolVersion,
        [CLIENT_INFO_META_KEY]: this.clientInfo,
        [CLIENT_CAPABILITIES_META_KEY]: clientCapabilities
      }
    };
  }
  /**
   * Transport-level rewrite of a modern (SEP-2663) `CreateTaskResult`
   * (`resultType: "task"`) — the one task frame the SDK v2 codec rejects (tasks
   * were removed, so the codec knows only `complete`/`input_required`). The true
   * frame is already logged by `trackResponse`; here we hand the SDK a benign
   * `CallToolResult` that carries the real `DetailedTask` under
   * {@link MODERN_TASK_HANDLE_META}, where {@link pollTaskToolCall} reads it to
   * drive the poll. Any other message passes through untouched.
   */
  rewriteModernTaskResult(message) {
    if (!isModernCreateTaskResult(message.result)) {
      return message;
    }
    const task = message.result;
    return {
      ...message,
      result: {
        resultType: "complete",
        content: [{ type: "text", text: `Modern task ${task.taskId} created` }],
        _meta: { [MODERN_TASK_HANDLE_META]: task }
      }
    };
  }
  /**
   * Send an extension method the SDK v2 era gate refuses to route — the modern
   * `tasks/get` / `tasks/update` / `tasks/cancel`, which are spec-method names
   * absent from the 2026-07-28 era, so `client.request` throws
   * `MethodNotSupportedByProtocolVersion` before anything reaches the wire.
   *
   * We mint a string JSON-RPC id (the SDK only mints numeric ids, so ours never
   * collide), send the raw frame straight through the transport (which still
   * logs it via `trackRequest`, so the Protocol/Network tabs see it), and await
   * the matching response — captured and consumed by the transport's
   * consume-response hook so it never confuses the SDK Client. The response is
   * validated with the caller's explicit schema.
   */
  async rawWireRequest(method, params, resultSchema) {
    const transport = this.transport;
    if (!transport) {
      throw new Error("Client is not connected");
    }
    const id = `inspector-ext-${this.rawWireRequestCounter += 1}`;
    const message = {
      jsonrpc: "2.0",
      id,
      method,
      params
    };
    const timeoutMs = this.requestTimeout ?? 3e4;
    const raw = await new Promise((resolve7, reject) => {
      const timer = setTimeout(() => {
        this.pendingRawWireRequests.delete(id);
        reject(
          new Error(`Raw request "${method}" timed out after ${timeoutMs} ms`)
        );
      }, timeoutMs);
      this.pendingRawWireRequests.set(id, { resolve: resolve7, reject, timer });
      transport.send(message).catch((err) => {
        const pending = this.pendingRawWireRequests.get(id);
        if (pending) {
          clearTimeout(pending.timer);
          this.pendingRawWireRequests.delete(id);
        }
        reject(err instanceof Error ? err : new Error(String(err)));
      });
    });
    return resultSchema.parse(raw);
  }
  /**
   * Transport consume-response hook: resolve/reject a pending
   * {@link rawWireRequest} when its response arrives, and report it as consumed
   * (so the transport does not forward it to the SDK Client, which never sent
   * it). Returns false for any id we don't own, leaving normal SDK traffic
   * untouched.
   */
  consumeRawWireResponse(message) {
    const id = String(message.id);
    const pending = this.pendingRawWireRequests.get(id);
    if (!pending) {
      return false;
    }
    this.pendingRawWireRequests.delete(id);
    clearTimeout(pending.timer);
    if ("error" in message) {
      const err = message.error;
      pending.reject(new Error(err?.message ?? `Request ${id} failed`));
    } else {
      pending.resolve(message.result);
    }
    return true;
  }
  /**
   * Reject and clear all pending raw-wire requests — on every route out that
   * can hold one, and at the top of `connect()` for the route in that settles
   * nothing (see the comment there).
   */
  rejectPendingRawWireRequests(reason) {
    for (const [, pending] of this.pendingRawWireRequests) {
      clearTimeout(pending.timer);
      pending.reject(new Error(reason));
    }
    this.pendingRawWireRequests.clear();
  }
  /**
   * Get requestor task status by taskId (tasks we created on the server)
   * @param taskId Task identifier
   * @returns Task status
   */
  async getRequestorTask(taskId) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    if (this.isTasksExtensionNegotiated()) {
      const modern = await this.rawWireRequest(
        "tasks/get",
        this.withModernTaskEnvelope({ taskId }),
        ModernGetTaskResultSchema
      );
      const task2 = normalizeModernTask(modern);
      this.dispatchTypedEvent("requestorTaskUpdated", {
        taskId: task2.taskId,
        task: task2
      });
      return task2;
    }
    const task = await this.client.request(
      { method: "tasks/get", params: { taskId } },
      GetTaskResultSchema,
      this.getRequestOptions()
    );
    this.dispatchTypedEvent("requestorTaskUpdated", {
      taskId: task.taskId,
      task
    });
    return task;
  }
  /**
   * Get requestor task result by taskId (tasks we created on the server)
   * @param taskId Task identifier
   * @returns Task result
   */
  async getRequestorTaskResult(taskId) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    return await this.client.request(
      { method: "tasks/result", params: { taskId } },
      CallToolResultSchema,
      this.getRequestOptions()
    );
  }
  /**
   * Cancel a running requestor task (task we created on the server)
   * @param taskId Task identifier
   * @returns Cancel result
   */
  async cancelRequestorTask(taskId) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    this.cancelledTaskIds.add(taskId);
    const inputAbort = this.taskInputAbortControllers.get(taskId);
    if (inputAbort) {
      inputAbort.abort(new Error(`Task ${taskId} cancelled by user`));
    }
    if (this.isTasksExtensionNegotiated()) {
      await this.rawWireRequest(
        "tasks/cancel",
        this.withModernTaskEnvelope({ taskId }),
        ModernCancelTaskResultSchema
      );
    } else {
      await this.client.request(
        { method: "tasks/cancel", params: { taskId } },
        CancelTaskResultSchema,
        this.getRequestOptions()
      );
    }
    this.dispatchTypedEvent("taskCancelled", { taskId });
  }
  /**
   * Fulfil the outstanding `inputRequests` of a modern (SEP-2663)
   * `input_required` task by sending `tasks/update` with the collected
   * `inputResponses`. The server acks with an empty result; the task's
   * observable status advances on a subsequent `tasks/get` poll (the update is
   * eventually consistent). Modern-only — legacy tasks surface input through the
   * server→client request channel, not `tasks/update`.
   *
   * @param taskId Task identifier
   * @param inputResponses Responses keyed by the server's `inputRequests` ids
   */
  async updateRequestorTask(taskId, inputResponses) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    await this.rawWireRequest(
      "tasks/update",
      this.withModernTaskEnvelope({ taskId, inputResponses }),
      ModernUpdateTaskResultSchema
    );
  }
  /**
   * Cancel the in-flight ordinary (non-task) tool call started by
   * {@link callTool}. Aborting its request makes the SDK send a
   * `notifications/cancelled` to the server (the MCP cancellation flow) and
   * reject the pending call, which `callTool` surfaces as a
   * {@link ToolCallCancelledError}.
   *
   * Task-augmented calls have a server-side task and are cancelled via
   * {@link cancelRequestorTask} instead — this is a no-op for them (and whenever
   * no ordinary call is in flight).
   *
   * @returns `true` if a call was in flight to cancel, `false` otherwise.
   */
  cancelToolCall() {
    const controller = this.activeToolCallAbortController;
    if (!controller) {
      return false;
    }
    this.activeToolCallAbortController = void 0;
    controller.abort(TOOL_CALL_CANCELLED_REASON);
    return true;
  }
  /**
   * List all requestor tasks with optional pagination (tasks we created on the server)
   * @param cursor Optional pagination cursor
   * @returns List of tasks with optional next cursor
   */
  async listRequestorTasks(cursor) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const result = await this.client.request(
      { method: "tasks/list", params: cursor ? { cursor } : {} },
      ListTasksResultSchema,
      this.getRequestOptions()
    );
    return { tasks: result.tasks, nextCursor: result.nextCursor };
  }
  /**
   * Surface a sampling request through the pending-request UI and resolve with
   * the user's answer. Shared by the inbound `sampling/createMessage` handler
   * (legacy server→client request) and the MRTR driver (a modern
   * `input_required` round embeds the request in a tool-call result). `origin`
   * tags which of the two so the UI can show era-accurate semantics.
   *
   * Sampling has no decline/cancel action (unlike elicitation): the panel
   * either sends a `CreateMessageResult` (resolve — echoed to the server) or
   * Rejects (reject — which fails the tool call). A `signal` abort likewise
   * rejects so the MRTR driver can abort the originating call. `signal` is only
   * passed by the MRTR driver (the tool call's abort signal) — while the driver
   * awaits an answer there is no in-flight SDK request to carry it.
   */
  enqueuePendingSample(request, origin, signal) {
    return new Promise((resolvePromise, rejectPromise) => {
      const sample = new SamplingCreateMessage(
        request,
        resolvePromise,
        rejectPromise,
        (id) => this.removePendingSample(id),
        origin
      );
      this.addPendingSample(sample);
      this.wirePendingAbort(signal, () => {
        this.removePendingSample(sample.id);
        rejectPromise(createPendingAbortError());
      });
    });
  }
  /**
   * Surface an elicitation request through the pending-request UI and resolve
   * with the user's answer. Shared by the inbound `elicitation/create` handler
   * and the MRTR driver — see {@link enqueuePendingSample} for the `origin` /
   * `signal` semantics. A declined/cancelled elicitation resolves with the
   * corresponding `ElicitResult` (echoed to the server on retry); only a
   * genuine failure or a `signal` abort rejects.
   */
  async enqueuePendingElicitation(request, origin, signal) {
    const appResult = await this.tryAppElicitation(request, signal);
    if (appResult) return appResult;
    return new Promise((resolvePromise, rejectPromise) => {
      const elicitation = new ElicitationCreateMessage(
        request,
        resolvePromise,
        (id) => this.removePendingElicitation(id),
        rejectPromise,
        origin
      );
      this.addPendingElicitation(elicitation);
      this.wirePendingAbort(signal, () => {
        this.removePendingElicitation(elicitation.id);
        rejectPromise(createPendingAbortError());
      });
    });
  }
  /**
   * Attempt to resolve an `elicitation/create` request by rendering the MCP App
   * the server attached to it (#1854), returning the app's standard
   * `ElicitResult`.
   *
   * Returns `null` for "not app-rendered — use the native UI", which covers
   * every negotiation gate and every failure mode the contract lists: either
   * peer did not negotiate the capability, the metadata is absent or unusable,
   * the mode is not `form`, the renderer failed (resource read, sandbox/bridge
   * init, missing app capability, timeout), or the app returned something that
   * is not a valid result for this request. An explicit `decline` or `cancel`
   * is a *completed* elicitation and is returned, not fallen back on.
   *
   * The one case that does not fall back is an abort of the originating
   * request: the caller is cancelling the whole elicitation, so re-opening it
   * in the native queue would resurrect work the user just abandoned.
   */
  async tryAppElicitation(request, signal) {
    const renderer = this.appElicitationRenderer;
    if (!renderer) return null;
    if (!supportsAppElicitation(this.clientCapabilities, this.capabilities)) {
      return null;
    }
    if (!isFormElicitation(request.params)) return null;
    let resourceUri;
    try {
      resourceUri = getElicitationUiResourceUri(request.params);
    } catch (error) {
      this.logger.warn(
        { error },
        "Elicitation carried unusable _meta.ui.resourceUri; using the native elicitation UI"
      );
      return null;
    }
    if (!resourceUri) return null;
    if (signal?.aborted) throw createPendingAbortError();
    const controller = new AbortController();
    const forwardAbort = () => controller.abort(signal?.reason);
    signal?.addEventListener("abort", forwardAbort, { once: true });
    this.activeAppElicitations.add(controller);
    try {
      const result = await renderer({
        requestId: `${this.appElicitationPrefix}-${++this.appElicitationSeq}`,
        resourceUri,
        params: request.params,
        signal: controller.signal
      });
      this.outputValidator ??= new AjvJsonSchemaValidator();
      const invalid = validateAppElicitResult(
        this.outputValidator,
        request.params,
        result
      );
      if (invalid) {
        this.logger.warn(
          { resourceUri, reason: invalid },
          "App-rendered elicitation returned an invalid result; using the native elicitation UI"
        );
        return null;
      }
      return result;
    } catch (error) {
      if (controller.signal.aborted) throw createPendingAbortError();
      this.logger.warn(
        { error, resourceUri },
        "App-rendered elicitation failed; using the native elicitation UI"
      );
      return null;
    } finally {
      this.activeAppElicitations.delete(controller);
      signal?.removeEventListener("abort", forwardAbort);
    }
  }
  /**
   * Reject a still-pending request when `signal` aborts (e.g. the user cancels
   * the tool call while its MRTR round is awaiting an answer). No-op when
   * `signal` is absent — the legacy inbound-handler path passes none.
   */
  wirePendingAbort(signal, onAbort) {
    if (!signal) return;
    if (signal.aborted) {
      onAbort();
      return;
    }
    signal.addEventListener("abort", onAbort, { once: true });
  }
  /**
   * Drive a multi-round-trip request (SEP-2322 "MRTR") for one of the modern
   * multi-round-trip methods (`tools/call`, `prompts/get`, `resources/read`).
   *
   * The client is constructed with `inputRequired: { autoFulfill: false }`, so
   * an `input_required` result is handed back here (via `allowInputRequired`)
   * instead of the SDK silently fulfilling and retrying. We surface each
   * embedded request through the SAME pending-request UI the legacy
   * server→client path uses (`fulfilInputRequests`), gather the bare results,
   * and retry the ORIGINAL request with `inputResponses` + the echoed
   * `requestState` on a fresh JSON-RPC id (`client.request` mints it). The loop
   * runs until the server returns a complete result, bounded by
   * {@link MRTR_MAX_ROUNDS}.
   *
   * On legacy connections a server never returns `input_required`, so the first
   * response is always complete and this is a single `client.request` call.
   */
  async requestWithInputRequired(method, params, resultSchema, requestOptions) {
    const client = this.client;
    if (!client) {
      throw new Error("Client is not connected");
    }
    const wrapped = withInputRequired(resultSchema);
    const signal = requestOptions.signal;
    let round = 0;
    let nextParams = params;
    while (true) {
      const outcome = await client.request(
        { method, params: nextParams },
        wrapped,
        {
          ...requestOptions,
          allowInputRequired: true
        }
      );
      if (!isInputRequiredResult(outcome)) {
        return outcome;
      }
      round += 1;
      if (round > _InspectorClient.MRTR_MAX_ROUNDS) {
        throw new Error(
          `Multi-round-trip "${method}" exceeded ${_InspectorClient.MRTR_MAX_ROUNDS} input_required rounds without completing.`
        );
      }
      const inputResponses = await this.fulfilInputRequests(
        outcome.inputRequests,
        signal
      );
      nextParams = {
        ...params,
        ...inputResponses ? { inputResponses } : {},
        ...outcome.requestState !== void 0 ? { requestState: outcome.requestState } : {}
      };
    }
  }
  /**
   * Fulfil the embedded requests of one MRTR `input_required` round, keyed by
   * the server-assigned identifiers echoed back in `inputResponses`. Sequential
   * (one modal at a time) to keep the single-slot pending UI coherent. Returns
   * `undefined` for a `requestState`-only round (no embedded requests); an empty
   * `inputRequests` map yields an empty `{}`, which the retry echoes harmlessly.
   */
  async fulfilInputRequests(inputRequests, signal, origin = "input-required") {
    if (!inputRequests) return void 0;
    const responses = {};
    for (const [key, embedded] of Object.entries(inputRequests)) {
      responses[key] = await this.fulfilEmbeddedInputRequest(
        embedded,
        signal,
        origin
      );
    }
    return responses;
  }
  /**
   * Fulfil a single embedded input request. `roots/list` is auto-answered from
   * the configured roots (consistent with the legacy `roots/list` handler — no
   * pending UX); `elicitation/create` and `sampling/createMessage` surface
   * through the pending-request UI tagged with `origin`. `origin` distinguishes
   * an MRTR round (`"input-required"`, answer echoed as a retry) from a modern
   * task round (`"task-input-required"`, answer submitted via `tasks/update`).
   */
  async fulfilEmbeddedInputRequest(request, signal, origin = "input-required") {
    switch (request.method) {
      case "roots/list":
        return { roots: this.roots ?? [] };
      case "elicitation/create":
        return this.enqueuePendingElicitation(request, origin, signal);
      case "sampling/createMessage":
        return this.enqueuePendingSample(request, origin, signal);
      /* v8 ignore next 6 -- defensive: an SDK server rejects an unknown embedded
         method before it reaches the wire, so this only guards against a
         non-conformant hand-rolled server; not reproducible against the
         SDK-based test servers. */
      default:
        throw new Error(
          `Unsupported embedded input_required request method: ${request.method}`
        );
    }
  }
  /**
   * Get all pending sampling requests
   */
  getPendingSamples() {
    return [...this.pendingSamples];
  }
  /**
   * Add a pending sampling request
   */
  addPendingSample(sample) {
    this.pendingSamples.push(sample);
    this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
    this.dispatchTypedEvent("newPendingSample", sample);
  }
  /**
   * Remove a pending sampling request by ID
   */
  removePendingSample(id) {
    const index = this.pendingSamples.findIndex((s) => s.id === id);
    if (index !== -1) {
      this.pendingSamples.splice(index, 1);
      this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
    }
  }
  /**
   * Get all pending elicitation requests
   */
  getPendingElicitations() {
    return [...this.pendingElicitations];
  }
  /**
   * Add a pending elicitation request
   */
  addPendingElicitation(elicitation) {
    this.pendingElicitations.push(elicitation);
    this.dispatchTypedEvent(
      "pendingElicitationsChange",
      this.pendingElicitations
    );
    this.dispatchTypedEvent("newPendingElicitation", elicitation);
  }
  /**
   * Remove a pending elicitation request by ID
   */
  removePendingElicitation(id) {
    const index = this.pendingElicitations.findIndex((e) => e.id === id);
    if (index !== -1) {
      this.pendingElicitations.splice(index, 1);
      this.dispatchTypedEvent(
        "pendingElicitationsChange",
        this.pendingElicitations
      );
    }
  }
  /**
   * Get server capabilities
   */
  getCapabilities() {
    return this.capabilities;
  }
  /**
   * Get the capabilities this client advertises to the server. Snapshotted
   * from the initialize-time build in setupClient(); does not reflect later
   * registerCapabilities() calls on the underlying SDK Client.
   */
  getClientCapabilities() {
    return this.clientCapabilities;
  }
  /**
   * Get server info (name, version)
   */
  getServerInfo() {
    return this.serverInfo;
  }
  /**
   * Get server instructions
   */
  getInstructions() {
    return this.instructions;
  }
  /**
   * Get the MCP protocol version negotiated with the server. On a legacy
   * connect this is the version from the initialize handshake (e.g.
   * "2025-06-18"); on a modern connect it's the negotiated modern revision.
   * Undefined when not connected.
   */
  getProtocolVersion() {
    return this.protocolVersion;
  }
  /**
   * The protocol era negotiated with the server (SEP §7.8): `"legacy"` for the
   * 2025-11-25 initialize handshake, `"modern"` for the 2026-era sessionless
   * model. Populated for every era once connected — including a plain legacy
   * (`mode: "legacy"`) connect, which the SDK reports as `"legacy"`. Undefined
   * only when not connected (before connect / after disconnect). (#1626)
   */
  getProtocolEra() {
    return this.protocolEra;
  }
  /**
   * The `server/discover` result captured on a probed (`"auto"`) or pinned
   * (`"modern"`) connect — server identity, capabilities, and supported
   * versions learned up front without an initialize handshake. Undefined when
   * not connected or on a plain legacy connect. Persistable and feedable back
   * to the SDK as `connect(transport, { prior })` for a zero-round-trip
   * reconnect. (#1626)
   */
  getDiscoverResult() {
    return this.discoverResult;
  }
  /**
   * The per-server settings this client was constructed with (headers,
   * timeouts, roots, OAuth, the auto-refresh-on-list-changed option, etc.).
   * Read by the managed list state to decide whether to auto-refresh on
   * `list_changed` notifications (#1402).
   */
  getServerSettings() {
    return this.serverSettings;
  }
  /**
   * Replace the in-memory per-server settings on a live client. Lets a settings
   * edit (e.g. toggling auto-refresh-on-list-changed) take effect on the
   * current connection without a reconnect — the managed list state reads
   * `getServerSettings()` at notification time, so the next `list_changed`
   * notification honors the new value (#1444). Connection-time inputs
   * (transport, OAuth, timeouts) still only apply on the next connect.
   */
  setServerSettings(settings) {
    this.serverSettings = settings;
  }
  /**
   * Set the logging level for the MCP server (legacy era only).
   *
   * On legacy servers logging is session-scoped: one `logging/setLevel` request
   * sets the level for all subsequent `notifications/message`. Modern servers
   * removed this method — use {@link setModernLogLevel} there instead.
   *
   * @param level Logging level to set
   * @throws Error if client is not connected or server doesn't support logging
   */
  async setLoggingLevel(level) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    if (!this.capabilities?.logging) {
      throw new Error("Server does not support logging");
    }
    await this.client.setLoggingLevel(level, this.getRequestOptions());
  }
  /**
   * Set (or clear) the modern-era per-request log level (#1629).
   *
   * On 2026-07-28 servers `logging/setLevel` is gone and there is no
   * session-scoped level: the client opts into logs per request by stamping the
   * `io.modelcontextprotocol/logLevel` `_meta` key, and the server MUST NOT emit
   * `notifications/message` for requests that omit it. This stores the level so
   * {@link mergeMeta} stamps it on every subsequent request; pass `undefined` to
   * stop opting in (logs then stay silently absent). Takes effect immediately —
   * no request is sent, and it is a no-op on the wire until the next request.
   *
   * @param level Level to stamp on every request, or `undefined` to opt out.
   */
  setModernLogLevel(level) {
    this.modernLogLevel = level;
  }
  /** The modern-era per-request log level, or `undefined` when not opted in. */
  getModernLogLevel() {
    return this.modernLogLevel;
  }
  /**
   * Fetch a single page of tools without updating the client's internal list.
   */
  async listTools(cursor, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const effectiveMeta = this.mergeMeta(metadata);
    const params = {
      ...effectiveMeta ? { _meta: effectiveMeta } : {},
      // `!== undefined`, not truthiness: "" is a valid cursor. Dropping it asks
      // for page one again — and `listToolsForScan` pairs this call with a
      // fallback that already preserves it, so the two legs of one scan page
      // would otherwise disagree about which page they fetched.
      ...cursor !== void 0 && { cursor }
    };
    const response = await this.invokeMcpClient(
      () => this.client.request(
        { method: "tools/list", params },
        ListToolsResultSchema,
        this.getRequestOptions(this.progressTokenOf(metadata))
      )
    );
    const tools = [...response.tools || []];
    return { tools, nextCursor: response.nextCursor };
  }
  /**
   * Aggregate ALL pages of `tools/list` via the SDK's high-level
   * `client.listTools()` — the cache-aware verb. Unlike the single-page
   * {@link listTools} (raw `client.request`, for pagination debugging), this is
   * the path the managed tool list uses on refresh: the SDK walks every page,
   * applies the SEP-2243 `x-mcp-header` exclusion, and consults/writes its
   * response cache. `cacheMode` selects the disposition (`'use'` serves a
   * still-fresh cached list without a round trip; `'refresh'` always fetches
   * and re-stores; `'bypass'` fetches without touching the cache) — only
   * meaningful on modern servers that send `ttlMs` hints; a no-op on legacy
   * (nothing is cached, so every call hits the wire regardless).
   */
  async listAllTools(options) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const tools = await this.listAllSalvaging({
      method: "tools/list",
      itemsKey: "tools",
      resultSchema: ListToolsResultSchema,
      // Era-aware: the neutral `ToolSchema` is more permissive than the
      // negotiated era's wire schema — see `toolItemSchemaForEra`.
      itemSchema: toolItemSchemaForEra(this.isModernEra()),
      metadata: options?.metadata,
      finalize: (salvaged) => this.excludeInvalidXMcpHeaderTools(salvaged),
      aggregate: async () => [
        ...(await this.client.listTools(
          this.aggregateListParams(options?.metadata),
          this.getCacheableRequestOptions(options?.cacheMode)
        )).tools
      ]
    });
    await this.refreshExcludedTools(options?.metadata).catch((err) => {
      this.logger.warn(
        { err },
        "Excluded-tools walk failed; the SEP-2243 excluded list may be incomplete"
      );
    });
    return { tools };
  }
  /**
   * Whether this connection excludes tools with invalid `x-mcp-header`
   * annotations from `tools/list`, matching the SDK's gate: only the modern
   * (2026-07-28) era on a non-stdio (Streamable HTTP / SSE) transport. Legacy
   * and stdio keep such tools in the list, so there is nothing to surface.
   */
  excludesInvalidXMcpHeaderTools() {
    return this.isModernEra() && this.getServerType() !== "stdio";
  }
  /**
   * Mark the response that most recently answered `method` as rejected by the
   * client, so its Protocol entry shows why instead of rendering as a clean
   * success (#1953).
   *
   * The SDK gives no request id with a decode failure — `SdkError` carries the
   * method and nothing else — so the id is recovered by correlation: the last
   * response received for that method. That is exact rather than approximate,
   * because the SDK rejects synchronously while decoding the response (inside
   * the transport's `onmessage`) and the caller's `catch` runs in the very next
   * microtask. Delivering another response for the same method in that window
   * would take a macrotask (a socket read), which cannot interleave there.
   *
   * A no-op when nothing has answered `method` this session.
   */
  markResponseRejected(method, reason) {
    const id = this.lastAnsweredRequestByMethod.get(method);
    if (id === void 0) return;
    this.dispatchTypedEvent("responseRejected", { id, reason });
  }
  /**
   * Put the method's response correlation back where it was before a salvage
   * re-walk, so a caller that goes on to mark the failure still marks the
   * ORIGINAL rejected response.
   *
   * The re-walk answers the same method, so on any rethrow path the map points
   * at a lenient page that succeeded. `ManagedListState.refresh()` catches the
   * rethrown error and calls `markResponseRejected(method, ...)`, which would
   * then stamp "Rejected by the Inspector" onto that page and leave the real
   * one rendering clean — the same misattribution the capture inside
   * `salvageList` exists to prevent, just one frame further out. Deleting when
   * nothing had answered before makes the outer mark a no-op rather than a lie.
   */
  restoreResponseCorrelation(method, id) {
    if (id === void 0) this.lastAnsweredRequestByMethod.delete(method);
    else this.lastAnsweredRequestByMethod.set(method, id);
  }
  /** Every entry dropped from a list result as malformed, across methods (#1909). */
  getMalformedListItems() {
    return [...this.malformedListItems.values()].flat();
  }
  /**
   * Record (or clear) the malformed entries for one list method and notify.
   *
   * A no-op when the method had none and still has none, so the common case —
   * every conforming refresh, of which there are many — costs no event and no
   * re-render.
   */
  setMalformedListItems(method, malformed) {
    const previous = this.malformedListItems.get(method) ?? [];
    if (previous.length === 0 && malformed.length === 0) return;
    if (malformed.length === 0) this.malformedListItems.delete(method);
    else this.malformedListItems.set(method, malformed);
    this.dispatchTypedEvent(
      "malformedListItemsChange",
      this.getMalformedListItems()
    );
  }
  /**
   * Run a list aggregate, falling back to a per-item salvage when the SDK
   * rejects the result it received (#1909).
   *
   * The strict aggregate runs first and unchanged, so a conforming server pays
   * nothing: no extra request, no extra parse, and the SDK's response cache
   * still serves it. Only an `InvalidResult` rejection — a response in hand
   * that failed validation — reaches {@link salvageList}; every other failure
   * (transport drop, timeout, server-sent error) rethrows untouched, because
   * re-listing would not produce anything to salvage.
   */
  async listAllSalvaging({
    method,
    itemsKey,
    itemSchema,
    resultSchema,
    aggregate,
    metadata,
    finalize
  }) {
    try {
      const items = await this.invokeMcpClient(aggregate, { method });
      this.setMalformedListItems(method, []);
      return items;
    } catch (err) {
      if (!isSalvageableRejection(err)) throw err;
      const salvaged = await this.salvageList({
        method,
        itemsKey,
        itemSchema,
        resultSchema,
        metadata,
        err
      });
      return finalize ? finalize(salvaged) : salvaged;
    }
  }
  /**
   * Drop the tools the SDK would have excluded from `tools/list` for invalid
   * `x-mcp-header` annotations (SEP-2243).
   *
   * The strict aggregate is filtered by the SDK before we see it, so the
   * salvage path has to reapply the same rule itself: otherwise one ordinary
   * malformed tool anywhere in the list would trip the fallback and quietly
   * readmit every invalid-header tool alongside it — turning a rendering fix
   * into a spec violation. The dropped tools are still reported through
   * `refreshExcludedTools`, which lists them with their reason.
   */
  excludeInvalidXMcpHeaderTools(tools) {
    if (!this.excludesInvalidXMcpHeaderTools()) return tools;
    return tools.filter(
      (tool) => scanXMcpHeaderDeclarations(tool.inputSchema).valid
    );
  }
  /**
   * Re-walk a list method with pagination validated but entries left raw, then
   * validate each entry on its own — keeping the good ones and reporting the
   * rest (#1909).
   *
   * Two deliberate properties:
   *
   * - **The original error wins when nothing is salvageable.** If every entry
   *   validates here, the strict rejection was about something else (a
   *   top-level field, an era codec decision), so there is no per-item story to
   *   tell and swallowing it would hide a real failure. Rethrow the original.
   * - **A salvaged response is still a rejected response.** The Protocol entry
   *   is marked (#1953) with what was dropped, so the wire truth survives even
   *   though the list now renders.
   *
   * This walk is deliberately un-cached (it uses the raw per-page path), which
   * is fine: it only runs against a server that just returned a non-conforming
   * result.
   */
  async salvageList({
    method,
    itemsKey,
    itemSchema,
    resultSchema,
    metadata,
    err
  }) {
    const pageSchema = lenientListPageSchema(resultSchema, itemsKey);
    const rejectedResponseId = this.lastAnsweredRequestByMethod.get(method);
    const valid = [];
    const malformed = [];
    const seenCursors = /* @__PURE__ */ new Set();
    let cursor;
    let pages = 0;
    try {
      do {
        if (pages >= LIST_MAX_PAGES) throw listPaginationExceeded(method);
        pages += 1;
        const effectiveMeta = this.mergeMeta(metadata);
        const params = {
          ...effectiveMeta ? { _meta: effectiveMeta } : {},
          // `!== undefined`, not truthiness: "" is a valid cursor, and dropping it
          // would re-request page one and duplicate its entries until the
          // repeated-cursor guard below stopped the walk.
          ...cursor !== void 0 && { cursor }
        };
        const page = await this.invokeMcpClient(
          () => this.isModernEra() ? this.rawWireRequest(
            method,
            this.withModernEnvelope(params),
            pageSchema
          ) : this.client.request(
            { method, params },
            pageSchema,
            this.getRequestOptions(this.progressTokenOf(metadata))
          ),
          { method }
        );
        if (this.isModernEra() && !ModernResultEnvelopeSchema.safeParse(page).success) {
          throw err;
        }
        const items = rawItemsOf(page, itemsKey);
        if (items === void 0) throw err;
        const salvaged = salvageListItems({
          method,
          items,
          schema: itemSchema,
          startIndex: valid.length + malformed.length
        });
        valid.push(...salvaged.valid);
        malformed.push(...salvaged.malformed);
        cursor = nextCursorOf(page);
        if (cursor !== void 0) {
          if (seenCursors.has(cursor)) break;
          seenCursors.add(cursor);
        }
      } while (cursor !== void 0);
    } catch (walkErr) {
      if (walkErr !== err) {
        this.logger.warn(
          { err: walkErr, method },
          "Salvage re-walk failed; surfacing the original list error"
        );
      }
      this.restoreResponseCorrelation(method, rejectedResponseId);
      throw err;
    }
    if (malformed.length === 0) {
      this.restoreResponseCorrelation(method, rejectedResponseId);
      throw err;
    }
    if (rejectedResponseId !== void 0) {
      this.dispatchTypedEvent("responseRejected", {
        id: rejectedResponseId,
        reason: summarizeMalformed(malformed)
      });
    }
    this.setMalformedListItems(method, malformed);
    this.logger.warn(
      { method, malformed },
      "Dropped malformed entries from a list result; the rest of the list was kept"
    );
    return valid;
  }
  /** The current SEP-2243 excluded-tools set (empty on legacy/stdio). */
  getExcludedTools() {
    return this.excludedTools;
  }
  /**
   * Recompute the tools the SDK excludes from `tools/list` for invalid
   * `x-mcp-header` annotations (SEP-2243), and emit `excludedToolsChange`.
   * Returns `[]` without any round trip on connections that don't exclude
   * (legacy/stdio). Otherwise walks every page of the RAW `tools/list` (which,
   * unlike the SDK's high-level `listTools()`, is NOT filtered) and keeps the
   * tools whose annotation scan fails, each with its reason. A repeating cursor
   * stops the walk (non-converging-server guard, mirroring the SDK).
   */
  async refreshExcludedTools(metadata) {
    const excluded = [];
    if (this.excludesInvalidXMcpHeaderTools()) {
      const seenCursors = /* @__PURE__ */ new Set();
      let cursor;
      let pages = 0;
      do {
        if (pages >= LIST_MAX_PAGES) throw listPaginationExceeded("tools/list");
        pages += 1;
        const page = await this.listToolsForScan(cursor, metadata);
        for (const tool of page.tools) {
          const scan = scanXMcpHeaderDeclarations(tool.inputSchema);
          if (!scan.valid) excluded.push({ tool, reason: scan.reason });
        }
        cursor = page.nextCursor;
        if (cursor !== void 0) {
          if (seenCursors.has(cursor)) break;
          seenCursors.add(cursor);
        }
      } while (cursor !== void 0);
    }
    this.excludedTools = excluded;
    this.dispatchTypedEvent("excludedToolsChange", excluded);
    return excluded;
  }
  /**
   * One page of the RAW `tools/list` for the SEP-2243 scan, tolerating a
   * malformed sibling entry (#1632 + #1909).
   *
   * The scan needs the unfiltered list, so it uses the strict single-page call
   * — but that call rejects the whole page when any entry is non-conforming,
   * which would blank the excluded set and leave a tool that vanished for an
   * invalid header with no explanation at all. That is the same failure #1909
   * is about, one level down. So on a decode rejection the page is re-fetched
   * leniently and scanned entry by entry.
   *
   * The malformed ones are not "excluded tools" and are not returned: they
   * describe THIS exchange, not the aggregate the Tools panel renders, so they
   * surface as the rejection reason on this response's own Protocol entry and
   * go no further (see `refreshExcludedTools`).
   */
  async listToolsForScan(cursor, metadata) {
    try {
      return await this.listTools(cursor, metadata);
    } catch (err) {
      if (!isSalvageableRejection(err)) throw err;
      const rejectedResponseId = this.lastAnsweredRequestByMethod.get("tools/list");
      try {
        const effectiveMeta = this.mergeMeta(metadata);
        const params = {
          ...effectiveMeta ? { _meta: effectiveMeta } : {},
          // See `salvageList`: "" is a valid cursor.
          ...cursor !== void 0 && { cursor }
        };
        const pageSchema = lenientListPageSchema(
          ListToolsResultSchema,
          "tools"
        );
        const page = await this.invokeMcpClient(
          () => this.isModernEra() ? this.rawWireRequest(
            "tools/list",
            this.withModernEnvelope(params),
            pageSchema
          ) : this.client.request(
            { method: "tools/list", params },
            pageSchema,
            this.getRequestOptions(this.progressTokenOf(metadata))
          ),
          { method: "tools/list" }
        );
        if (this.isModernEra() && !ModernResultEnvelopeSchema.safeParse(page).success) {
          throw err;
        }
        const items = rawItemsOf(page, "tools");
        if (items === void 0) throw err;
        const { valid, malformed } = salvageListItems({
          method: "tools/list",
          items,
          schema: toolItemSchemaForEra(this.isModernEra())
        });
        if (rejectedResponseId !== void 0) {
          this.dispatchTypedEvent("responseRejected", {
            id: rejectedResponseId,
            reason: malformed.length > 0 ? summarizeMalformed(malformed) : err instanceof Error ? err.message : String(err)
          });
        }
        const nextCursor = nextCursorOf(page);
        return {
          tools: valid,
          ...nextCursor !== void 0 && { nextCursor }
        };
      } catch (fallbackErr) {
        if (rejectedResponseId !== void 0) {
          this.dispatchTypedEvent("responseRejected", {
            id: rejectedResponseId,
            reason: err instanceof Error ? err.message : String(err)
          });
        }
        if (fallbackErr !== err) {
          this.logger.warn(
            { err: fallbackErr },
            "Excluded-tools salvage re-fetch failed; surfacing the original list error"
          );
        }
        this.restoreResponseCorrelation("tools/list", rejectedResponseId);
        throw err;
      }
    }
  }
  /**
   * Call a tool. Caller must provide the Tool (e.g. from a state manager).
   * @param tool The tool to call (use tool.name for the request)
   * @param args Tool arguments
   * @param generalMetadata Optional general metadata
   * @param toolSpecificMetadata Optional tool-specific metadata (takes precedence over general)
   * @param taskOptions Optional task options (e.g. ttl) for task-augmented requests
   * @returns Tool call response
   */
  async callTool(tool, args, generalMetadata, toolSpecificMetadata, taskOptions, options) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    if (tool.execution?.taskSupport === "required") {
      throw new Error(
        `Tool "${tool.name}" requires task support. Use callToolStream() instead of callTool().`
      );
    }
    const request = {
      tool,
      args,
      generalMetadata,
      toolSpecificMetadata,
      taskOptions,
      options
    };
    const abortController = new AbortController();
    this.activeToolCallAbortController = abortController;
    try {
      return await this.callToolWithRetries(request, abortController);
    } finally {
      if (this.activeToolCallAbortController === abortController) {
        this.activeToolCallAbortController = void 0;
      }
    }
  }
  /**
   * The URL-elicitation retry loop for {@link callTool}, factored out so the
   * caller can wrap it in the abort-controller lifecycle (`try`/`finally`). On a
   * user cancellation (the call's abort signal fired with the cancel reason) the
   * SDK has already sent `notifications/cancelled`, so we throw a
   * {@link ToolCallCancelledError} without recording a failed call — the cancel
   * was intentional, not a failure.
   */
  async callToolWithRetries(request, abortController) {
    const { tool, args, generalMetadata, toolSpecificMetadata } = request;
    let urlElicitationAttempt = 0;
    const presentedUrls = /* @__PURE__ */ new Set();
    while (true) {
      try {
        return await this.attemptToolCall(request, abortController.signal);
      } catch (error) {
        if (abortController.signal.aborted && abortController.signal.reason === TOOL_CALL_CANCELLED_REASON) {
          throw new ToolCallCancelledError(tool.name);
        }
        const urlElicitations = getUrlElicitationsFromError(error);
        if (urlElicitations && urlElicitations.length > 0 && urlElicitationAttempt < MAX_URL_ELICITATION_RETRIES) {
          const repeated = urlElicitations.find(
            (e) => presentedUrls.has(e.url)
          );
          if (repeated) {
            const loopError = new UrlElicitationLoopError(repeated.url);
            this.dispatchFailedToolCall(
              tool,
              args,
              generalMetadata,
              toolSpecificMetadata,
              loopError.message
            );
            throw loopError;
          }
          urlElicitationAttempt++;
          for (const e of urlElicitations) {
            presentedUrls.add(e.url);
          }
          const action = await this.runUrlElicitations(urlElicitations);
          if (action === "accept") {
            continue;
          }
          const abortError = new Error(
            `Tool call cancelled: required URL elicitation was ${action === "decline" ? "declined" : "cancelled"}.`
          );
          this.dispatchFailedToolCall(
            tool,
            args,
            generalMetadata,
            toolSpecificMetadata,
            abortError.message
          );
          throw abortError;
        }
        if (urlElicitations && urlElicitations.length > 0) {
          this.logger.warn(
            { tool: tool.name, attempts: urlElicitationAttempt },
            `Tool "${tool.name}" still required URL elicitations after ${MAX_URL_ELICITATION_RETRIES} attempts; giving up.`
          );
        }
        this.dispatchFailedToolCall(
          tool,
          args,
          generalMetadata,
          toolSpecificMetadata,
          error instanceof Error ? error.message : String(error)
        );
        throw error;
      }
    }
  }
  /**
   * Coerce the string-valued entries of a tool's arguments to the types its
   * `inputSchema` declares (the Tools form hands everything over as text).
   * Shared by the two `tools/call` entry points — {@link attemptToolCall} and
   * {@link callToolStream} — so both put the SAME arguments on the wire.
   */
  convertStringToolArgs(tool, args) {
    const stringArgs = {};
    for (const [key, value] of Object.entries(args)) {
      if (typeof value === "string") {
        stringArgs[key] = value;
      }
    }
    if (Object.keys(stringArgs).length === 0) return args;
    return { ...args, ...convertToolParameters(tool, stringArgs) };
  }
  /**
   * SEP-2243: mirror `x-mcp-header`-annotated arguments into `Mcp-Param-*`
   * headers on a modern connection. The SDK only does this inside
   * `client.callTool()` (and skips it in the browser), but we route
   * `tools/call` through `client.request()` for manual MRTR driving (#1704), so
   * we mirror ourselves. `Protocol.request` forwards `headers` (preserved
   * across MRTR retry legs) to the transport, and the remote transport relays
   * them to the backend's upstream send — issued server-side, where the browser
   * skip doesn't apply. No-op on legacy/stdio (no annotations).
   *
   * Applied by BOTH `tools/call` entry points: a plain call
   * ({@link attemptToolCall}) and a task-augmented one
   * ({@link callToolStream}) — a strict modern server rejects either with
   * `-32020` when the mirrored header is missing.
   */
  applyMirroredParamHeaders(tool, convertedArgs, requestOptions) {
    if (this.protocolEra !== "modern") return;
    const paramHeaders = mcpParamHeadersForTool(tool, convertedArgs);
    if (Object.keys(paramHeaders).length === 0) return;
    requestOptions.headers = { ...requestOptions.headers, ...paramHeaders };
  }
  /**
   * Run a single tools/call attempt: convert args, issue the request, validate,
   * and return a successful {@link ToolCallInvocation}. Throws on any error
   * (including a `-32042` UrlElicitationRequired response); {@link callTool}'s
   * retry loop owns the elicitation handling and failure bookkeeping.
   */
  async attemptToolCall(request, signal) {
    const {
      tool,
      args,
      generalMetadata,
      toolSpecificMetadata,
      taskOptions,
      options
    } = request;
    const client = this.client;
    if (!client) {
      throw new Error("Client is not connected");
    }
    const convertedArgs = this.convertStringToolArgs(tool, args);
    const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
    const timestamp = /* @__PURE__ */ new Date();
    const metadata = this.mergeMeta(callMetadata);
    const callParams = {
      name: tool.name,
      arguments: convertedArgs,
      _meta: metadata
    };
    if (taskOptions?.ttl != null) {
      callParams.task = { ttl: taskOptions.ttl };
    }
    const requestOptions = this.getRequestOptions(
      this.progressTokenOf(metadata),
      signal
    );
    this.applyMirroredParamHeaders(tool, convertedArgs, requestOptions);
    const rawResult = await this.invokeMcpClient(
      () => this.requestWithInputRequired(
        "tools/call",
        callParams,
        CallToolResultSchema,
        requestOptions
      ),
      { method: "tools/call", toolName: tool.name }
    );
    const taskHandle = rawResult._meta?.[MODERN_TASK_HANDLE_META];
    const result = taskHandle ? await this.pollModernTaskToTermination(taskHandle) : rawResult;
    const outputValidationError = this.validateToolOutput(tool, result);
    if (outputValidationError && !options?.skipOutputValidation) {
      throw new ProtocolError2(
        ProtocolErrorCode2.InvalidParams,
        outputValidationError
      );
    }
    const invocation = {
      toolName: tool.name,
      params: args,
      result,
      timestamp,
      success: true,
      metadata,
      outputValidationError
    };
    this.dispatchTypedEvent("toolCallResultChange", {
      toolName: tool.name,
      params: args,
      result: invocation.result,
      timestamp,
      success: true,
      metadata,
      outputValidationError
    });
    return invocation;
  }
  /**
   * Record a failed tools/call as a `toolCallResultChange` event (history + the
   * Tools panel) without throwing. {@link callTool} calls this before rethrowing
   * so a failure — whether a transport error, a declined URL elicitation, or a
   * non-spec `-32042` — lands in the request history exactly once.
   */
  dispatchFailedToolCall(tool, args, generalMetadata, toolSpecificMetadata, errorMessage) {
    const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
    const metadata = this.mergeMeta(callMetadata);
    this.dispatchTypedEvent("toolCallResultChange", {
      toolName: tool.name,
      params: args,
      result: null,
      timestamp: /* @__PURE__ */ new Date(),
      success: false,
      error: errorMessage,
      metadata
    });
  }
  /**
   * Surface the URL elicitations carried by a `-32042` error, one at a time and
   * in order (per the spec's "URL mode with elicitation required error" flow),
   * returning as soon as the user declines/cancels one. Returns `"accept"` only
   * when every elicitation was accepted, which is {@link callTool}'s signal to
   * retry the original call.
   */
  async runUrlElicitations(elicitations) {
    for (const params of elicitations) {
      const action = await this.awaitUrlElicitation(params);
      if (action !== "accept") {
        return action;
      }
    }
    return "accept";
  }
  /**
   * Add one error-path URL elicitation to the pending queue (so it renders in
   * the same modal as request-path elicitations) and resolve with the user's
   * action. Unlike the request-path handler there is no server request to
   * answer — accepting it just unblocks the retry; the server's optional
   * `notifications/elicitation/complete` resolves it as accepted too (via
   * `completeIfPending`).
   */
  awaitUrlElicitation(params) {
    return new Promise((resolve7) => {
      const request = {
        method: "elicitation/create",
        params
      };
      const message = new ElicitationCreateMessage(
        request,
        (result) => resolve7(result.action),
        (id) => this.removePendingElicitation(id)
      );
      this.addPendingElicitation(message);
    });
  }
  /**
   * Non-fatally validate a delivered tool result against the tool's outputSchema
   * (used by the skipOutputValidation path). Delegates to the pure
   * {@link validateToolOutput} helper with this client's lazily-built Ajv
   * validator. Returns an advisory message, or undefined when valid.
   */
  validateToolOutput(tool, result) {
    this.outputValidator ??= new AjvJsonSchemaValidator();
    return validateToolOutput(this.outputValidator, tool, result);
  }
  /**
   * When a modern (SEP-2663) task is `input_required`, fulfil its embedded
   * `inputRequests` through the pending-request UI and submit them via
   * `tasks/update`. No-op for any other status. Shared by the streaming
   * ({@link pollTaskToolCall}) and ordinary ({@link pollModernTaskToTermination})
   * poll loops so the input handling lives in one place.
   *
   * `priorRounds` is the count of `input_required` rounds already handled for
   * this task; the return value is the updated count. A non-conformant server
   * that keeps returning `input_required` without ever completing would
   * otherwise re-prompt the user on every poll forever, so we bound it with the
   * same {@link MRTR_MAX_ROUNDS} cap the MRTR driver uses.
   */
  async submitModernTaskInput(detailed, task, priorRounds, signal) {
    if (task.status !== "input_required") {
      return priorRounds;
    }
    const rounds = priorRounds + 1;
    if (rounds > _InspectorClient.MRTR_MAX_ROUNDS) {
      throw new Error(
        `Modern task "${task.taskId}" exceeded ${_InspectorClient.MRTR_MAX_ROUNDS} input_required rounds without completing.`
      );
    }
    const inputResponses = await this.fulfilInputRequests(
      this.tagInputRequestsWithTask(readInputRequests(detailed), task.taskId),
      signal,
      "task-input-required"
    );
    if (inputResponses) {
      await this.updateRequestorTask(task.taskId, inputResponses);
    }
    return rounds;
  }
  /**
   * Stamp `_meta[RELATED_TASK_META_KEY]` with the owning task id on each embedded
   * request of a modern task's `inputRequests`. The pending-request UI reads that
   * id (via `ElicitationCreateMessage.taskId`) so its Cancel control can cancel
   * the TASK — not just answer the request — when a task is paused at
   * `input_required`.
   */
  tagInputRequestsWithTask(inputRequests, taskId) {
    if (!inputRequests) return inputRequests;
    const tagged = {};
    for (const [key, req] of Object.entries(inputRequests)) {
      const request = req;
      tagged[key] = {
        ...request,
        params: {
          ...request.params,
          _meta: {
            ...request.params?._meta,
            [RELATED_TASK_META_KEY3]: { taskId }
          }
        }
      };
    }
    return tagged;
  }
  /**
   * Terminal outcome for a modern task: the inlined `CallToolResult` for a
   * `completed` task (SEP-2663 removed the blocking `tasks/result`), or a
   * `ProtocolError` for `failed` / `cancelled`. Shared so both poll loops agree
   * on the result/error shape.
   */
  modernTaskTerminalOutcome(task, detailed) {
    if (task.status === "completed") {
      return {
        type: "result",
        result: detailed.result ?? { content: [] }
      };
    }
    return {
      type: "error",
      error: new ProtocolError2(
        ProtocolErrorCode2.InternalError,
        task.statusMessage ?? `Task ${task.status}`
      )
    };
  }
  /**
   * Poll cadence for a task: the server-advertised `pollInterval` when
   * positive, else the default. Shared by every task poll loop (both eras).
   */
  taskPollInterval(task) {
    const advertised = task.pollInterval;
    if (typeof advertised !== "number") return DEFAULT_TASK_POLL_INTERVAL_MS;
    return advertised > 0 ? advertised : DEFAULT_TASK_POLL_INTERVAL_MS;
  }
  /**
   * Register a per-task abort controller (keyed by taskId) whose signal gates
   * the task's `input_required` pending request, and return the signal plus a
   * `release` cleanup. {@link cancelRequestorTask} aborts it to unblock a task
   * paused at the pending-request modal.
   */
  registerTaskInputAbort(taskId) {
    const controller = new AbortController();
    this.taskInputAbortControllers.set(taskId, controller);
    return {
      signal: controller.signal,
      release: () => {
        if (this.taskInputAbortControllers.get(taskId) === controller) {
          this.taskInputAbortControllers.delete(taskId);
        }
      }
    };
  }
  /**
   * Drive a modern (SEP-2663) task to a terminal state from a seed
   * `DetailedTask`, dispatching task events so the Tasks tab and toasts track
   * it, and return the completed task's inlined `CallToolResult` (or throw on
   * `failed` / `cancelled`). Used by the ORDINARY `callTool` path when a server
   * returns an unsolicited task handle (the run-as-task streaming path drives
   * the equivalent loop inline in {@link pollTaskToolCall}). `input_required`
   * rounds are answered through the pending-request UI and submitted via
   * `tasks/update`.
   */
  async pollModernTaskToTermination(seed) {
    let detailed = seed;
    let task = normalizeModernTask(detailed);
    const emit = (t) => {
      this.dispatchTypedEvent("toolCallTaskUpdated", {
        taskId: t.taskId,
        task: t
      });
      this.dispatchTypedEvent("requestorTaskUpdated", {
        taskId: t.taskId,
        task: t
      });
    };
    emit(task);
    const { signal: inputSignal, release } = this.registerTaskInputAbort(
      task.taskId
    );
    try {
      let inputRounds = 0;
      while (!_InspectorClient.isTerminalTaskStatus(task.status)) {
        inputRounds = await this.submitModernTaskInput(
          detailed,
          task,
          inputRounds,
          inputSignal
        );
        await new Promise(
          (resolve7) => setTimeout(resolve7, this.taskPollInterval(task))
        );
        detailed = await this.rawWireRequest(
          "tasks/get",
          this.withModernTaskEnvelope({ taskId: task.taskId }),
          ModernGetTaskResultSchema
        );
        task = normalizeModernTask(detailed);
        emit(task);
      }
    } finally {
      release();
    }
    const outcome = this.modernTaskTerminalOutcome(task, detailed);
    if (outcome.type === "error") {
      throw outcome.error;
    }
    return outcome.result;
  }
  /**
   * Poll a task-augmented tool call to completion. Replaces the removed
   * `client.experimental.tasks.callToolStream` helper: it sends the
   * task-augmented `tools/call` (the server responds with a task handle, i.e. a
   * `CreateTaskResult`), then polls `tasks/get` until the task reaches a
   * terminal status, yielding the same `taskCreated | taskStatus | result |
   * error` message shapes the caller's `for await` loop consumes — so all the
   * downstream event dispatch and terminal-state handling stays unchanged.
   */
  async *pollTaskToolCall(params, requestOptions) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const client = this.client;
    const progressHandlers = client._progressHandlers;
    const keysBeforeRequest = new Set(progressHandlers.keys());
    const modernTasks = this.isTasksExtensionNegotiated();
    const requestPromise = client.request(
      {
        // On modern the SDK codec stamps the tasks-extension client capability
        // into the request envelope (advertised at construction), so a server
        // may answer with a `CreateTaskResult` — no per-call `_meta` needed.
        method: "tools/call",
        params
      },
      // Modern: the SDK codec can't decode a `resultType: "task"` result, so the
      // transport rewrote it to a `CallToolResult` carrying the task handle in
      // `_meta` — parse as a CallToolResult and read the handle below. Legacy:
      // accept a `{ task }` handle or an immediate result.
      modernTasks ? CallToolResultSchema : CreateTaskResultSchema.or(CallToolResultSchema),
      requestOptions
    );
    const progressSubscriptionId = requestOptions.onprogress ? [...progressHandlers.keys()].find((k) => !keysBeforeRequest.has(k)) : void 0;
    const created = await requestPromise;
    if (modernTasks) {
      const handle = created._meta?.[MODERN_TASK_HANDLE_META];
      if (!handle) {
        yield { type: "result", result: created };
        return;
      }
      let detailed = handle;
      let task2 = normalizeModernTask(detailed);
      yield { type: "taskCreated", task: task2 };
      if (progressSubscriptionId != null && requestOptions.onprogress) {
        progressHandlers.set(progressSubscriptionId, requestOptions.onprogress);
      }
      const { signal: inputSignal, release } = this.registerTaskInputAbort(
        task2.taskId
      );
      let inputRounds = 0;
      try {
        while (!_InspectorClient.isTerminalTaskStatus(task2.status)) {
          inputRounds = await this.submitModernTaskInput(
            detailed,
            task2,
            inputRounds,
            inputSignal
          );
          await new Promise(
            (resolve7) => setTimeout(resolve7, this.taskPollInterval(task2))
          );
          detailed = await this.rawWireRequest(
            "tasks/get",
            this.withModernTaskEnvelope({ taskId: task2.taskId }),
            ModernGetTaskResultSchema
          );
          task2 = normalizeModernTask(detailed);
          yield { type: "taskStatus", task: task2 };
        }
      } finally {
        release();
        if (progressSubscriptionId != null) {
          progressHandlers.delete(progressSubscriptionId);
        }
      }
      yield this.modernTaskTerminalOutcome(task2, detailed);
      return;
    }
    if (!("task" in created) || created.task == null) {
      yield { type: "result", result: created };
      return;
    }
    let task = created.task;
    yield { type: "taskCreated", task };
    if (progressSubscriptionId != null && requestOptions.onprogress) {
      progressHandlers.set(progressSubscriptionId, requestOptions.onprogress);
    }
    try {
      while (!_InspectorClient.isTerminalTaskStatus(task.status)) {
        await new Promise(
          (resolve7) => setTimeout(resolve7, this.taskPollInterval(task))
        );
        task = await client.request(
          { method: "tasks/get", params: { taskId: task.taskId } },
          GetTaskResultSchema,
          this.getRequestOptions()
        );
        yield { type: "taskStatus", task };
      }
    } finally {
      if (progressSubscriptionId != null) {
        progressHandlers.delete(progressSubscriptionId);
      }
    }
    if (task.status === "completed") {
      const result = await client.request(
        { method: "tasks/result", params: { taskId: task.taskId } },
        CallToolResultSchema,
        this.getRequestOptions()
      );
      yield { type: "result", result };
    } else {
      yield {
        type: "error",
        error: new ProtocolError2(
          ProtocolErrorCode2.InternalError,
          task.statusMessage ?? `Task ${task.status}`
        )
      };
    }
  }
  /**
   * Call a tool with task support (streaming).
   * Caller must provide the Tool (e.g. from a state manager).
   * @param tool The tool to call (use tool.name for the request)
   * @param args Tool arguments
   * @param generalMetadata Optional general metadata
   * @param toolSpecificMetadata Optional tool-specific metadata (takes precedence over general)
   * @param taskOptions Optional task options (e.g. ttl) for task-augmented requests
   * @returns Tool call response
   */
  async callToolStream(tool, args, generalMetadata, toolSpecificMetadata, taskOptions) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    try {
      const convertedArgs = this.convertStringToolArgs(tool, args);
      const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
      const timestamp = /* @__PURE__ */ new Date();
      const metadata = this.mergeMeta(callMetadata);
      const streamParams = {
        name: tool.name,
        arguments: convertedArgs
      };
      if (metadata) {
        streamParams._meta = metadata;
      }
      if (taskOptions?.ttl != null) {
        streamParams.task = { ttl: taskOptions.ttl };
      }
      let finalResult;
      let taskId;
      let error;
      const requestOptions = this.getRequestOptions(
        this.progressTokenOf(metadata)
      );
      this.applyMirroredParamHeaders(tool, convertedArgs, requestOptions);
      if (this.progress) {
        const innerOnProgress = requestOptions.onprogress;
        requestOptions.onprogress = (progress) => {
          innerOnProgress?.(progress);
          if (taskId) {
            this.dispatchTypedEvent("requestorTaskProgress", {
              taskId,
              progress
            });
          }
        };
      }
      const stream = this.pollTaskToolCall(
        streamParams,
        requestOptions
      );
      for await (const message of stream) {
        switch (message.type) {
          case "taskCreated":
            taskId = message.task.taskId;
            this.dispatchTypedEvent("toolCallTaskUpdated", {
              taskId: message.task.taskId,
              task: message.task
            });
            this.dispatchTypedEvent("requestorTaskUpdated", {
              taskId: message.task.taskId,
              task: message.task
            });
            break;
          case "taskStatus":
            if (!taskId) {
              taskId = message.task.taskId;
            }
            this.dispatchTypedEvent("toolCallTaskUpdated", {
              taskId: message.task.taskId,
              task: message.task
            });
            this.dispatchTypedEvent("requestorTaskUpdated", {
              taskId: message.task.taskId,
              task: message.task
            });
            break;
          case "result":
            finalResult = message.result;
            if (taskId) {
              const completedTask = {
                taskId,
                ttl: null,
                status: "completed",
                statusMessage: "Task completed",
                lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
              };
              this.dispatchTypedEvent("toolCallTaskUpdated", {
                taskId,
                task: completedTask,
                result: finalResult
              });
              this.dispatchTypedEvent("requestorTaskUpdated", {
                taskId,
                task: completedTask,
                result: finalResult
              });
            }
            break;
          case "error": {
            const errorMessage = message.error.message || "Task execution failed";
            error = new Error(errorMessage);
            if (taskId) {
              const cancelled = this.cancelledTaskIds.has(taskId);
              this.cancelledTaskIds.delete(taskId);
              const terminalTask = {
                taskId,
                ttl: null,
                status: cancelled ? "cancelled" : "failed",
                statusMessage: cancelled ? "Client cancelled task execution." : errorMessage,
                lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
              };
              this.dispatchTypedEvent("toolCallTaskUpdated", {
                taskId,
                task: terminalTask,
                error: message.error
              });
              this.dispatchTypedEvent("requestorTaskUpdated", {
                taskId,
                task: terminalTask,
                error: message.error
              });
            }
            break;
          }
        }
      }
      if (error) {
        throw error;
      }
      if (!finalResult && taskId) {
        try {
          finalResult = await this.client.request(
            { method: "tasks/result", params: { taskId } },
            CallToolResultSchema,
            this.getRequestOptions()
            // no metadata for fallback
          );
        } catch (resultError) {
          throw new Error(
            `Tool call did not return a result: ${resultError instanceof Error ? resultError.message : String(resultError)}`,
            { cause: resultError }
          );
        }
      }
      if (!finalResult) {
        throw new Error("Tool call did not return a result");
      }
      const invocation = {
        toolName: tool.name,
        params: args,
        result: finalResult,
        timestamp,
        success: true,
        metadata
      };
      this.dispatchTypedEvent("toolCallResultChange", {
        toolName: tool.name,
        params: args,
        result: invocation.result,
        timestamp,
        success: true,
        metadata
      });
      return invocation;
    } catch (error) {
      const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
      const timestamp = /* @__PURE__ */ new Date();
      const metadata = this.mergeMeta(callMetadata);
      this.dispatchTypedEvent("toolCallResultChange", {
        toolName: tool.name,
        params: args,
        result: null,
        timestamp,
        success: false,
        error: error instanceof Error ? error.message : String(error),
        metadata
      });
      throw error;
    }
  }
  /**
   * List available resources with pagination support (stateless; state managers hold the list).
   * @param cursor Optional cursor for pagination
   * @param metadata Optional metadata to include in the request
   * @returns Object containing resources array and optional nextCursor
   */
  async listResources(cursor, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const effectiveMeta = this.mergeMeta(metadata);
    const params = {
      ...effectiveMeta ? { _meta: effectiveMeta } : {},
      ...cursor ? { cursor } : {}
    };
    const response = await this.invokeMcpClient(
      () => this.client.request(
        { method: "resources/list", params },
        ListResourcesResultSchema,
        this.getRequestOptions(this.progressTokenOf(metadata))
      )
    );
    return {
      resources: response.resources || [],
      nextCursor: response.nextCursor
    };
  }
  /**
   * Aggregate ALL pages of `resources/list` via the SDK's high-level
   * cache-aware `client.listResources()`. See {@link listAllTools} for the
   * `cacheMode` semantics; this is the path the managed resource list uses on
   * refresh.
   */
  async listAllResources(options) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const resources = await this.listAllSalvaging({
      method: "resources/list",
      itemsKey: "resources",
      resultSchema: ListResourcesResultSchema,
      itemSchema: ResourceSchema,
      metadata: options?.metadata,
      aggregate: async () => [
        ...(await this.client.listResources(
          this.aggregateListParams(options?.metadata),
          this.getCacheableRequestOptions(options?.cacheMode)
        )).resources
      ]
    });
    return { resources };
  }
  /**
   * Read a resource by URI
   * @param uri Resource URI
   * @param metadata Optional metadata to include in the request
   * @returns Resource content
   */
  async readResource(uri, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const effectiveMeta = this.mergeMeta(metadata);
    const params = {
      uri,
      ...effectiveMeta ? { _meta: effectiveMeta } : {}
    };
    const result = await this.invokeMcpClient(
      () => this.requestWithInputRequired(
        "resources/read",
        params,
        ReadResourceResultSchema,
        this.getRequestOptions(this.progressTokenOf(metadata))
      ),
      { method: "resources/read" }
    );
    const invocation = {
      result,
      timestamp: /* @__PURE__ */ new Date(),
      uri,
      metadata: effectiveMeta
    };
    this.dispatchTypedEvent("resourceContentChange", {
      uri,
      content: invocation,
      timestamp: invocation.timestamp
    });
    return invocation;
  }
  /**
   * Read a resource from a template by expanding the template URI with parameters
   * This encapsulates the business logic of template expansion and associates the
   * loaded resource with its template in InspectorClient state
   * @param templateName The name/ID of the resource template
   * @param params Parameters to fill in the template variables
   * @param metadata Optional metadata to include in the request
   * @returns The resource content along with expanded URI and template name
   * @throws Error if template is not found or URI expansion fails
   */
  async readResourceFromTemplate(uriTemplate, params, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const uriTemplateString = uriTemplate;
    let expandedUri;
    try {
      expandedUri = expandUriTemplateStrict(uriTemplateString, params);
    } catch (error) {
      throw new Error(
        `Failed to expand URI template "${uriTemplate}": ${error instanceof Error ? error.message : String(error)}`,
        { cause: error }
      );
    }
    const readInvocation = await this.readResource(expandedUri, metadata);
    const invocation = {
      uriTemplate: uriTemplateString,
      expandedUri,
      result: readInvocation.result,
      timestamp: readInvocation.timestamp,
      params,
      metadata: readInvocation.metadata
    };
    this.dispatchTypedEvent("resourceTemplateContentChange", {
      uriTemplate: uriTemplateString,
      content: invocation,
      params,
      timestamp: invocation.timestamp
    });
    return invocation;
  }
  /**
   * List resource templates with pagination support (stateless; state managers hold the list).
   * @param cursor Optional cursor for pagination
   * @param metadata Optional metadata to include in the request
   * @returns Object containing resourceTemplates array and optional nextCursor
   */
  async listResourceTemplates(cursor, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const effectiveMeta = this.mergeMeta(metadata);
    const params = {
      ...effectiveMeta ? { _meta: effectiveMeta } : {},
      ...cursor ? { cursor } : {}
    };
    const response = await this.invokeMcpClient(
      () => this.client.request(
        { method: "resources/templates/list", params },
        ListResourceTemplatesResultSchema,
        this.getRequestOptions(this.progressTokenOf(metadata))
      ),
      { method: "resources/templates/list" }
    );
    return {
      resourceTemplates: response.resourceTemplates || [],
      nextCursor: response.nextCursor
    };
  }
  /**
   * Aggregate ALL pages of `resources/templates/list` via the SDK's high-level
   * cache-aware `client.listResourceTemplates()`. See {@link listAllTools} for
   * the `cacheMode` semantics; this is the path the managed resource-template
   * list uses on refresh.
   */
  async listAllResourceTemplates(options) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const resourceTemplates = await this.listAllSalvaging({
      method: "resources/templates/list",
      itemsKey: "resourceTemplates",
      resultSchema: ListResourceTemplatesResultSchema,
      itemSchema: ResourceTemplateSchema,
      metadata: options?.metadata,
      aggregate: async () => [
        ...(await this.client.listResourceTemplates(
          this.aggregateListParams(options?.metadata),
          this.getCacheableRequestOptions(options?.cacheMode)
        )).resourceTemplates
      ]
    });
    return { resourceTemplates };
  }
  /**
   * List available prompts with pagination support
   * @param cursor Optional cursor for pagination
   * @param metadata Optional metadata to include in the request
   * @returns Object containing prompts array and optional nextCursor
   */
  async listPrompts(cursor, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const effectiveMeta = this.mergeMeta(metadata);
    const params = {
      ...effectiveMeta ? { _meta: effectiveMeta } : {},
      ...cursor ? { cursor } : {}
    };
    const response = await this.invokeMcpClient(
      () => this.client.request(
        { method: "prompts/list", params },
        ListPromptsResultSchema,
        this.getRequestOptions(this.progressTokenOf(metadata))
      )
    );
    return {
      prompts: response.prompts || [],
      nextCursor: response.nextCursor
    };
  }
  /**
   * Aggregate ALL pages of `prompts/list` via the SDK's high-level
   * cache-aware `client.listPrompts()`. See {@link listAllTools} for the
   * `cacheMode` semantics; this is the path the managed prompt list uses on
   * refresh.
   */
  async listAllPrompts(options) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const prompts = await this.listAllSalvaging({
      method: "prompts/list",
      itemsKey: "prompts",
      resultSchema: ListPromptsResultSchema,
      itemSchema: PromptSchema,
      metadata: options?.metadata,
      aggregate: async () => [
        ...(await this.client.listPrompts(
          this.aggregateListParams(options?.metadata),
          this.getCacheableRequestOptions(options?.cacheMode)
        )).prompts
      ]
    });
    return { prompts };
  }
  /**
   * Get a prompt by name
   * @param name Prompt name
   * @param args Optional prompt arguments
   * @param metadata Optional metadata to include in the request
   * @returns Prompt content
   */
  async getPrompt(name, args, metadata) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    const stringArgs = args ? convertPromptArguments(args) : {};
    const effectiveMeta = this.mergeMeta(metadata);
    const params = {
      name,
      arguments: stringArgs,
      ...effectiveMeta ? { _meta: effectiveMeta } : {}
    };
    const result = await this.invokeMcpClient(
      () => this.requestWithInputRequired(
        "prompts/get",
        params,
        GetPromptResultSchema,
        this.getRequestOptions(this.progressTokenOf(metadata))
      ),
      { method: "prompts/get", toolName: name }
    );
    const invocation = {
      result,
      timestamp: /* @__PURE__ */ new Date(),
      name,
      params: Object.keys(stringArgs).length > 0 ? stringArgs : void 0,
      metadata: effectiveMeta
    };
    this.dispatchTypedEvent("promptContentChange", {
      name,
      content: invocation,
      timestamp: invocation.timestamp
    });
    return invocation;
  }
  /**
   * Request completions for a resource template variable or prompt argument
   * @param ref Resource template reference or prompt reference
   * @param argumentName Name of the argument/variable to complete
   * @param argumentValue Current (partial) value of the argument
   * @param context Optional context with other argument values
   * @param metadata Optional metadata to include in the request
   * @returns Completion result with values array
   * @throws Error if client is not connected or request fails (except MethodNotFound)
   */
  async getCompletions(ref, argumentName, argumentValue, context, metadata) {
    if (!this.client) {
      return { values: [] };
    }
    try {
      const effectiveMeta = this.mergeMeta(metadata);
      const params = {
        ref,
        argument: {
          name: argumentName,
          value: argumentValue
        },
        ...context ? { context: { arguments: context } } : {},
        ...effectiveMeta ? { _meta: effectiveMeta } : {}
      };
      const response = await this.invokeMcpClient(
        () => this.client.complete(
          params,
          this.getRequestOptions(this.progressTokenOf(metadata))
        ),
        {
          method: "completion/complete",
          toolName: ref.type === "ref/prompt" ? ref.name : ref.uri
        }
      );
      return {
        values: response.completion.values || [],
        total: response.completion.total,
        hasMore: response.completion.hasMore
      };
    } catch (error) {
      if (error instanceof ProtocolError2 && error.code === ProtocolErrorCode2.MethodNotFound || error instanceof Error && (error.message.includes("Method not found") || error.message.includes("does not support completions"))) {
        return { values: [] };
      }
      throw new Error(
        `Failed to get completions: ${error instanceof Error ? error.message : String(error)}`,
        { cause: error }
      );
    }
  }
  /**
   * Fetch server info (capabilities, serverInfo, instructions) from cached initialize response
   * This does not send any additional MCP requests - it just reads cached data
   * Always called on connect
   */
  async fetchServerInfo() {
    if (!this.client) {
      return;
    }
    try {
      this.capabilities = this.client.getServerCapabilities();
      this.dispatchTypedEvent("capabilitiesChange", this.capabilities);
      this.serverInfo = this.client.getServerVersion();
      this.instructions = this.client.getInstructions();
      this.dispatchTypedEvent("serverInfoChange", this.serverInfo);
      if (this.instructions !== void 0) {
        this.dispatchTypedEvent("instructionsChange", this.instructions);
      }
      this.protocolEra = this.client.getProtocolEra();
      this.dispatchTypedEvent("protocolEraChange", this.protocolEra);
      this.discoverResult = this.client.getDiscoverResult();
      this.dispatchTypedEvent("discoverResultChange", this.discoverResult);
      this.protocolVersion = this.client.getNegotiatedProtocolVersion();
      this.dispatchTypedEvent("protocolVersionChange", this.protocolVersion);
    } catch {
    }
  }
  dispatchStderrLog(entry) {
    this.dispatchTypedEvent("stderrLog", entry);
  }
  dispatchFetchRequest(entry) {
    this.logger.info(
      {
        component: "InspectorClient",
        category: entry.category,
        fetchRequest: {
          url: entry.url,
          method: entry.method,
          headers: entry.requestHeaders,
          body: entry.requestBody ?? "[no body]"
        },
        fetchResponse: entry.error ? { error: entry.error } : {
          status: entry.responseStatus,
          statusText: entry.responseStatusText,
          headers: entry.responseHeaders,
          body: entry.responseBody
        }
      },
      `${entry.category} fetch`
    );
    this.dispatchTypedEvent("fetchRequest", entry);
  }
  dispatchFetchRequestBodyUpdate(id, responseBody) {
    this.dispatchTypedEvent("fetchRequestBodyUpdate", { id, responseBody });
  }
  /**
   * Get current session ID (from OAuth state authId)
   */
  getSessionId() {
    return this.sessionId;
  }
  /**
   * Set session ID (typically extracted from OAuth state)
   */
  setSessionId(sessionId) {
    this.sessionId = sessionId;
  }
  /**
   * Dispatch saveSession so FetchRequestLogState (or other listeners) can persist.
   * Call before OAuth redirect; listeners use sessionStorage with this sessionId.
   */
  saveSession() {
    if (!this.sessionId) return;
    this.dispatchTypedEvent("saveSession", { sessionId: this.sessionId });
  }
  /**
   * Get current roots
   */
  getRoots() {
    return this.roots !== void 0 ? [...this.roots] : [];
  }
  /**
   * Set roots and announce the change to the server.
   *
   * Note this does **not** enable the roots capability on a client that was
   * built without the constructor's `roots` option. `capabilities.roots` is
   * negotiated at `initialize` and the SDK refuses `registerCapabilities`
   * after connect, so such a client has no `roots/list` handler (see {@link
   * registerPeerRequestHandlers}) and would have to answer `-32601` if a
   * server asked. So on such a client the roots set here are stored and
   * readable via {@link getRoots}, but no server can ask for them and the
   * change is not announced — the SDK refuses `roots/list_changed` from a
   * client that never declared `roots.listChanged`, so the notification could
   * not have gone out anyway. Pass `roots` at construction — `[]` is enough —
   * in any client that may call this (#1797).
   *
   * The argument runs through `cleanRoots`, the same normalizer the
   * connect-time and settings-save paths use, so all three ways roots enter the
   * client agree and no caller can advertise a `Root` with no `uri` (the CLI's
   * `--roots-json` only checks that the JSON is an array). It is idempotent, so
   * a caller that already cleaned loses nothing.
   */
  async setRoots(roots) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    this.roots = cleanRoots(roots);
    this.dispatchTypedEvent("rootsChange", [...this.roots]);
    if (!this.rootsListChangedCapabilityAdvertised) {
      this.logger.warn(
        "setRoots() on a client that did not advertise `roots.listChanged`; roots are stored locally but the change is not announced"
      );
      return;
    }
    try {
      await this.client.notification({
        method: "notifications/roots/list_changed"
      });
    } catch (error) {
      this.logger.error(
        { error },
        "Failed to send roots/list_changed notification"
      );
    }
  }
  /**
   * Get list of currently subscribed resource URIs
   */
  getSubscribedResources() {
    return Array.from(this.subscribedResources);
  }
  /**
   * Check if a resource is currently subscribed
   */
  isSubscribedToResource(uri) {
    return this.subscribedResources.has(uri);
  }
  /**
   * Check if the server supports resource subscriptions
   */
  supportsResourceSubscriptions() {
    return this.capabilities?.resources?.subscribe === true;
  }
  /**
   * The negotiated protocol era once connected (SEP §7.8). Modern (2026-07-28)
   * connections manage resource subscriptions through a `subscriptions/listen`
   * stream instead of `resources/subscribe`; every other era is legacy.
   */
  isModernEra() {
    return this.protocolEra === "modern";
  }
  /**
   * Current state of the modern-era `subscriptions/listen` stream (#1630).
   * `active: false` on the legacy era (there is no persistent stream).
   */
  getResourceSubscriptionStreamState() {
    return this.modernStreamState;
  }
  setModernStreamState(next) {
    this.modernStreamState = next;
    this.dispatchTypedEvent("resourceSubscriptionStreamChange", next);
  }
  dispatchSubscriptionsChange() {
    this.dispatchTypedEvent(
      "resourceSubscriptionsChange",
      Array.from(this.subscribedResources)
    );
  }
  /**
   * The `subscriptions/listen` filter for the current modern subscriptions:
   * the subscribed URIs, plus the list-change opt-ins the Inspector already
   * tracks (config ∩ server capability) so the single stream also carries
   * list-change notifications — the spec models one listen stream for every
   * opted-in notification type (SEP §7.4).
   */
  buildSubscriptionFilter() {
    const filter = {};
    if (this.subscribedResources.size > 0) {
      filter.resourceSubscriptions = Array.from(this.subscribedResources);
    }
    if (this.listChangedNotifications.tools && this.capabilities?.tools?.listChanged) {
      filter.toolsListChanged = true;
    }
    if (this.listChangedNotifications.resources && this.capabilities?.resources?.listChanged) {
      filter.resourcesListChanged = true;
    }
    if (this.listChangedNotifications.prompts && this.capabilities?.prompts?.listChanged) {
      filter.promptsListChanged = true;
    }
    return filter;
  }
  /**
   * Whether the modern listen stream should be open: the built filter carries
   * something to listen for (#1920). Before #1920 this was "at least one URI is
   * subscribed", which made the stream unreachable on a server with no resources
   * — a tools-only server advertising `tools.listChanged` had no way to open it,
   * so `notifications/tools/list_changed` could never arrive. The filter already
   * modelled the list-change opt-ins; only the trigger was narrower than the
   * filter it built. This matches the SDK's own `ClientOptions.listChanged`
   * auto-open, which opens whenever the effective (config ∩ capability)
   * intersection is non-empty.
   *
   * Note this is deliberately *not* the same predicate as the stream state's
   * `active` — see `modernStreamActive()`.
   */
  wantsModernStream() {
    const filter = this.buildSubscriptionFilter();
    return (filter.resourceSubscriptions?.length ?? 0) > 0 || filter.toolsListChanged === true || filter.resourcesListChanged === true || filter.promptsListChanged === true;
  }
  /**
   * Whether the stream state reports `active` — i.e. whether the *Subscriptions*
   * UI has a stream to describe. That is a narrower question than
   * `wantsModernStream()`: `ResourceSubscriptionStreamState` drives the
   * Subscriptions section's badge, so a stream open purely for list-change
   * notifications (no subscribed URI) has nothing to report there and stays
   * `active: false` (#1920). Keeping the two apart also preserves the invariant
   * the rest of this file is written against — an empty subscribed set is never
   * announced alongside an `active` stream.
   */
  modernStreamActive() {
    return this.subscribedResources.size > 0;
  }
  /**
   * Open the modern listen stream at the end of a successful connect, when the
   * filter is non-empty (#1920). Only the list-change opt-ins can make it
   * non-empty here — the subscribed set is emptied by `resetSessionState()` on
   * the way in — so this is exactly the "server advertises a listChanged the
   * Inspector wants" case that had no trigger before.
   *
   * A failure is not allowed to fail the connect: the handshake succeeded and
   * every request-scoped feature works without this stream. It is reported the
   * way a lost stream is — hand it to the reconnect machinery, which retries
   * with backoff and settles on `"ended"` past the cap.
   *
   * Gated on the same generation test as `subscribeToResource` — see the long
   * comment there. The `connect` event has deliberately *not* been dispatched
   * yet (that is the point of running here), so a list-state consumer is not the
   * risk; what is, is anything else that bumps the generation while this
   * `listen()` is in flight. `statusChange` has already fired, and any
   * concurrent call on this instance qualifies: a `subscribeToResource` from a
   * caller restoring subscriptions, or a `disconnect()` — whose
   * `resetSubscriptionStream` bumps the generation too, making a reconcile here
   * arm a reconnect for a session that is already gone.
   */
  async openModernListenStreamOnConnect() {
    if (!this.isModernEra() || !this.wantsModernStream()) return;
    const generationBefore = this.modernListenGeneration;
    try {
      await this.refreshModernSubscription();
    } catch (error) {
      this.logger.error(
        { error },
        "Failed to open the modern subscriptions/listen stream on connect"
      );
      if (this.modernListenGeneration === generationBefore + 1) {
        this.reconcileModernStreamStateAfterFailedRefresh();
      }
    }
  }
  /** Cancel a pending reconnect re-listen, if any (#1630). */
  clearModernReconnectTimer() {
    if (this.modernReconnectTimer !== void 0) {
      clearTimeout(this.modernReconnectTimer);
      this.modernReconnectTimer = void 0;
    }
  }
  /**
   * (Re-)establish the modern `subscriptions/listen` stream to match the current
   * filter (#1630). Because the stream is not resumable, every filter change
   * re-lists: the existing stream is closed and a fresh `listen()` opened. With
   * an empty filter — no subscribed URIs *and* no enabled list-change opt-in the
   * server advertises — the stream is left closed (#1920).
   *
   * `modernListenGeneration` guards against races — if a newer refresh starts
   * while this one awaits its acknowledgement, the just-opened stream is
   * discarded rather than overwriting the newer one.
   */
  async refreshModernSubscription(fromReconnect = false) {
    if (!this.client) return;
    if (!fromReconnect) {
      this.clearModernReconnectTimer();
      this.modernReconnectAttempts = 0;
    }
    const generation = ++this.modernListenGeneration;
    const previous = this.modernSubscription;
    this.modernSubscription = null;
    if (previous) {
      await closeSubscriptionBestEffort(previous);
    }
    if (!this.wantsModernStream()) {
      this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
      return;
    }
    const subscription = await this.client.listen(
      this.buildSubscriptionFilter(),
      this.getRequestOptions()
    );
    if (generation !== this.modernListenGeneration) {
      await closeSubscriptionBestEffort(subscription);
      return;
    }
    this.modernSubscription = subscription;
    this.modernReconnectAttempts = 0;
    this.setModernStreamState({
      active: this.modernStreamActive(),
      status: "acknowledged",
      honoredUris: subscription.honoredFilter.resourceSubscriptions ?? []
    });
    void subscription.closed.then(
      (reason) => this.onModernSubscriptionClosed(subscription, reason, generation),
      // The rejection arm exists because a `closed` that rejects carries no
      // reason to act on, and an unhandled rejection ends a Node process by
      // default. It is scoped to the rejection rather than chained after the
      // handler because the handler cannot throw today — it only assigns state,
      // dispatches on a native `EventTarget` (which reports listener throws
      // rather than propagating them) and arms a timer — and chaining a
      // `.catch` after it would silently abandon a re-listen if that ever
      // changed. (Closing a stream *resolves* `closed`; what the connect-path
      // close newly reaches is the handler running at all, where the reference
      // used to be dropped with `closed` pending forever.)
      () => {
      }
    );
  }
  /**
   * Handle termination of a modern listen stream (#1630). `"remote"` is an
   * unexpected drop — reconnect by re-listing (no resumability, so the re-listen
   * re-establishes the full filter). `"local"` (we closed it) and `"graceful"`
   * (server shutdown) are expected and leave the stream ended.
   */
  onModernSubscriptionClosed(subscription, reason, generation) {
    if (generation !== this.modernListenGeneration || this.modernSubscription !== subscription) {
      return;
    }
    this.modernSubscription = null;
    const shouldReconnect = reason === "remote" && !isTerminalStatus(this.status) && this.wantsModernStream();
    if (!shouldReconnect) {
      this.setModernStreamState({
        active: this.modernStreamActive(),
        status: "ended",
        honoredUris: []
      });
      return;
    }
    this.scheduleModernReconnect();
  }
  /**
   * Reconcile the stream state after a re-listen *this* call owned and lost
   * (#1797). Only correct for a caller that has not been superseded — a newer
   * refresh owns the state as well as the filter — so both call sites gate on
   * the generation first.
   *
   * The two branches are the empty and non-empty *filter* (#1920) — which is
   * "nothing subscribed" only when no list-change opt-in is live. The empty case
   * is the ordinary one: nothing to listen for, no stream, inactive. The
   * non-empty one exists because a failed re-listen leaves `modernSubscription`
   * null with the filter still wanting a stream, and nothing else will notice: the reconnect machinery is reachable only from a stream that closed
   * or a reconnect that failed, and neither happened here. Left alone, the state
   * keeps whatever the last success (or the optimistic `"connecting"`) wrote — a
   * badge that will never change over subscriptions the server may never have
   * honored, recoverable only by an Unsubscribe/Subscribe toggle (a fresh
   * Subscribe early-returns on the URI already being in the set).
   *
   * So it reconnects rather than settling for an honest-but-dead `"ended"`:
   * every other route to "stream gone, filter live" either expects the close or
   * has exhausted the retry cap, and this is the one that has made no attempt
   * at all. `scheduleModernReconnect` fits as-is — a user-initiated refresh
   * already reset `modernReconnectAttempts`, so it starts at the base delay; the
   * timer bails on a terminal status or an emptied filter; and past the cap
   * `onModernReconnectFailed` lands on the same `"ended"` badge. The state
   * therefore becomes true or ends after a real attempt. The caller still sees
   * its error either way — the retry is about the subscriptions, not the call.
   */
  reconcileModernStreamStateAfterFailedRefresh() {
    if (!this.wantsModernStream()) {
      this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
      return;
    }
    this.scheduleModernReconnect();
  }
  /**
   * Schedule a reconnect re-listen after the current backoff delay (#1630).
   * `modernReconnectAttempts` reflects the number of *consecutive failed*
   * re-lists (reset to 0 on any successful acknowledgement), so the delay grows
   * only while re-listing keeps failing.
   */
  scheduleModernReconnect() {
    this.setModernStreamState({
      active: this.modernStreamActive(),
      status: "reconnecting",
      honoredUris: []
    });
    const delay = Math.min(
      MODERN_RECONNECT_BASE_MS * 2 ** this.modernReconnectAttempts,
      MODERN_RECONNECT_MAX_MS
    );
    this.clearModernReconnectTimer();
    this.modernReconnectTimer = setTimeout(() => {
      this.modernReconnectTimer = void 0;
      if (isTerminalStatus(this.status) || !this.wantsModernStream()) {
        return;
      }
      this.refreshModernSubscription(true).catch(
        () => this.onModernReconnectFailed()
      );
    }, delay);
  }
  /**
   * A reconnect re-listen failed (#1630). Count it and either retry with a
   * longer backoff or, past the consecutive-failure cap, give up and mark the
   * stream ended (re-subscribing resets the run and tries again).
   */
  onModernReconnectFailed() {
    this.modernReconnectAttempts += 1;
    if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
      this.setModernStreamState({
        active: this.modernStreamActive(),
        status: "ended",
        honoredUris: []
      });
      return;
    }
    this.scheduleModernReconnect();
  }
  /**
   * Subscribe to a resource to receive update notifications.
   *
   * Legacy era: sends `resources/subscribe`. Modern era (2026-07-28): adds the
   * URI to the `subscriptions/listen` filter and re-lists (#1630). In both eras
   * `notifications/resources/updated` is delivered through the same handler.
   *
   * @param uri - The URI of the resource to subscribe to
   * @throws Error if client is not connected or server doesn't support subscriptions
   */
  async subscribeToResource(uri) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    if (!this.supportsResourceSubscriptions()) {
      throw new Error("Server does not support resource subscriptions");
    }
    try {
      if (this.isModernEra()) {
        if (this.subscribedResources.has(uri)) return;
        this.subscribedResources.add(uri);
        this.dispatchSubscriptionsChange();
        this.setModernStreamState({
          active: true,
          status: "connecting",
          honoredUris: this.modernStreamState.honoredUris
        });
        const generationBefore = this.modernListenGeneration;
        try {
          await this.refreshModernSubscription();
        } catch (error) {
          if (this.modernListenGeneration === generationBefore + 1) {
            this.subscribedResources.delete(uri);
            this.reconcileModernStreamStateAfterFailedRefresh();
            this.dispatchSubscriptionsChange();
          }
          throw error;
        }
        return;
      }
      await this.client.subscribeResource({ uri }, this.getRequestOptions());
      this.subscribedResources.add(uri);
      this.dispatchSubscriptionsChange();
    } catch (error) {
      throw new Error(
        `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`,
        { cause: error }
      );
    }
  }
  /**
   * Unsubscribe from a resource.
   *
   * Legacy era: sends `resources/unsubscribe`. Modern era: drops the URI from
   * the `subscriptions/listen` filter and re-lists (closing the stream once the
   * last URI is removed) (#1630).
   *
   * @param uri - The URI of the resource to unsubscribe from
   * @throws Error if client is not connected
   */
  async unsubscribeFromResource(uri) {
    if (!this.client) {
      throw new Error("Client is not connected");
    }
    try {
      if (this.isModernEra()) {
        if (!this.subscribedResources.delete(uri)) return;
        if (this.subscribedResources.size === 0) {
          this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
        }
        this.dispatchSubscriptionsChange();
        const generationBefore = this.modernListenGeneration;
        try {
          await this.refreshModernSubscription();
        } catch (error) {
          if (this.modernListenGeneration === generationBefore + 1) {
            this.reconcileModernStreamStateAfterFailedRefresh();
          }
          throw error;
        }
      } else {
        await this.client.unsubscribeResource(
          { uri },
          this.getRequestOptions()
        );
        this.subscribedResources.delete(uri);
        this.dispatchSubscriptionsChange();
      }
    } catch (error) {
      throw new Error(
        `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`,
        { cause: error }
      );
    }
  }
  // ============================================================================
  // OAuth Support (delegated to oauthManager)
  // ============================================================================
  ensureOAuthManager() {
    if (!this.oauthManager) {
      throw new Error("OAuth not configured. Call setOAuthConfig() first.");
    }
    return this.oauthManager;
  }
  /**
   * Get server URL from transport config (full URL including path, for OAuth discovery)
   */
  getServerUrl() {
    if (this.transportConfig.type === "sse" || this.transportConfig.type === "streamable-http") {
      return this.transportConfig.url;
    }
    throw new Error(
      "OAuth is only supported for HTTP-based transports (SSE, streamable-http)"
    );
  }
  /**
   * Set OAuth configuration
   */
  setOAuthConfig(config) {
    if (!this.oauthManager) {
      throw new Error(
        "OAuth config must be set at creation. Pass oauth in constructor."
      );
    }
    this.oauthManager.setOAuthConfig(config);
  }
  /**
   * Initiates OAuth flow. Can be called directly by user or automatically
   * triggered by 401 errors.
   */
  async authenticate() {
    return this.ensureOAuthManager().authenticate();
  }
  /**
   * Satisfy a mid-session auth challenge (token refresh, step-up, or interactive re-auth).
   */
  async handleAuthChallenge(challenge, options) {
    return this.ensureOAuthManager().handleAuthChallenge(challenge, options);
  }
  /**
   * Re-read OAuth storage and test whether a challenge is already satisfied.
   * See {@link OAuthManager.checkAuthChallengeSatisfied}.
   */
  async checkAuthChallengeSatisfied(challenge) {
    return this.ensureOAuthManager().checkAuthChallengeSatisfied(challenge);
  }
  /**
   * Push recovered OAuth auth state to the remote backend (same MCP session).
   */
  async pushRemoteAuthState() {
    if (!(this.baseTransport instanceof RemoteClientTransport)) {
      return;
    }
    await this.baseTransport.pushAuthState();
  }
  /**
   * Handle an ambient (SSE) auth challenge when no command-scoped send is active.
   * Recovers session tokens on the remote backend; does not retry RPCs.
   */
  async handleAmbientAuthChallenge(challenge) {
    const key = this.ambientAuthChallengeKey(challenge);
    const existing = this.ambientAuthChallengeInFlight.get(key);
    if (existing) {
      return existing;
    }
    const promise = this.runAmbientAuthChallenge(challenge);
    this.ambientAuthChallengeInFlight.set(key, promise);
    try {
      await promise;
    } finally {
      if (this.ambientAuthChallengeInFlight.get(key) === promise) {
        this.ambientAuthChallengeInFlight.delete(key);
      }
    }
  }
  async runAmbientAuthChallenge(challenge) {
    try {
      this.dispatchTypedEvent("authChallengeAmbient", { challenge });
      const oauthManager = this.oauthManager;
      if (!oauthManager) {
        return;
      }
      const outcome = await oauthManager.handleAuthChallenge(challenge);
      if (outcome.kind === "satisfied") {
        if (this.baseTransport instanceof RemoteClientTransport) {
          await this.pushRemoteAuthState();
        } else {
          await this.reconnectAfterAuthRecovery();
        }
        this.dispatchTypedEvent("authChallengeRecovered", { challenge });
      } else if (outcome.kind === "step_up_confirm") {
        this.dispatchTypedEvent("authChallengeInteractive", {
          challenge: outcome.challenge,
          authorizationUrl: EMA_STEP_UP_PENDING_URL
        });
      } else if (outcome.kind === "interactive") {
        this.dispatchTypedEvent("authChallengeInteractive", {
          challenge: outcome.challenge,
          authorizationUrl: outcome.authorizationUrl
        });
      } else {
        this.dispatchTypedEvent("oauthError", { error: outcome.error });
      }
    } catch (error) {
      this.dispatchTypedEvent("oauthError", {
        error: error instanceof Error ? error : new Error(String(error))
      });
    }
  }
  ambientAuthChallengeKey(challenge) {
    const requiredScopes = [...challenge.requiredScopes ?? []].sort().join(" ");
    const authorizationScopes = [...challenge.authorizationScopes ?? []].sort().join(" ");
    return `${challenge.reason}:${requiredScopes}:${authorizationScopes}`;
  }
  /**
   * Full disconnect + reconnect after ambient auth recovery on direct transports.
   */
  async reconnectAfterAuthRecovery() {
    await this.disconnect().catch(() => {
    });
    await this.dropCachedTransport();
    await this.connect();
  }
  /** Direct (non-remote) OAuth transports recover via fetch intercept + handleAuthChallenge. */
  usesDirectAuthRecovery() {
    return this.directAuthRecovery && this.directAuthRecoveryActive === true;
  }
  /**
   * True when connect() sends the SDK's `server/discover` negotiation probe —
   * i.e. `protocolEra` is "auto", or "modern" (`{ pin: MODERN_PROTOCOL_VERSION }`).
   * Legacy is the default for an absent `mode`, matching the SDK.
   *
   * `versionNegotiation` is a public option, so a caller can pin a revision the
   * repo's own {@link eraToVersionNegotiation} never produces (it only ever
   * pins {@link MODERN_PROTOCOL_VERSION}). That is still "probing" and
   * deliberately reports true: per the SDK, *any* `{ pin }` sends the
   * connect-time `server/discover` — "the connect-time `server/discover` must
   * offer it. No fallback" — so the pinned revision doesn't change whether the
   * probe (and hence the buried-401 problem) happens. Only "legacy" skips it.
   */
  probesProtocolEra() {
    const mode = this.versionNegotiation.mode;
    return mode !== void 0 && mode !== "legacy";
  }
  async withDirectAuthRecovery(operation, context, attempt = 0) {
    try {
      return await operation(attempt);
    } catch (err) {
      if (attempt >= 1 || !this.usesDirectAuthRecovery()) {
        throw err;
      }
      if (!isAuthChallengeError(err)) {
        throw err;
      }
      if (this.authRecoveryDepth >= _InspectorClient.MAX_NESTED_AUTH_RECOVERIES) {
        throw err;
      }
      const challenge = parseAuthChallengeFromError(err, context);
      if (!challenge) {
        throw err;
      }
      if (context?.method || context?.toolName) {
        this.dispatchTypedEvent("authChallengeCommand", { challenge });
      } else {
        this.dispatchTypedEvent("authChallengeAmbient", { challenge });
      }
      const outcome = await this.handleAuthChallenge(challenge);
      if (outcome.kind === "satisfied") {
        if (this.activeToolCallAbortController) {
          this.activeToolCallAbortController = void 0;
        }
        this.authRecoveryDepth += 1;
        try {
          await this.reconnectAfterAuthRecovery();
        } finally {
          this.authRecoveryDepth -= 1;
        }
        this.dispatchTypedEvent("authChallengeRecovered", { challenge });
        return this.withDirectAuthRecovery(operation, context, attempt + 1);
      }
      if (outcome.kind === "step_up_confirm") {
        throw new AuthRecoveryRequiredError(
          EMA_STEP_UP_PENDING_URL,
          outcome.challenge,
          { emaStepUpConfirm: true }
        );
      }
      if (outcome.kind === "interactive") {
        throw new AuthRecoveryRequiredError(
          outcome.authorizationUrl,
          outcome.challenge
        );
      }
      this.dispatchTypedEvent("oauthError", { error: outcome.error });
      throw outcome.error;
    }
  }
  async invokeMcpClient(operation, context) {
    if (!this.usesDirectAuthRecovery()) {
      return operation(0);
    }
    return this.withDirectAuthRecovery(operation, context);
  }
  /**
   * Completes OAuth flow with authorization code from the redirect callback.
   * Direct transports reconnect after token exchange so the live MCP session
   * picks up the new Bearer token (mirrors silent recovery reconnect).
   */
  async completeOAuthFlow(authorizationCode, iss) {
    await this.ensureOAuthManager().completeOAuthFlow(authorizationCode, iss);
    if (this.usesDirectAuthRecovery()) {
      await this.reconnectAfterAuthRecovery();
    }
  }
  /**
   * Navigate to the authorization server for interactive recovery.
   */
  async beginInteractiveAuthorization(authorizationUrl) {
    return this.ensureOAuthManager().beginInteractiveAuthorization(
      authorizationUrl
    );
  }
  /** Remote Hono session id when using {@link RemoteClientTransport}. */
  getRemoteBackendSessionId() {
    if (this.baseTransport instanceof RemoteClientTransport) {
      return this.baseTransport.getRemoteBackendSessionId();
    }
    return void 0;
  }
  /**
   * Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
   */
  async resumeAfterOAuth(authorizationCode, options) {
    await this.completeOAuthFlow(authorizationCode, options?.iss);
    const remoteSessionId = options?.remoteSessionId;
    const transport = this.baseTransport;
    if (remoteSessionId && transport instanceof RemoteClientTransport) {
      try {
        await transport.attachToSession(remoteSessionId);
        await transport.pushAuthState();
        if (this.status !== "connected") {
          await this.connect();
        }
        return;
      } catch {
      }
    }
    if (this.status !== "connected") {
      await this.connect();
    }
  }
  /**
   * Gets current OAuth tokens (if authorized)
   */
  async getOAuthTokens() {
    if (!this.oauthManager) {
      return void 0;
    }
    return this.oauthManager.getOAuthTokens();
  }
  /**
   * Clears OAuth tokens and client information
   */
  async clearOAuthTokens() {
    await this.oauthManager?.clearOAuthTokens();
  }
  /**
   * Checks if client is currently OAuth authorized
   */
  async isOAuthAuthorized() {
    if (!this.oauthManager) {
      return false;
    }
    return this.oauthManager.isOAuthAuthorized();
  }
  /**
   * In-memory OAuth flow snapshot. Undefined when no flow has run on this
   * client instance; use {@link getOAuthState} for persisted authorization state.
   */
  getOAuthFlowState() {
    return this.oauthManager?.getOAuthFlowState();
  }
  /** Current step when an OAuth flow is active. */
  getOAuthFlowStep() {
    return this.oauthManager?.getOAuthFlowStep();
  }
  /**
   * Persisted OAuth authorization snapshot for this HTTP server (storage +
   * config). Undefined for stdio transports or when OAuth is not configured.
   */
  async getOAuthState() {
    if (!this.isHttpOAuthConfig() || !this.oauthManager) {
      return void 0;
    }
    return this.oauthManager.getOAuthState();
  }
};

// ../../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);
}
function isLoopbackHost(host) {
  const h = canonicalUrlHost(host).replace(/\.$/, "");
  return h === "localhost" || h === "[::1]" || /^127(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/.test(h);
}

// src/error-handler.ts
var EXIT_CODES = {
  OK: 0,
  USAGE: 1,
  NO_APP: 2,
  AUTH_REQUIRED: 3,
  UNREACHABLE: 4,
  TOOL_ERROR: 5,
  SCHEMA_UNPORTABLE: 6
};
var CliExitCodeError = class extends Error {
  constructor(exitCode, message, envelope) {
    super(message);
    this.exitCode = exitCode;
    this.envelope = envelope;
    this.name = "CliExitCodeError";
  }
  exitCode;
  envelope;
};
var MAX_CAUSE_DEPTH = 16;
function causeOf(error, depth = 0) {
  if (depth >= MAX_CAUSE_DEPTH) return void 0;
  if (!(error instanceof Error)) return void 0;
  const cause = error.cause;
  if (cause === void 0 || cause === null) return void 0;
  if (cause instanceof Error) {
    const nested = causeOf(cause, depth + 1);
    return nested ? `${cause.message}: ${nested}` : cause.message;
  }
  return String(cause);
}
function statusOf(error) {
  if (error == null || typeof error !== "object") return void 0;
  const e = error;
  if (typeof e.status === "number") return e.status;
  if (typeof e.response?.status === "number") return e.response.status;
  if (typeof e.code === "number" && e.code >= 100 && e.code <= 599) {
    return e.code;
  }
  return void 0;
}
var UNREACHABLE_PATTERN = /ENOTFOUND|ECONNREFUSED|ECONNRESET|EAI_AGAIN|ETIMEDOUT|fetch failed|getaddrinfo|connect timed out|aborted/i;
function classifyError(error, context) {
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
  const cause = causeOf(error);
  const status = statusOf(error);
  const url = context?.url;
  if (error instanceof CliExitCodeError) {
    return {
      exitCode: error.exitCode,
      envelope: {
        code: error.envelope?.code ?? codeForExit(error.exitCode),
        message,
        ...cause !== void 0 && { cause },
        ...error.envelope?.status !== void 0 && {
          status: error.envelope.status
        },
        ...(error.envelope?.url ?? url) !== void 0 && {
          url: error.envelope?.url ?? url
        }
      }
    };
  }
  if (status === 401 || status === 403 || /WWW-Authenticate|Unauthorized|invalid_token|OAuth/i.test(
    message + " " + (cause ?? "")
  )) {
    return {
      exitCode: EXIT_CODES.AUTH_REQUIRED,
      envelope: {
        code: "auth_required",
        message,
        ...cause !== void 0 && { cause },
        ...status !== void 0 && { status },
        ...url !== void 0 && { url }
      }
    };
  }
  if (UNREACHABLE_PATTERN.test(message + " " + (cause ?? ""))) {
    return {
      exitCode: EXIT_CODES.UNREACHABLE,
      envelope: {
        code: "unreachable",
        message,
        ...cause !== void 0 && { cause },
        ...status !== void 0 && { status },
        ...url !== void 0 && { url }
      }
    };
  }
  return {
    exitCode: EXIT_CODES.USAGE,
    envelope: {
      code: "error",
      message,
      ...cause !== void 0 && { cause },
      ...status !== void 0 && { status },
      ...url !== void 0 && { url }
    }
  };
}
function codeForExit(exitCode) {
  switch (exitCode) {
    case EXIT_CODES.NO_APP:
      return "no_app";
    case EXIT_CODES.AUTH_REQUIRED:
      return "auth_required";
    case EXIT_CODES.UNREACHABLE:
      return "unreachable";
    case EXIT_CODES.TOOL_ERROR:
      return "tool_error";
    case EXIT_CODES.SCHEMA_UNPORTABLE:
      return "schema_unportable";
    default:
      return "error";
  }
}
function formatErrorOutput(error, context) {
  const { exitCode, envelope } = classifyError(error, context);
  return {
    exitCode,
    stderr: JSON.stringify({ error: envelope }) + "\n"
  };
}
async function handleError(error) {
  const { exitCode, stderr } = formatErrorOutput(error);
  await awaitableError(stderr);
  process.exit(exitCode);
}

// ../../core/json/schemaLint.ts
var BOOLEAN_VALUED_KEYWORDS = /* @__PURE__ */ new Set([
  "additionalProperties",
  "unevaluatedProperties",
  "additionalItems",
  "unevaluatedItems"
]);
var SUBSCHEMA_KEYWORDS = [
  "items",
  "contains",
  "not",
  "propertyNames",
  "if",
  "then",
  "else",
  "additionalProperties",
  "unevaluatedProperties",
  "additionalItems",
  "unevaluatedItems",
  // 2019-09/2020-12: the schema the *decoded* string content must satisfy.
  // A real schema position, so a bare `true` or a remote `$ref` under it is a
  // real finding.
  "contentSchema"
];
var SUBSCHEMA_ARRAY_KEYWORDS = [
  "allOf",
  "anyOf",
  "oneOf",
  "prefixItems"
];
var SUBSCHEMA_MAP_KEYWORDS = [
  "properties",
  "patternProperties",
  "dependentSchemas",
  "dependencies",
  "$defs",
  "definitions"
];
var CONSTRAINING_KEYWORDS = /* @__PURE__ */ new Set([
  // Assertions
  "type",
  "enum",
  "const",
  "multipleOf",
  "maximum",
  "exclusiveMaximum",
  "minimum",
  "exclusiveMinimum",
  "maxLength",
  "minLength",
  "pattern",
  "format",
  "contentMediaType",
  "contentEncoding",
  "contentSchema",
  "maxItems",
  "minItems",
  "uniqueItems",
  "maxContains",
  "minContains",
  "maxProperties",
  "minProperties",
  "required",
  "dependentRequired",
  // Applicators
  "allOf",
  "anyOf",
  "oneOf",
  "not",
  // `if` / `then` / `else` are deliberately absent — they only constrain as a
  // PAIR. `if` alone has no assertion effect, and `then` / `else` are ignored
  // without it, so `{"if": {"const": 1}}` accepts every value. The pairing is
  // checked separately in `constrainsInstance`.
  "items",
  "prefixItems",
  "contains",
  "additionalItems",
  "unevaluatedItems",
  "properties",
  "patternProperties",
  "additionalProperties",
  "unevaluatedProperties",
  "propertyNames",
  "dependentSchemas",
  "dependencies",
  // References
  "$ref",
  "$dynamicRef",
  "$recursiveRef"
]);
var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Set([
  "null",
  "boolean",
  "object",
  "array",
  "number",
  "string",
  "integer"
]);
function constrainsInstance(node) {
  if (Object.keys(node).some((key) => CONSTRAINING_KEYWORDS.has(key))) {
    return true;
  }
  return "if" in node && ("then" in node || "else" in node);
}
function isPortableTypeUnion(type) {
  if (type.length === 0) return false;
  if (!type.every((t) => typeof t === "string" && JSON_SCHEMA_TYPES.has(t))) {
    return false;
  }
  return new Set(type).size === type.length;
}
var MAX_DEPTH = 64;
function isRecord3(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function childPath(parent, key) {
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
    return parent === "" ? key : `${parent}.${key}`;
  }
  return `${parent}[${JSON.stringify(key)}]`;
}
function indexPath(parent, index) {
  return `${parent}[${index}]`;
}
function describeSchemaPath(schema, path5) {
  return path5 === "" ? schema : `${schema}.${path5}`;
}
function declaresAnyId(node, depth = 0) {
  if (depth > MAX_DEPTH || !isRecord3(node)) return false;
  if (typeof node.$id === "string" && node.$id !== "") return true;
  let found = false;
  forEachSubschema(node, "", (child) => {
    if (!found) found = declaresAnyId(child, depth + 1);
  });
  return found;
}
function add(ctx, rule, severity, path5, issue, suggestion) {
  ctx.findings.push({
    rule,
    severity,
    schema: ctx.schema,
    path: path5,
    issue,
    suggestion
  });
}
function forEachSubschema(node, path5, visit) {
  for (const key of SUBSCHEMA_MAP_KEYWORDS) {
    const map = node[key];
    if (!isRecord3(map)) continue;
    for (const [name, sub] of Object.entries(map)) {
      visit(sub, childPath(childPath(path5, key), name), key);
    }
  }
  for (const key of SUBSCHEMA_ARRAY_KEYWORDS) {
    const arr = node[key];
    if (!Array.isArray(arr)) continue;
    arr.forEach((sub, i) => {
      visit(sub, indexPath(childPath(path5, key), i), key);
    });
  }
  for (const key of SUBSCHEMA_KEYWORDS) {
    if (!(key in node)) continue;
    const value = node[key];
    if (key === "items" && Array.isArray(value)) {
      value.forEach((sub, i) => {
        visit(sub, indexPath(childPath(path5, key), i), key);
      });
      continue;
    }
    visit(value, childPath(path5, key), key);
  }
}
function booleanTrueSuggestion(keyword) {
  if (keyword === "not") {
    return "Use `{}`, the object form of the always-true schema \u2014 under `not` that is an exact replacement. (Declaring a concrete type here would instead let every value of another type through.)";
  }
  return 'Declare what the value actually is \u2014 e.g. `{"type": "object", "additionalProperties": true}` for a free-form object. That is a deliberate change of contract, not an equivalent rewrite: `true` accepts any JSON value at all.';
}
function walk(node, path5, keyword, depth, ctx) {
  if (depth > MAX_DEPTH) return;
  if (typeof node === "boolean") {
    if (keyword !== void 0 && BOOLEAN_VALUED_KEYWORDS.has(keyword)) return;
    add(
      ctx,
      "boolean-schema",
      "error",
      path5,
      `Bare \`${String(node)}\` used where a schema object is expected.`,
      node ? booleanTrueSuggestion(keyword) : (
        // `false` in a property position means the property is FORBIDDEN.
        // Deleting the entry is not the same thing: with `additionalProperties`
        // absent (default `true`) the property becomes allowed with any value,
        // so the suggestion has to preserve the always-false constraint.
        'Use the object form of the always-false schema \u2014 `{"not": {}}`. (Deleting the entry is a different contract: under the default `additionalProperties` the property would then be allowed with any value, not forbidden.)'
      )
    );
    return;
  }
  if (!isRecord3(node)) return;
  lintNode(node, path5, keyword, ctx);
  forEachSubschema(node, path5, (child, childPathValue, childKeyword) => {
    walk(child, childPathValue, childKeyword, depth + 1, ctx);
  });
}
function lintNode(node, path5, keyword, ctx) {
  const type = node.type;
  if (Array.isArray(type) && isPortableTypeUnion(type)) {
    add(
      ctx,
      "type-union",
      "warning",
      path5,
      `\`type\` is an array (${JSON.stringify(type)}). The array form is legal JSON Schema, but several MCP clients read \`type\` as a single string and either reject the tool or drop the constraint.`,
      `Split it into \`anyOf\` branches, each with a single \`type\` \u2014 \`{"anyOf": [${type.map((t) => `{"type": "${t}"}`).join(
        ", "
      )}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)`
    );
  }
  const ref = node.$ref;
  if (typeof ref === "string" && ref !== "" && !ref.startsWith("#") && !ctx.hasEmbeddedIds) {
    add(
      ctx,
      "remote-ref",
      "warning",
      path5,
      `\`$ref\` points outside this document (\`${ref}\`). Clients do not fetch remote schemas, so the constraint is dropped or the tool is rejected.`,
      "Inline the referenced schema, or move it into `$defs` and reference it as `#/$defs/<name>`."
    );
  }
  const constrains = constrainsInstance(node);
  if (!constrains && keyword !== "not") {
    add(
      ctx,
      "untyped-schema",
      "warning",
      path5,
      "Schema carries no validation keyword at all, so it accepts any value \u2014 the object-literal spelling of a bare `true`.",
      // "changes", not "narrows": in an ordinary property position it does
      // narrow, but under a conditional (`if`) a typed replacement redirects
      // which branch applies rather than tightening anything.
      'Declare what the value actually is \u2014 e.g. `{"type": "object", "additionalProperties": true}` for a free-form object. That is a deliberate change of contract, which is the point: as written it constrains nothing.'
    );
  }
}
function lintSchema(schema, kind, findings) {
  walk(schema, "", void 0, 0, {
    schema: kind,
    findings,
    hasEmbeddedIds: declaresAnyId(schema)
  });
}
function lintToolSchemas(tool) {
  const findings = [];
  if (tool.inputSchema !== void 0) {
    lintSchema(tool.inputSchema, "inputSchema", findings);
  }
  if (tool.outputSchema !== void 0) {
    lintSchema(tool.outputSchema, "outputSchema", findings);
  }
  return findings;
}
function lintTools(tools) {
  const results = [];
  for (const tool of tools) {
    const findings = lintToolSchemas(tool);
    if (findings.length > 0) results.push({ toolName: tool.name, findings });
  }
  return results;
}
function countFindings(results) {
  let errors = 0;
  let warnings = 0;
  for (const { findings } of results) {
    for (const f of findings) {
      if (f.severity === "error") errors++;
      else warnings++;
    }
  }
  return { errors, warnings };
}
function plural(count, noun) {
  return `${count} ${noun}${count === 1 ? "" : "s"}`;
}
function summarizeFindings(results) {
  const { errors, warnings } = countFindings(results);
  return `${plural(errors, "error")}, ${plural(warnings, "warning")} across ${plural(results.length, "tool")}`;
}
function formatSchemaLintReport(results) {
  const lines = [];
  for (const { toolName, findings } of results) {
    for (const f of findings) {
      lines.push(
        `${f.severity === "error" ? "Error" : "Warning"}: tool "${toolName}"`
      );
      lines.push(`  Path: ${describeSchemaPath(f.schema, f.path)}`);
      lines.push(`  Issue: ${f.issue}`);
      lines.push(`  Suggestion: ${f.suggestion}`);
      lines.push("");
    }
  }
  lines.push(`${summarizeFindings(results)}.`);
  return lines.join("\n");
}

// src/handlers/schema-lint-report.ts
function toolsFromResult(result) {
  const tools = result.tools;
  if (!Array.isArray(tools)) return [];
  return tools.filter(
    (t) => typeof t === "object" && t !== null && typeof t.name === "string"
  );
}
function lintListResult(result) {
  return lintTools(toolsFromResult(result));
}
async function writeSchemaLintReport(results, strict) {
  if (results.length === 0) return;
  if (!strict) {
    await awaitableError(
      `Schema portability: ${summarizeFindings(results)}. Re-run with --strict for details.
`
    );
    return;
  }
  await awaitableError(`${formatSchemaLintReport(results)}
`);
}

// src/handlers/emit-result.ts
async function emitResult(result, appInfo, args) {
  const json = args.format === "json";
  if (args.appInfo) {
    const info = appInfo ?? {
      hasApp: false,
      toolName: args.toolName ?? ""
    };
    await awaitableLog(JSON.stringify(json ? { appInfo: info } : info) + "\n");
    if (!info.hasApp) {
      throw new CliExitCodeError(
        EXIT_CODES.NO_APP,
        `Tool '${args.toolName}' has no MCP App UI resource (_meta.ui.resourceUri).`
      );
    }
    return;
  }
  const lint = args.method === "tools/list" ? lintListResult(result) : void 0;
  if (json) {
    const envelope = { result };
    if (appInfo?.hasApp) envelope.appInfo = appInfo;
    if (args.strict && lint && lint.length > 0) envelope.schemaFindings = lint;
    await awaitableLog(JSON.stringify(envelope) + "\n");
  } else {
    await awaitableLog(JSON.stringify(result, null, 2) + "\n");
  }
  if (lint) await writeSchemaLintReport(lint, args.strict === true);
  if (result.isError === true) {
    throw new CliExitCodeError(
      EXIT_CODES.TOOL_ERROR,
      `Tool '${args.toolName}' returned isError:true.`,
      { code: "tool_is_error" }
    );
  }
  if (args.strict && lint) {
    const { errors } = countFindings(lint);
    if (errors > 0) {
      throw new CliExitCodeError(
        EXIT_CODES.SCHEMA_UNPORTABLE,
        `${errors} tool schema portability error${errors === 1 ? "" : "s"} found (--strict).`,
        { code: "schema_unportable" }
      );
    }
  }
}

// src/handlers/consume-outcome.ts
async function consumeMethodOutcome(outcome, args) {
  if (outcome.kind === "result") {
    await emitResult(outcome.result, outcome.appInfo, args);
    return;
  }
  if (outcome.kind === "ndjson") {
    for (const line of outcome.lines) {
      await awaitableLog(JSON.stringify(line) + "\n");
    }
    return;
  }
  await new Promise((resolve7) => {
    const stop = outcome.start((obj) => {
      void awaitableLog(JSON.stringify(obj) + "\n");
    });
    const onSignal = () => {
      stop();
      process.off("SIGINT", onSignal);
      process.off("SIGTERM", onSignal);
      resolve7();
    };
    process.on("SIGINT", onSignal);
    process.on("SIGTERM", onSignal);
  });
}

// ../../core/mcp/state/managedListState.ts
var DEFAULT_LIST_CHANGED_DEBOUNCE_MS = 250;
var ManagedListState = class extends TypedEventTarget {
  items = [];
  client = null;
  unsubscribe = null;
  _metadata = void 0;
  listChanged = false;
  // The last fetch's failure, kept as observable state so a load that fails
  // (a transport error, or a result the SDK codec rejects as invalid) is
  // rendered by the list panel instead of vanishing into an unhandled
  // rejection — which read as "this server has no tools" (#1953).
  error = null;
  config;
  // Debounce a burst of `list_changed` notifications into a single
  // refresh (or one indicator light) once it settles.
  listChangedTimer = null;
  // Second line of defense beyond the debounce, for the auto-refresh path:
  // while a refresh is fetching, a new (post-debounce) notification queues a
  // single re-run instead
  // of firing another concurrent paginated fetch.
  running = false;
  runQueued = false;
  constructor(client, config) {
    super();
    this.client = client;
    this.config = config;
    const onConnect = () => {
      if (this.config.deferWhenPaginated && this.client?.getServerSettings()?.paginatedLists) {
        return;
      }
      void this.refresh().catch(() => {
      });
    };
    const onListChanged = () => {
      if (this.listChangedTimer !== null) clearTimeout(this.listChangedTimer);
      this.listChangedTimer = setTimeout(() => {
        this.listChangedTimer = null;
        this.runListChanged();
      }, config.debounceMs);
    };
    const onStatusChange = () => {
      if (isTerminalStatus(this.client?.getStatus())) {
        if (this.listChangedTimer !== null) {
          clearTimeout(this.listChangedTimer);
          this.listChangedTimer = null;
        }
        this.items = [];
        this.dispatchChange();
        this.setListChanged(false);
        this.setError(null);
      }
    };
    this.client.addEventListener("connect", onConnect);
    this.client.addEventListener(config.listChangedEvent, onListChanged);
    this.client.addEventListener("statusChange", onStatusChange);
    this.unsubscribe = () => {
      if (this.listChangedTimer !== null) {
        clearTimeout(this.listChangedTimer);
        this.listChangedTimer = null;
      }
      if (this.client) {
        this.client.removeEventListener("connect", onConnect);
        this.client.removeEventListener(config.listChangedEvent, onListChanged);
        this.client.removeEventListener("statusChange", onStatusChange);
      }
      this.client = null;
    };
  }
  /**
   * The debounced list-changed action. With auto-refresh on, pull the new list
   * (guarded against overlap so a slow older fetch can't clobber a newer list
   * via last-write-wins `applyItems`). With auto-refresh off, lights the
   * indicator without any network — the user pulls the new list via Refresh
   * (#1402, #1444). Lists without an indicator (resource templates) do nothing
   * in the off case.
   */
  runListChanged() {
    if (this.running) {
      this.runQueued = true;
      return;
    }
    void this.runListChangedOnce();
  }
  async runListChangedOnce() {
    this.running = true;
    try {
      do {
        this.runQueued = false;
        const settings = this.client?.getServerSettings();
        const skipAggregate = this.config.deferWhenPaginated && settings?.paginatedLists;
        if (!skipAggregate && settings?.autoRefreshOnListChanged) {
          await this.refresh(void 0, "refresh").catch(() => {
          });
        } else if (this.config.supportsIndicator) {
          this.setListChanged(true);
        }
      } while (this.runQueued);
    } finally {
      this.running = false;
    }
  }
  /** Defensive copy of the current list. */
  getItems() {
    return [...this.items];
  }
  /** Whether a `list_changed` arrived since the last refresh (indicator on). */
  getListChanged() {
    return this.listChanged;
  }
  /**
   * Clear the list-changed flag — called when the user refreshes the list. The
   * blind light on the notification leaves the indicator set until the user
   * acknowledges by pulling.
   */
  clearListChanged() {
    this.setListChanged(false);
  }
  /**
   * Dispatch a configured event by name. `dispatchTypedEvent`'s
   * `EventMap[K] extends void ? [] : [detail]` overload can't resolve when the
   * key is a generic `keyof M`, so we narrow the method signature here. The
   * concrete subclass event maps keep the call sites type-safe.
   */
  emit(type, detail) {
    this.dispatchTypedEvent(
      type,
      detail
    );
  }
  setListChanged(value) {
    if (this.listChanged === value) return;
    this.listChanged = value;
    this.emit("listChangedChange", value);
  }
  /** The last fetch's failure, or `null` when the last fetch succeeded. */
  getError() {
    return this.error;
  }
  // Compared by identity rather than message: two distinct failures with the
  // same text are still two events, and a re-render on a repeat failure is
  // cheap next to silently coalescing them.
  setError(value) {
    if (this.error === value) return;
    this.error = value;
    this.emit("errorChange", value);
  }
  setMetadata(metadata) {
    this._metadata = metadata;
  }
  /**
   * Refresh the full list. `cacheMode` selects the SDK cache disposition for
   * this fetch: `undefined` (the connect-time load) uses the default `'use'`;
   * a user-initiated or auto refresh passes `'refresh'` to force a
   * cache-bypassing round trip and re-store the fresh aggregate.
   *
   * A failure is recorded as observable state (`getError`) AND re-thrown: the
   * state drives the panel's error rendering, while the rejection is what the
   * caller's auth-recovery wrapper keys off to detect a 401 and start a
   * re-authorization. Callers with nobody to await them (the connect-time load,
   * the `list_changed` auto-refresh) catch it explicitly (#1953).
   */
  async refresh(metadata, cacheMode) {
    let next;
    try {
      next = await this.fetchItems(metadata, cacheMode);
    } catch (err) {
      const error = err instanceof Error ? err : new Error(String(err));
      this.setError(error);
      if (isClientDecodeRejection(err)) {
        this.client?.markResponseRejected?.(
          this.config.listMethod,
          error.message
        );
      }
      throw err;
    }
    this.setError(null);
    if (next === null) return this.getItems();
    this.applyItems(next);
    return this.getItems();
  }
  /**
   * Fetch the complete list (all pages, via the SDK's cache-aware verb), then
   * `applyItems` commits it (see `refresh`). Returns `null` when not connected,
   * or `[]` when the server doesn't advertise the gating capability (calling
   * the list method there returns -32601 "Method not found", which would spam
   * the console; empty list is the right semantics).
   */
  async fetchItems(metadata, cacheMode) {
    const client = this.client;
    if (!client || client.getStatus() !== "connected") return null;
    if (!client.getCapabilities()?.[this.config.capabilityKey]) return [];
    const effectiveMetadata = metadata ?? this._metadata;
    return this.config.fetchAll(client, cacheMode, effectiveMetadata);
  }
  /** Commit a fetched list as the current one and notify subscribers. */
  applyItems(items) {
    this.items = items;
    this.dispatchChange();
  }
  dispatchChange() {
    this.emit(this.config.changeEvent, this.getItems());
  }
  /** Unsubscribe from the client and drop the list; idempotent. */
  destroy() {
    this.unsubscribe?.();
    this.unsubscribe = null;
    this.items = [];
  }
};

// ../../core/mcp/state/managedToolsState.ts
var ManagedToolsState = class extends ManagedListState {
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
    super(client, {
      listMethod: "tools/list",
      changeEvent: "toolsChange",
      listChangedEvent: "toolsListChanged",
      capabilityKey: "tools",
      deferWhenPaginated: true,
      supportsIndicator: true,
      debounceMs,
      fetchAll: async (c, cacheMode, metadata) => {
        const result = await c.listAllTools({ cacheMode, metadata });
        return result.tools;
      }
    });
  }
  getTools() {
    return this.getItems();
  }
};

// ../../core/mcp/state/messageLogState.ts
var MessageLogState = class extends TypedEventTarget {
  messages = [];
  /** Pending request entries by JSON-RPC message id for matching responses. */
  pendingRequestEntries = /* @__PURE__ */ new Map();
  client = null;
  unsubscribe = null;
  maxMessages;
  constructor(client, options = {}) {
    super();
    this.maxMessages = options.maxMessages ?? 1e3;
    this.client = client;
    const pushEntry = (entry) => {
      if (this.maxMessages > 0 && this.messages.length >= this.maxMessages) {
        this.messages.shift();
      }
      this.messages.push(entry);
      this.dispatchTypedEvent("message", entry);
      this.dispatchTypedEvent("messagesChange", this.getMessages());
    };
    const onMessage = (event) => {
      const entry = event.detail;
      if (entry.direction === "request") {
        const reqId = "id" in entry.message ? entry.message.id : void 0;
        if (reqId !== void 0) {
          this.pendingRequestEntries.set(reqId, entry);
        }
        pushEntry(entry);
        return;
      }
      if (entry.direction === "response") {
        const messageId = "id" in entry.message ? entry.message.id : void 0;
        const requestEntry = messageId !== void 0 ? this.pendingRequestEntries.get(messageId) : void 0;
        if (requestEntry) {
          this.pendingRequestEntries.delete(messageId);
          requestEntry.response = entry.message;
          requestEntry.duration = entry.timestamp.getTime() - requestEntry.timestamp.getTime();
          this.dispatchTypedEvent("message", requestEntry);
          this.dispatchTypedEvent("messagesChange", this.getMessages());
          return;
        }
      }
      pushEntry(entry);
    };
    const onResponseRejected = (event) => {
      const { id, reason } = event.detail;
      for (let i = this.messages.length - 1; i >= 0; i--) {
        const entry = this.messages[i];
        const entryId = entry.message.id;
        if (entryId !== id) continue;
        if (entry.direction === "notification") continue;
        if (entry.direction === "request" && !entry.response) continue;
        entry.clientError = reason;
        this.dispatchTypedEvent("message", entry);
        this.dispatchTypedEvent("messagesChange", this.getMessages());
        return;
      }
    };
    const onStatusChange = () => {
      if (isTerminalStatus(this.client?.getStatus())) {
        this.messages = [];
        this.pendingRequestEntries.clear();
        this.dispatchTypedEvent("messagesChange", []);
      }
    };
    this.client.addEventListener("message", onMessage);
    this.client.addEventListener("responseRejected", onResponseRejected);
    this.client.addEventListener("statusChange", onStatusChange);
    this.unsubscribe = () => {
      if (this.client) {
        this.client.removeEventListener("message", onMessage);
        this.client.removeEventListener("responseRejected", onResponseRejected);
        this.client.removeEventListener("statusChange", onStatusChange);
      }
      this.client = null;
    };
  }
  getMessages(predicate) {
    if (predicate) {
      return this.messages.filter(predicate);
    }
    return [...this.messages];
  }
  /**
   * Remove messages from history. When `predicate` is provided, removes only
   * entries for which predicate returns true. When omitted, clears all messages.
   * Dispatches messagesChange only if the list actually changed.
   */
  clearMessages(predicate) {
    const before = this.messages.length;
    this.messages = predicate ? this.messages.filter((m) => !predicate(m)) : [];
    if (predicate) {
      const survivors = new Set(this.messages);
      for (const [id, entry] of this.pendingRequestEntries) {
        if (!survivors.has(entry)) {
          this.pendingRequestEntries.delete(id);
        }
      }
    } else {
      this.pendingRequestEntries.clear();
    }
    if (this.messages.length !== before) {
      this.dispatchTypedEvent("messagesChange", this.getMessages());
    }
  }
  destroy() {
    this.unsubscribe?.();
    this.unsubscribe = null;
    this.messages = [];
    this.pendingRequestEntries.clear();
  }
};

// ../../core/mcp/state/managedResourcesState.ts
var ManagedResourcesState = class extends ManagedListState {
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
    super(client, {
      listMethod: "resources/list",
      changeEvent: "resourcesChange",
      listChangedEvent: "resourcesListChanged",
      capabilityKey: "resources",
      deferWhenPaginated: true,
      supportsIndicator: true,
      debounceMs,
      fetchAll: async (c, cacheMode, metadata) => {
        const result = await c.listAllResources({ cacheMode, metadata });
        return result.resources;
      }
    });
  }
  getResources() {
    return this.getItems();
  }
};

// ../../core/mcp/state/managedResourceTemplatesState.ts
var ManagedResourceTemplatesState = class extends ManagedListState {
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
    super(client, {
      listMethod: "resources/templates/list",
      changeEvent: "resourceTemplatesChange",
      listChangedEvent: "resourceTemplatesListChanged",
      // Templates are gated on the broader `resources` capability.
      capabilityKey: "resources",
      // No paged counterpart (templates aren't paginated in the UI), so they
      // must still aggregate on connect even in paginated mode (#1721).
      deferWhenPaginated: false,
      supportsIndicator: false,
      debounceMs,
      fetchAll: async (c, cacheMode, metadata) => {
        const result = await c.listAllResourceTemplates({
          cacheMode,
          metadata
        });
        return result.resourceTemplates;
      }
    });
  }
  getResourceTemplates() {
    return this.getItems();
  }
};

// ../../core/mcp/state/managedPromptsState.ts
var ManagedPromptsState = class extends ManagedListState {
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
    super(client, {
      listMethod: "prompts/list",
      changeEvent: "promptsChange",
      listChangedEvent: "promptsListChanged",
      capabilityKey: "prompts",
      deferWhenPaginated: true,
      supportsIndicator: true,
      debounceMs,
      fetchAll: async (c, cacheMode, metadata) => {
        const result = await c.listAllPrompts({ cacheMode, metadata });
        return result.prompts;
      }
    });
  }
  getPrompts() {
    return this.getItems();
  }
};

// ../../core/mcp/state/mergeTaskIntoList.ts
function mergeTaskIntoList(tasks, taskId, task) {
  const normalized = {
    ...task,
    taskId,
    createdAt: task.createdAt ?? task.lastUpdatedAt ?? ""
  };
  const idx = tasks.findIndex((t) => t.taskId === taskId);
  if (idx < 0) {
    return [normalized, ...tasks];
  }
  const next = [...tasks];
  next[idx] = normalized;
  return next;
}

// ../../core/mcp/state/managedRequestorTasksState.ts
var MAX_PAGES = 100;
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
  "completed",
  "failed",
  "cancelled"
]);
var ManagedRequestorTasksState = class extends TypedEventTarget {
  tasks = [];
  client = null;
  unsubscribe = null;
  // Task ids the user dismissed via clearCompleted(). Sticky for the session:
  // these are filtered out of refresh() results and ignored by the live-merge
  // handlers so a late status update (or a server that still lists the task)
  // can't resurrect a cleared task. Reset on disconnect / destroy.
  dismissedTaskIds = /* @__PURE__ */ new Set();
  // Task ids the user cancelled via the Cancel Task control. Cancellation is a
  // deliberate terminal decision, so it is sticky: a status update that arrives
  // afterwards — e.g. a server that completes the task anyway (cancellation is
  // cooperative) or the in-flight tool call resolving with a result — must not
  // flip a cancelled task back to "completed". Reset on disconnect / destroy.
  cancelledTaskIds = /* @__PURE__ */ new Set();
  constructor(client) {
    super();
    this.client = client;
    const onConnect = () => {
      void this.refresh();
    };
    const onTasksListChanged = () => {
      void this.refresh();
    };
    const onStatusChange = () => {
      if (isTerminalStatus(this.client?.getStatus())) {
        this.tasks = [];
        this.dismissedTaskIds.clear();
        this.cancelledTaskIds.clear();
        this.dispatchTypedEvent("tasksChange", []);
      }
    };
    const shouldIgnoreUpdate = (taskId, status) => this.dismissedTaskIds.has(taskId) || this.cancelledTaskIds.has(taskId) && status !== "cancelled";
    const onTaskStatusChange = (e) => {
      const { taskId, task } = e.detail;
      if (shouldIgnoreUpdate(taskId, task.status)) return;
      this.tasks = mergeTaskIntoList(this.tasks, taskId, task);
      this.dispatchTypedEvent("tasksChange", this.tasks);
    };
    const onRequestorTaskUpdated = (e) => {
      const { taskId, task } = e.detail;
      if (shouldIgnoreUpdate(taskId, task.status)) return;
      this.tasks = mergeTaskIntoList(this.tasks, taskId, task);
      this.dispatchTypedEvent("tasksChange", this.tasks);
    };
    const onTaskCancelled = (e) => {
      const { taskId } = e.detail;
      if (this.dismissedTaskIds.has(taskId)) return;
      this.cancelledTaskIds.add(taskId);
      const idx = this.tasks.findIndex((t) => t.taskId === taskId);
      if (idx >= 0) {
        const next = [...this.tasks];
        const prev = next[idx];
        next[idx] = { ...prev, status: "cancelled" };
        this.tasks = next;
        this.dispatchTypedEvent("tasksChange", this.tasks);
      }
    };
    this.client.addEventListener("connect", onConnect);
    this.client.addEventListener("tasksListChanged", onTasksListChanged);
    this.client.addEventListener("statusChange", onStatusChange);
    this.client.addEventListener("taskStatusChange", onTaskStatusChange);
    this.client.addEventListener(
      "requestorTaskUpdated",
      onRequestorTaskUpdated
    );
    this.client.addEventListener("taskCancelled", onTaskCancelled);
    this.unsubscribe = () => {
      if (this.client) {
        this.client.removeEventListener("connect", onConnect);
        this.client.removeEventListener("tasksListChanged", onTasksListChanged);
        this.client.removeEventListener("statusChange", onStatusChange);
        this.client.removeEventListener("taskStatusChange", onTaskStatusChange);
        this.client.removeEventListener(
          "requestorTaskUpdated",
          onRequestorTaskUpdated
        );
        this.client.removeEventListener("taskCancelled", onTaskCancelled);
      }
      this.client = null;
    };
  }
  getTasks() {
    return [...this.tasks];
  }
  async refresh() {
    const client = this.client;
    if (!client || client.getStatus() !== "connected") {
      return this.getTasks();
    }
    if (client.isTasksExtensionNegotiated()) {
      return this.refreshModern(client);
    }
    if (!client.getCapabilities()?.tasks) {
      this.tasks = [];
      this.dispatchTypedEvent("tasksChange", this.tasks);
      return this.getTasks();
    }
    this.tasks = [];
    let cursor;
    let pageCount = 0;
    do {
      const result = await client.listRequestorTasks(cursor);
      const page = result.tasks.filter((t) => !this.dismissedTaskIds.has(t.taskId)).map(
        (t) => this.cancelledTaskIds.has(t.taskId) ? { ...t, status: "cancelled" } : t
      );
      this.tasks = cursor ? [...this.tasks, ...page] : page;
      cursor = result.nextCursor;
      pageCount++;
      if (pageCount >= MAX_PAGES) {
        throw new Error(
          `Maximum pagination limit (${MAX_PAGES} pages) reached while listing requestor tasks`
        );
      }
    } while (cursor);
    this.dispatchTypedEvent("tasksChange", this.tasks);
    return this.getTasks();
  }
  /**
   * Modern refresh: re-poll each currently-known (non-dismissed) task via
   * `getRequestorTask` (`tasks/get`). Each poll dispatches `requestorTaskUpdated`,
   * which the live-merge handler folds back into `this.tasks` — so the list
   * updates in place. A poll that errors (e.g. the task expired and the server
   * returns "unknown taskId") is swallowed so one dead handle can't abort the
   * whole refresh. No known tasks ⇒ a no-op refresh (the correct "no server
   * list" semantics).
   */
  async refreshModern(client) {
    const ids = this.tasks.map((t) => t.taskId).filter((id) => !this.dismissedTaskIds.has(id));
    for (const id of ids) {
      try {
        await client.getRequestorTask(id);
      } catch {
      }
    }
    return this.getTasks();
  }
  /**
   * Drop terminal-state tasks (completed / failed / cancelled) from the list.
   * Their ids are remembered in `dismissedTaskIds` so they stay gone for the
   * rest of the session — a subsequent refresh() or live update won't bring
   * them back. No-op (and no event) when there's nothing terminal to clear.
   */
  clearCompleted() {
    const remaining = [];
    let dismissedAny = false;
    for (const task of this.tasks) {
      if (TERMINAL_STATUSES.has(task.status)) {
        this.dismissedTaskIds.add(task.taskId);
        dismissedAny = true;
      } else {
        remaining.push(task);
      }
    }
    if (!dismissedAny) return;
    this.tasks = remaining;
    this.dispatchTypedEvent("tasksChange", this.tasks);
  }
  destroy() {
    this.unsubscribe?.();
    this.unsubscribe = null;
    this.tasks = [];
    this.dismissedTaskIds.clear();
    this.cancelledTaskIds.clear();
  }
};

// ../../core/mcp/apps.ts
import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
function getAppResourceUri(tool) {
  return getToolUiResourceUri(
    tool
  );
}
function readUiMeta(carrier) {
  const ui = carrier !== null && typeof carrier === "object" ? carrier._meta?.ui : void 0;
  return ui !== null && typeof ui === "object" ? ui : void 0;
}
function normalizeResourceUri(uri) {
  const lowered = uri.toLowerCase();
  return lowered.endsWith("/") ? lowered.slice(0, -1) : lowered;
}
function findResourceContent(resource, requestedUri) {
  const exact = resource.contents.find((c) => c.uri === requestedUri);
  if (exact) return exact;
  const norm = normalizeResourceUri(requestedUri);
  const normalized = resource.contents.find(
    (c) => normalizeResourceUri(c.uri) === norm
  );
  if (normalized) return normalized;
  return resource.contents.length === 1 ? resource.contents[0] : void 0;
}
function extractAppInfo(tool, resource) {
  const resourceUri = getAppResourceUri(tool);
  if (resourceUri === void 0) {
    return { hasApp: false, toolName: tool.name };
  }
  const toolUi = readUiMeta(tool);
  const content = resource && findResourceContent(resource, resourceUri);
  const resourceUi = readUiMeta(content) ?? readUiMeta(resource);
  return {
    hasApp: true,
    toolName: tool.name,
    resourceUri,
    ...toolUi?.visibility ? { visibility: toolUi.visibility } : {},
    ...resourceUi?.csp ? { csp: resourceUi.csp } : {},
    ...resourceUi?.permissions ? { permissions: resourceUi.permissions } : {},
    ...resourceUi?.domain ? { domain: resourceUi.domain } : {},
    ...resourceUi?.prefersBorder !== void 0 ? { prefersBorder: resourceUi.prefersBorder } : {},
    ...typeof content?.mimeType === "string" ? { resourceMimeType: content.mimeType } : {}
  };
}

// src/handlers/collect-app-info.ts
async function collectAppInfo(client, tool, metadata) {
  let base;
  try {
    base = extractAppInfo(tool);
  } catch (e) {
    return {
      hasApp: false,
      toolName: tool.name,
      resourceError: e instanceof Error ? e.message : String(e)
    };
  }
  if (!base.hasApp || base.resourceUri === void 0) return base;
  try {
    const read = await client.readResource(base.resourceUri, metadata);
    return extractAppInfo(tool, read.result);
  } catch (e) {
    return {
      ...base,
      resourceError: e instanceof Error ? e.message : String(e)
    };
  }
}

// src/handlers/run-method.ts
async function runMethod(inspectorClient, args) {
  let managedToolsState = null;
  let managedResourcesState = null;
  let managedResourceTemplatesState = null;
  let managedPromptsState = null;
  let managedTasksState = null;
  try {
    let result;
    let appInfo;
    if (args.method === "tools/list" || args.method === "tools/call") {
      managedToolsState = new ManagedToolsState(inspectorClient);
      managedToolsState.setMetadata(args.metadata);
      await managedToolsState.refresh();
    }
    if (args.method === "resources/list") {
      managedResourcesState = new ManagedResourcesState(inspectorClient);
      managedResourcesState.setMetadata(args.metadata);
      await managedResourcesState.refresh();
    } else if (args.method === "resources/templates/list") {
      managedResourceTemplatesState = new ManagedResourceTemplatesState(
        inspectorClient
      );
      managedResourceTemplatesState.setMetadata(args.metadata);
      await managedResourceTemplatesState.refresh();
    } else if (args.method === "prompts/list") {
      managedPromptsState = new ManagedPromptsState(inspectorClient);
      managedPromptsState.setMetadata(args.metadata);
      await managedPromptsState.refresh();
    } else if (args.method === "tasks/list") {
      managedTasksState = new ManagedRequestorTasksState(inspectorClient);
      await managedTasksState.refresh();
    }
    if (args.method === "tools/list") {
      const tools = managedToolsState.getTools();
      if (args.appInfo) {
        const lines = [];
        for (const tool of tools) {
          lines.push(
            await collectAppInfo(inspectorClient, tool, args.metadata)
          );
        }
        return { kind: "ndjson", lines };
      }
      result = { tools };
    } else if (args.method === "tools/call") {
      if (!args.toolName) {
        throw new Error(
          "Tool name is required for tools/call method. Use --tool-name to specify the tool name."
        );
      }
      const tool = managedToolsState.getTools().find((t) => t.name === args.toolName);
      if (!tool) {
        throw new CliExitCodeError(
          EXIT_CODES.TOOL_ERROR,
          `Tool '${args.toolName}' not found on server.`,
          { code: "tool_not_found" }
        );
      }
      if (args.appInfo || args.format === "json") {
        appInfo = await collectAppInfo(inspectorClient, tool, args.metadata);
      }
      if (args.appInfo) {
        result = { ...appInfo };
      } else {
        const invocation = args.task ? await inspectorClient.callToolStream(
          tool,
          args.toolArg || {},
          args.metadata,
          args.toolMeta
        ) : await inspectorClient.callTool(
          tool,
          args.toolArg || {},
          args.metadata,
          args.toolMeta
        );
        if (invocation.result !== null) {
          result = invocation.result;
        } else {
          result = {
            content: [
              {
                type: "text",
                text: invocation.error || "Tool call failed"
              }
            ],
            isError: true
          };
        }
      }
    } else if (args.method === "resources/list") {
      result = { resources: managedResourcesState.getResources() };
    } else if (args.method === "resources/read") {
      if (!args.uri) {
        throw new Error(
          "URI is required for resources/read method. Use --uri to specify the resource URI."
        );
      }
      const invocation = await inspectorClient.readResource(
        args.uri,
        args.metadata
      );
      result = invocation.result;
    } else if (args.method === "resources/templates/list") {
      result = {
        resourceTemplates: managedResourceTemplatesState.getResourceTemplates()
      };
    } else if (args.method === "resources/subscribe") {
      if (!args.uri) {
        throw new Error(
          "URI is required for resources/subscribe. Use --uri to specify the resource URI."
        );
      }
      await inspectorClient.subscribeToResource(args.uri);
      return {
        kind: "stream",
        label: "resources/subscribe",
        start: (writeLine) => {
          writeLine({ type: "subscribed", uri: args.uri });
          const onUpdate = (ev) => {
            const detail = ev.detail;
            writeLine({
              type: "resources/updated",
              uri: detail?.uri ?? args.uri
            });
          };
          inspectorClient.addEventListener("resourceUpdated", onUpdate);
          return () => {
            inspectorClient.removeEventListener("resourceUpdated", onUpdate);
            void inspectorClient.unsubscribeFromResource(args.uri);
          };
        }
      };
    } else if (args.method === "resources/unsubscribe") {
      if (!args.uri) {
        throw new Error(
          "URI is required for resources/unsubscribe. Use --uri to specify the resource URI."
        );
      }
      await inspectorClient.unsubscribeFromResource(args.uri);
      result = { unsubscribed: true, uri: args.uri };
    } else if (args.method === "prompts/list") {
      result = { prompts: managedPromptsState.getPrompts() };
    } else if (args.method === "prompts/get") {
      if (!args.promptName) {
        throw new Error(
          "Prompt name is required for prompts/get method. Use --prompt-name to specify the prompt name."
        );
      }
      const invocation = await inspectorClient.getPrompt(
        args.promptName,
        args.promptArgs || {},
        args.metadata
      );
      result = invocation.result;
    } else if (args.method === "prompts/complete") {
      if (!args.completeRefType || !args.completeRef || !args.completeArgName) {
        throw new Error(
          "prompts/complete requires --complete-ref-type, --complete-ref, and --complete-arg-name."
        );
      }
      const ref = args.completeRefType === "ref/prompt" ? { type: "ref/prompt", name: args.completeRef } : { type: "ref/resource", uri: args.completeRef };
      result = await inspectorClient.getCompletions(
        ref,
        args.completeArgName,
        args.completeArgValue ?? "",
        void 0,
        args.metadata
      );
    } else if (args.method === "initialize") {
      result = {
        serverInfo: inspectorClient.getServerInfo(),
        protocolVersion: inspectorClient.getProtocolVersion(),
        capabilities: inspectorClient.getCapabilities(),
        instructions: inspectorClient.getInstructions()
      };
    } else if (args.method === "logging/setLevel") {
      if (!args.logLevel) {
        throw new Error(
          "Log level is required for logging/setLevel method. Use --log-level to specify the log level."
        );
      }
      await inspectorClient.setLoggingLevel(args.logLevel);
      result = {};
    } else if (args.method === "logging/tail") {
      return {
        kind: "stream",
        label: "logging/tail",
        start: (writeLine) => {
          const log = new MessageLogState(inspectorClient);
          const onMessage = (ev) => {
            const entry = ev.detail;
            if (entry.direction === "notification" && entry.message?.method === "notifications/message") {
              writeLine(entry);
            }
          };
          log.addEventListener("message", onMessage);
          return () => {
            log.removeEventListener("message", onMessage);
            log.destroy();
          };
        }
      };
    } else if (args.method === "tasks/list") {
      result = { tasks: managedTasksState.getTasks() };
    } else if (args.method === "tasks/get") {
      if (!args.taskId) {
        throw new Error("Task id is required for tasks/get. Use --task-id.");
      }
      result = {
        task: await inspectorClient.getRequestorTask(args.taskId)
      };
    } else if (args.method === "tasks/cancel") {
      if (!args.taskId) {
        throw new Error("Task id is required for tasks/cancel. Use --task-id.");
      }
      await inspectorClient.cancelRequestorTask(args.taskId);
      result = { cancelled: true, taskId: args.taskId };
    } else if (args.method === "tasks/result") {
      if (!args.taskId) {
        throw new Error("Task id is required for tasks/result. Use --task-id.");
      }
      result = await inspectorClient.getRequestorTaskResult(
        args.taskId
      );
    } else if (args.method === "roots/list") {
      result = { roots: inspectorClient.getRoots() };
    } else if (args.method === "roots/set") {
      if (!args.rootsJson) {
        throw new Error(
          "roots/set requires --roots-json '<json array of {uri,name?}>'."
        );
      }
      let roots;
      try {
        const parsed = JSON.parse(args.rootsJson);
        if (!Array.isArray(parsed)) {
          throw new Error("must be a JSON array");
        }
        roots = parsed;
      } catch (e) {
        throw new Error(
          `--roots-json is invalid: ${e instanceof Error ? e.message : String(e)}`,
          { cause: e }
        );
      }
      await inspectorClient.setRoots(roots);
      result = { roots: inspectorClient.getRoots() };
    } else {
      throw new Error(
        `Unsupported method: ${args.method}. Supported methods include: initialize, tools/*, resources/*, prompts/*, logging/*, tasks/*, roots/*.`
      );
    }
    return { kind: "result", result, appInfo };
  } finally {
    managedToolsState?.destroy();
    managedResourcesState?.destroy();
    managedResourceTemplatesState?.destroy();
    managedPromptsState?.destroy();
    managedTasksState?.destroy();
  }
}

// src/handlers/method-types.ts
var ONE_SHOT_METHODS = [
  "initialize",
  "tools/list",
  "tools/call",
  "resources/list",
  "resources/read",
  "resources/templates/list",
  "prompts/list",
  "prompts/get",
  "logging/setLevel"
];
function isOneShotMethod(method) {
  return ONE_SHOT_METHODS.includes(method);
}

// src/cli.ts
import {
  refreshAuthorization,
  discoverAuthorizationServerMetadata as discoverAuthorizationServerMetadata4
} from "@modelcontextprotocol/client";

// ../../core/auth/issuerBinding.ts
import { AuthorizationServerMismatchError } from "@modelcontextprotocol/client";
var MISSING_DISCOVERY_STATE_PHRASE = "discoveryState was not available";
function isAuthorizationServerMismatchShape(err) {
  if (err === null || typeof err !== "object") {
    return false;
  }
  const candidate = err;
  if (typeof candidate.recordedIssuer !== "string" || typeof candidate.currentIssuer !== "string") {
    return false;
  }
  return AuthorizationServerMismatchError.isInstance(err) || candidate.name === "AuthorizationServerMismatchError";
}
function isMissingDiscoveryStatePlaceholder(recordedIssuer) {
  if (recordedIssuer.includes(MISSING_DISCOVERY_STATE_PHRASE)) {
    return true;
  }
  try {
    const url = new URL(recordedIssuer);
    return url.protocol !== "http:" && url.protocol !== "https:";
  } catch {
    return true;
  }
}
function findIssuerBindingFailure(err) {
  return findIssuerBindingFailureDeep(err, /* @__PURE__ */ new Set());
}
function findIssuerBindingFailureDeep(err, seen) {
  if (err === null || typeof err !== "object" || seen.has(err)) {
    return void 0;
  }
  seen.add(err);
  if (isAuthorizationServerMismatchShape(err)) {
    return classifyAuthorizationServerMismatch(err);
  }
  const nested = findIssuerBindingFailureDeep(
    err.cause,
    seen
  );
  if (nested) {
    return nested;
  }
  const data = err.data;
  if (data !== null && typeof data === "object") {
    return findIssuerBindingFailureDeep(
      data.cause,
      seen
    );
  }
  return void 0;
}
function classifyAuthorizationServerMismatch(err) {
  if (isMissingDiscoveryStatePlaceholder(err.recordedIssuer)) {
    return {
      kind: "lost_authorization_state",
      currentIssuer: err.currentIssuer
    };
  }
  return {
    kind: "issuer_mismatch",
    recordedIssuer: err.recordedIssuer,
    currentIssuer: err.currentIssuer
  };
}

// ../../core/auth/node/oauth-callback-server.ts
import { createServer } from "http";
var DEFAULT_HOSTNAME = "127.0.0.1";
var DEFAULT_CALLBACK_PATH = "/oauth/callback";
var SUCCESS_HTML = `<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>OAuth complete</title></head>
<body><p>OAuth complete. You can close this window.</p></body>
</html>`;
function errorHtml(message) {
  return `<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>OAuth error</title></head>
<body><p>OAuth failed: ${escapeHtml(message)}</p></body>
</html>`;
}
function escapeHtml(s) {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
var OAuthCallbackServer = class {
  server = null;
  port = 0;
  callbackPath = DEFAULT_CALLBACK_PATH;
  handled = false;
  onCallback;
  onError;
  /**
   * Start the server. Port defaults to 0 (random) when omitted; TUI/CLI callers
   * pass the resolved port from {@link parseRunnerOAuthCallbackUrl} (6276 by default).
   * Returns port and redirectUrl for use as oauth.redirectUrl.
   */
  async start(options = {}) {
    const {
      port = 0,
      hostname = DEFAULT_HOSTNAME,
      path: path5 = DEFAULT_CALLBACK_PATH,
      onCallback,
      onError
    } = options;
    if (!path5.startsWith("/")) {
      return Promise.reject(
        new Error("Callback path must start with '/' (absolute path)")
      );
    }
    this.onCallback = onCallback;
    this.onError = onError;
    this.handled = false;
    this.callbackPath = path5;
    return new Promise((resolve7, reject) => {
      this.server = createServer((req, res) => this.handleRequest(req, res));
      this.server.on("error", reject);
      this.server.listen(port, hostname, () => {
        const a = this.server.address();
        if (!a || typeof a === "string") {
          reject(new Error("Failed to get server address"));
          return;
        }
        this.port = a.port;
        resolve7({
          port: this.port,
          redirectUrl: buildRedirectUrl(hostname, this.port, path5)
        });
      });
    });
  }
  /**
   * Stop the server. Idempotent.
   */
  async stop() {
    if (!this.server) return;
    await new Promise((resolve7) => {
      this.server.close(() => resolve7());
    });
    this.server = null;
  }
  handleRequest(req, res) {
    const needJson = req.headers["accept"]?.includes("application/json");
    const send = (status, body, contentType = "text/html; charset=utf-8") => {
      res.writeHead(status, { "Content-Type": contentType });
      res.end(body);
    };
    if (req.method !== "GET") {
      send(405, needJson ? '{"error":"Method Not Allowed"}' : SUCCESS_HTML);
      return;
    }
    let pathname;
    let search;
    let state;
    try {
      const u = new URL(
        /* v8 ignore next -- defensive: Node always populates req.url for an
           HTTP request, so the `?? ""` fallback is never taken. */
        req.url ?? "",
        "http://placeholder"
      );
      pathname = u.pathname;
      search = u.search;
      state = u.searchParams.get("state") ?? void 0;
    } catch {
      send(400, needJson ? '{"error":"Bad Request"}' : SUCCESS_HTML);
      return;
    }
    if (pathname !== this.callbackPath) {
      send(404, needJson ? '{"error":"Not Found"}' : SUCCESS_HTML);
      return;
    }
    if (this.handled) {
      send(
        409,
        needJson ? '{"error":"Callback already handled"}' : SUCCESS_HTML
      );
      return;
    }
    const params = parseOAuthCallbackParams(search);
    if (params.successful) {
      this.handled = true;
      const cb = this.onCallback;
      if (cb) {
        cb({ code: params.code, state, iss: params.iss }).then(() => {
          send(200, SUCCESS_HTML);
          void this.stop();
        }).catch((err) => {
          const msg2 = err instanceof Error ? err.message : String(err);
          this.onError?.({ error: "callback_error", error_description: msg2 });
          send(500, errorHtml(msg2));
          void this.stop();
        });
      } else {
        send(200, SUCCESS_HTML);
        void this.stop();
      }
      return;
    }
    this.handled = true;
    const msg = generateOAuthErrorDescription(params);
    this.onError?.({
      error: params.error,
      error_description: params.error_description ?? void 0
    });
    send(400, errorHtml(msg));
  }
};
function createOAuthCallbackServer() {
  return new OAuthCallbackServer();
}
function buildRedirectUrl(host, port, path5) {
  return `http://${formatHostForUrl(host)}:${port}${path5}`;
}

// ../../core/auth/node/runner-oauth-callback.ts
var RUNNER_OAUTH_CALLBACK_DEFAULT_HOSTNAME = "127.0.0.1";
var RUNNER_OAUTH_CALLBACK_DEFAULT_PORT = 6276;
var RUNNER_OAUTH_CALLBACK_PATH = "/oauth/callback";
var DEFAULT_RUNNER_OAUTH_CALLBACK_URL = `http://${RUNNER_OAUTH_CALLBACK_DEFAULT_HOSTNAME}:${RUNNER_OAUTH_CALLBACK_DEFAULT_PORT}${RUNNER_OAUTH_CALLBACK_PATH}`;
function parseRunnerOAuthCallbackUrl(cliCallbackUrl) {
  const raw = cliCallbackUrl?.trim() || process.env.MCP_OAUTH_CALLBACK_URL?.trim() || "";
  if (!raw) {
    return {
      hostname: RUNNER_OAUTH_CALLBACK_DEFAULT_HOSTNAME,
      port: RUNNER_OAUTH_CALLBACK_DEFAULT_PORT,
      pathname: RUNNER_OAUTH_CALLBACK_PATH
    };
  }
  let url;
  try {
    url = new URL(raw);
  } catch (err) {
    const reason = err?.message ?? String(err);
    throw new Error(`Invalid OAuth callback URL: ${reason}`, { cause: err });
  }
  if (url.protocol !== "http:") {
    throw new Error("OAuth callback URL must use http scheme");
  }
  const hostname = url.hostname;
  if (!hostname) {
    throw new Error("OAuth callback URL must include a hostname");
  }
  if (!isLoopbackHost(hostname)) {
    throw new Error(
      `OAuth callback URL must bind a loopback host (localhost / 127.0.0.1 / [::1]), not "${hostname}": the callback listener receives the OAuth authorization code over plaintext http, so a network-reachable host risks credential interception. Use 127.0.0.1 (and a port-forward if the browser is elsewhere).`
    );
  }
  const pathname = url.pathname || "/";
  let port;
  if (url.port === "") {
    port = 80;
  } else {
    port = Number(url.port);
    if (!Number.isFinite(port) || !Number.isInteger(port) || port < 0 || port > 65535) {
      throw new Error("OAuth callback URL port must be between 0 and 65535");
    }
  }
  return { hostname: stripBrackets(hostname), port, pathname };
}
function formatRunnerOAuthRedirectUrl(config) {
  return `http://${formatHostForUrl(config.hostname)}:${config.port}${config.pathname}`;
}

// ../../core/auth/node/runner-interactive-oauth.ts
var DEFAULT_RUNNER_INTERACTIVE_OAUTH_TIMEOUT_MS = 15 * 60 * 1e3;
function toRunnerOAuthError(err) {
  const failure = findIssuerBindingFailure(err);
  if (failure) {
    const copy = issuerBindingFailureCopy(failure);
    return new Error(`${copy.title}. ${copy.message}`, { cause: err });
  }
  return err instanceof Error ? err : new Error(String(err));
}
async function runRunnerInteractiveOAuth(options) {
  const createServer2 = options.createCallbackServer ?? createOAuthCallbackServer;
  const server = createServer2();
  let flowResolve;
  let flowReject;
  const flowDone = new Promise((resolve7, reject) => {
    flowResolve = resolve7;
    flowReject = reject;
  });
  let timeoutId;
  try {
    const { redirectUrl } = await server.start({
      hostname: options.callbackListen.hostname,
      port: options.callbackListen.port,
      path: options.callbackListen.pathname,
      onCallback: async (params) => {
        try {
          await options.client.completeOAuthFlow(params.code, params.iss);
          flowResolve();
        } catch (err) {
          flowReject(toRunnerOAuthError(err));
        }
      },
      onError: (params) => {
        flowReject(
          new Error(
            /* v8 ignore next -- params.error is a required non-null string, so the "OAuth error" fallback is unreachable */
            params.error_description ?? params.error ?? "OAuth error"
          )
        );
      }
    });
    options.onCallbackServer?.(server);
    options.redirectUrlProvider.redirectUrl = redirectUrl;
    const timeoutMs = options.callbackTimeoutMs ?? DEFAULT_RUNNER_INTERACTIVE_OAUTH_TIMEOUT_MS;
    const waitForCallback = Promise.race([
      flowDone.finally(() => {
        if (timeoutId !== void 0) {
          clearTimeout(timeoutId);
        }
      }),
      new Promise((_, reject) => {
        timeoutId = setTimeout(() => {
          reject(
            new Error(
              `OAuth callback timed out after ${Math.round(timeoutMs / 1e3)}s`
            )
          );
        }, timeoutMs);
      })
    ]);
    if (options.authorizationUrl) {
      await options.client.beginInteractiveAuthorization(
        options.authorizationUrl
      );
      await waitForCallback;
    } else {
      const authUrl = await options.client.authenticate();
      if (authUrl !== void 0) {
        await waitForCallback;
      } else {
        return { kind: "already_authorized" };
      }
    }
    if (options.authChallenge) {
      const satisfied = await options.client.checkAuthChallengeSatisfied(
        options.authChallenge
      );
      if (!satisfied) {
        return {
          kind: "insufficient_scope",
          challenge: options.authChallenge
        };
      }
    }
    return { kind: "success" };
  } finally {
    if (timeoutId !== void 0) {
      clearTimeout(timeoutId);
    }
    await server.stop().catch(() => {
    });
  }
}

// src/open-url.ts
import open from "open";
async function openUrl(url) {
  await open(typeof url === "string" ? url : url.href);
}

// src/style.ts
var identity = (s) => s;
function sgr(enabled, open2, close) {
  if (!enabled) return identity;
  return (s) => `\x1B[${open2}m${s}\x1B[${close}m`;
}
function createStyle(ansi) {
  return {
    ansi,
    bold: sgr(ansi, 1, 22),
    dim: sgr(ansi, 2, 22),
    red: sgr(ansi, 31, 39),
    yellow: sgr(ansi, 33, 39),
    green: sgr(ansi, 32, 39),
    cyan: sgr(ansi, 36, 39),
    link: (uri, label) => {
      const text = label ?? uri;
      if (!ansi || !uri) return text;
      return `\x1B]8;;${uri}\x07${text}\x1B]8;;\x07`;
    }
  };
}
var PLAIN = createStyle(false);
function resolveAnsiEnabled(opts = {}) {
  if (opts.plain) return false;
  if (opts.format === "json") return false;
  const noColor = opts.noColorEnv !== void 0 ? opts.noColorEnv : process.env.NO_COLOR;
  if (noColor !== void 0 && noColor !== "") return false;
  const tty = opts.isTTY !== void 0 ? opts.isTTY : process.stdout.isTTY === true;
  return tty;
}

// src/cli-oauth-navigation.ts
function resolveCliAutoOpenEnabled(env = process.env) {
  const flag = env.MCP_AUTO_OPEN_ENABLED;
  if (flag === "true") return true;
  if (flag === "false") return false;
  return !env.VITEST;
}
function isCliAutoOpenForced(env = process.env) {
  return env.MCP_AUTO_OPEN_ENABLED === "true";
}
function createCliOAuthNavigation(options = {}) {
  return new CallbackNavigation(async (url) => {
    const armed = options.autoOpenControl?.armed === true;
    if (options.disableAutoOpen || !armed) return;
    const href = url.href;
    const tty = options.isTTY !== void 0 ? options.isTTY : process.stderr.isTTY === true;
    const style = createStyle(
      resolveAnsiEnabled({
        isTTY: tty,
        noColorEnv: options.noColorEnv
      })
    );
    const write = options.write ?? ((line) => process.stderr.write(line));
    write(`Please navigate to: ${style.link(href)}
`);
    const envAllows = options.autoOpenEnabled !== void 0 ? options.autoOpenEnabled : resolveCliAutoOpenEnabled();
    if (!envAllows) return;
    const forced = options.forceAutoOpen ?? isCliAutoOpenForced();
    if (!forced && !tty) return;
    try {
      await (options.openBrowser ?? openUrl)(href);
    } catch {
    }
  });
}

// ../../core/client/config-parse.ts
import { z as z4 } 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 = z4.string().min(1).superRefine((val, ctx) => {
  const trimmed = val.trim();
  if (!isAbsoluteHttpUrl(trimmed)) {
    ctx.addIssue({
      code: z4.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: z4.ZodIssueCode.custom,
        message: "CIMD client metadata URL is required when CIMD is enabled"
      });
    }
    return;
  }
  const error = getCimdClientMetadataUrlError(trimmed);
  if (error) {
    ctx.addIssue({
      code: z4.ZodIssueCode.custom,
      message: error
    });
  }
}
var CimdConfigSchema = z4.object({
  enabled: z4.boolean(),
  clientMetadataUrl: z4.string()
}).superRefine((data, ctx) => {
  refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
});
var EnterpriseManagedAuthIdpConfigSchema = z4.object({
  issuer: HttpUrlStringSchema,
  clientId: z4.string().min(1),
  clientSecret: z4.string().optional()
});
var ClientConfigSchema = z4.object({
  enterpriseManagedAuth: z4.object({
    enabled: z4.boolean().optional(),
    idp: EnterpriseManagedAuthIdpConfigSchema
  }).optional(),
  cimd: CimdConfigSchema.optional()
});
function parseClientConfig(raw) {
  return ClientConfigSchema.parse(raw);
}

// ../../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 secretStoreGetStrict(
      secretStore,
      CLIENT_KEYCHAIN_ID,
      SECRET_FIELD_IDP_CLIENT_SECRET
    );
    if (existing === null) {
      await secretStore.set(
        CLIENT_KEYCHAIN_ID,
        SECRET_FIELD_IDP_CLIENT_SECRET,
        value
      );
    }
    if (!await secretStoreIsDurable(secretStore)) return config;
    await writeStoreFile(filePath, serializeStore(stripped));
    return stripped;
  } catch (err) {
    if (err instanceof SecretStoreUnavailableError) {
      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);
}

// ../../core/client/config.ts
function getClientConfigFilePath(customPath) {
  return customPath ?? getStoreFilePath(getDefaultStorageDir(), "client");
}
async function loadClientConfig(options) {
  const filePath = getClientConfigFilePath(options?.filePath);
  if (options?.secretStore) {
    return readClientConfigStore(filePath, options.secretStore);
  }
  const raw = await readStoreFile(filePath);
  if (raw === null) {
    return {};
  }
  return parseClientConfig(parseStore(raw));
}

// ../../core/client/types.ts
function isEnterpriseManagedAuthEnabled(config) {
  const ema = config.enterpriseManagedAuth;
  if (!ema?.idp) return false;
  return ema.enabled !== false;
}
function getActiveEnterpriseManagedAuthIdp(config) {
  if (!isEnterpriseManagedAuthEnabled(config)) return void 0;
  return config.enterpriseManagedAuth.idp;
}
function isCimdEnabled(config) {
  return config.cimd?.enabled === true;
}
function getActiveCimdClientMetadataUrl(config) {
  if (!isCimdEnabled(config)) return void 0;
  const url = config.cimd?.clientMetadataUrl?.trim();
  return url || void 0;
}

// ../../core/client/runner.ts
async function loadRunnerClientConfig(options) {
  const customPath = options?.clientConfigPath?.trim() || process.env.MCP_CLIENT_CONFIG_PATH?.trim() || void 0;
  const secretStore = options?.secretStore ?? defaultSecretStore();
  return loadClientConfig({ filePath: customPath, secretStore });
}
function isOAuthCapableServerConfig(config) {
  if (!config) return false;
  return config.type === "sse" || config.type === "streamable-http";
}
function buildRunnerClientAuthOptions(clientConfig, savedSettings, cliOverrides) {
  const activeIdp = getActiveEnterpriseManagedAuthIdp(clientConfig);
  const activeCimdUrl = getActiveCimdClientMetadataUrl(clientConfig);
  const serverAuthorizationParams = savedSettings ? oauthAuthorizationParamsFromSettings(savedSettings) : void 0;
  const serverEndpointOverrides = savedSettings ? oauthEndpointOverridesFromSettings(savedSettings) : void 0;
  const oauthFromServer = savedSettings && (savedSettings.oauthClientId || savedSettings.oauthClientSecret || savedSettings.oauthScopes || serverAuthorizationParams || serverEndpointOverrides || savedSettings.enterpriseManaged || savedSettings.oauthRequestRefreshToken === false) ? {
    ...savedSettings.oauthClientId && {
      clientId: savedSettings.oauthClientId
    },
    ...savedSettings.oauthClientSecret && {
      clientSecret: savedSettings.oauthClientSecret
    },
    ...savedSettings.oauthScopes && {
      scope: savedSettings.oauthScopes
    },
    ...serverAuthorizationParams && {
      authorizationParams: serverAuthorizationParams
    },
    ...serverEndpointOverrides,
    ...savedSettings.enterpriseManaged && {
      enterpriseManaged: true
    },
    // #2068: only the explicit opt-out is forwarded; omitting the key
    // leaves the provider's default (declare `refresh_token`) in place.
    ...savedSettings.oauthRequestRefreshToken === false && {
      requestRefreshToken: false
    }
  } : void 0;
  const clientMetadataUrl = cliOverrides?.clientMetadataUrl?.trim() || activeCimdUrl;
  const oauthFromCli = cliOverrides?.clientId || cliOverrides?.clientSecret || clientMetadataUrl ? {
    ...cliOverrides?.clientId && { clientId: cliOverrides.clientId },
    ...cliOverrides?.clientSecret && {
      clientSecret: cliOverrides.clientSecret
    },
    ...clientMetadataUrl && { clientMetadataUrl }
  } : void 0;
  const oauth = oauthFromServer || oauthFromCli ? {
    ...oauthFromServer ?? {},
    ...oauthFromCli ?? {}
  } : void 0;
  return {
    ...oauth && { oauth },
    ...activeIdp && {
      enterpriseManagedAuth: { idp: activeIdp }
    },
    ...clientConfig.enterpriseManagedAuth && {
      installEnterpriseManagedAuth: clientConfig.enterpriseManagedAuth
    },
    ...oauth && { directAuthRecovery: true }
  };
}

// src/cliOAuth.ts
import { createInterface } from "readline/promises";
var STEP_UP_PIPE_TIMEOUT_MS = 5e3;
function authRequiredFailure(message) {
  throw new CliExitCodeError(EXIT_CODES.AUTH_REQUIRED, message, {
    code: "auth_required"
  });
}
function assertInteractiveOAuthAllowed(options) {
  const humanPresent = options?.isTTY !== void 0 ? options.isTTY : process.stdin.isTTY === true || process.stderr.isTTY === true;
  if (humanPresent || isCliAutoOpenForced()) return;
  authRequiredFailure(
    "Interactive OAuth requires a TTY on stdin or stderr (or MCP_AUTO_OPEN_ENABLED=true). For CI/non-interactive runs use --stored-auth-only."
  );
}
function isStandardOAuthStepUp2(challenge, settings) {
  return isStandardOAuthStepUp(challenge, {
    enterpriseManaged: settings?.enterpriseManaged
  });
}
async function confirmStepUpFromStdin(timeoutMs) {
  const rl = createInterface({ input: process.stdin, output: process.stderr });
  const applyTimeoutMs = timeoutMs !== void 0 ? timeoutMs : process.stdin.isTTY === true ? void 0 : STEP_UP_PIPE_TIMEOUT_MS;
  try {
    const answer = await new Promise((resolve7, reject) => {
      let settled = false;
      let lastLine;
      let timer;
      function cleanup() {
        if (timer !== void 0) clearTimeout(timer);
        rl.removeListener("close", onClose);
        rl.removeListener("line", onLine);
      }
      function finish(value) {
        if (settled) return;
        settled = true;
        cleanup();
        resolve7(value);
      }
      function finishTimeout() {
        if (settled) return;
        settled = true;
        cleanup();
        reject(
          new CliExitCodeError(
            EXIT_CODES.AUTH_REQUIRED,
            `Step-up authorization timed out (no answer on stdin within ${String(applyTimeoutMs)}ms). For CI/non-interactive runs use --stored-auth-only.`,
            { code: "auth_required" }
          )
        );
      }
      function onLine(line) {
        lastLine = line;
      }
      function onClose() {
        finish(lastLine ?? "n");
      }
      rl.on("line", onLine);
      rl.once("close", onClose);
      if (applyTimeoutMs !== void 0) {
        timer = setTimeout(finishTimeout, applyTimeoutMs);
      }
      void rl.question("").then(
        (line) => finish(line),
        () => finish(lastLine ?? "n")
      );
    });
    const normalized = answer.trim().toLowerCase();
    return normalized === "y" || normalized === "yes";
  } finally {
    rl.close();
  }
}
async function promptStepUpConfirm(challenge, confirmStepUp) {
  process.stderr.write(`${stepUpConfirmMessage(challenge)}
`);
  process.stderr.write("Proceed with step-up authorization? [y/N] ");
  return confirmStepUp();
}
async function withArmedAutoOpen(control, fn) {
  if (!control) return fn();
  control.armed = true;
  try {
    return await fn();
  } finally {
    control.armed = false;
  }
}
async function runCliInteractiveOAuth(client, redirectUrlProvider, callbackUrlConfig, options) {
  const result = await withArmedAutoOpen(
    options?.autoOpenControl,
    () => runRunnerInteractiveOAuth({
      client,
      redirectUrlProvider,
      callbackListen: callbackUrlConfig,
      createCallbackServer: createOAuthCallbackServer,
      authorizationUrl: options?.authorizationUrl,
      authChallenge: options?.authChallenge
    })
  );
  if (result.kind === "insufficient_scope") {
    throw new Error(stepUpInsufficientScopeMessage(result.challenge));
  }
  if (result.kind === "success") {
    process.stderr.write("Authorization complete.\n");
  }
}
async function handleCliAuthRecoveryRequired(client, error, redirectUrlProvider, callbackUrlConfig, serverSettings, options) {
  const confirmStepUp = options?.confirmStepUp ?? (() => confirmStepUpFromStdin(options?.stepUpPromptTimeoutMs));
  if (isStandardOAuthStepUp2(error.authChallenge, serverSettings)) {
    if (await client.checkAuthChallengeSatisfied(error.authChallenge)) {
      return;
    }
    assertInteractiveOAuthAllowed(options);
    const proceed = await promptStepUpConfirm(
      error.authChallenge,
      confirmStepUp
    );
    if (!proceed) {
      throw new Error("Step-up authorization declined.");
    }
  } else if (await client.checkAuthChallengeSatisfied(error.authChallenge)) {
    return;
  } else {
    assertInteractiveOAuthAllowed(options);
  }
  await runCliInteractiveOAuth(client, redirectUrlProvider, callbackUrlConfig, {
    authorizationUrl: error.authorizationUrl,
    autoOpenControl: options?.autoOpenControl,
    ...error.authChallenge.reason === "insufficient_scope" && {
      authChallenge: error.authChallenge
    }
  });
}
async function connectInspectorWithOAuth(inspectorClient, serverConfig, redirectUrlProvider, callbackUrlConfig, serverSettings, options) {
  try {
    await inspectorClient.connect();
  } catch (err) {
    if (!isOAuthCapableServerConfig(serverConfig)) {
      throw err;
    }
    if (err instanceof AuthRecoveryRequiredError) {
      if (options?.storedAuthOnly) {
        if (await inspectorClient.checkAuthChallengeSatisfied(err.authChallenge)) {
          await inspectorClient.connect();
          return;
        }
        authRequiredFailure(
          err.message || "Authentication required and --stored-auth-only is set; refusing interactive OAuth."
        );
      }
      await handleCliAuthRecoveryRequired(
        inspectorClient,
        err,
        redirectUrlProvider,
        callbackUrlConfig,
        serverSettings,
        options
      );
      await inspectorClient.connect();
      return;
    }
    if (isUnauthorizedError(err)) {
      if (options?.storedAuthOnly) {
        authRequiredFailure(
          err instanceof Error ? err.message : "Authentication required and --stored-auth-only is set; refusing interactive OAuth."
        );
      }
      assertInteractiveOAuthAllowed(options);
      await inspectorClient.disconnect().catch(() => {
      });
      await runCliInteractiveOAuth(
        inspectorClient,
        redirectUrlProvider,
        callbackUrlConfig,
        { autoOpenControl: options?.autoOpenControl }
      );
      await inspectorClient.connect();
      return;
    }
    throw err;
  }
}
async function withCliAuthRecoveryRetry(inspectorClient, serverConfig, redirectUrlProvider, callbackUrlConfig, serverSettings, fn, options) {
  try {
    return await fn();
  } catch (err) {
    if (!isOAuthCapableServerConfig(serverConfig)) {
      throw err;
    }
    if (err instanceof AuthRecoveryRequiredError) {
      if (options?.storedAuthOnly) {
        if (await inspectorClient.checkAuthChallengeSatisfied(err.authChallenge)) {
          return await fn();
        }
        authRequiredFailure(
          err.message || "Authentication required and --stored-auth-only is set; refusing interactive OAuth."
        );
      }
      await handleCliAuthRecoveryRequired(
        inspectorClient,
        err,
        redirectUrlProvider,
        callbackUrlConfig,
        serverSettings,
        options
      );
      await inspectorClient.connect();
      process.stderr.write("Authorization complete. Retrying\u2026\n");
      return await fn();
    }
    if (isUnauthorizedError(err)) {
      if (options?.storedAuthOnly) {
        authRequiredFailure(
          err instanceof Error ? err.message : "Authentication required and --stored-auth-only is set; refusing interactive OAuth."
        );
      }
      assertInteractiveOAuthAllowed(options);
      await inspectorClient.disconnect().catch(() => {
      });
      await runCliInteractiveOAuth(
        inspectorClient,
        redirectUrlProvider,
        callbackUrlConfig,
        { autoOpenControl: options?.autoOpenControl }
      );
      await inspectorClient.connect();
      process.stderr.write("Authorization complete. Retrying\u2026\n");
      return await fn();
    }
    throw err;
  }
}

// src/cli.ts
import { LoggingLevelSchema } from "@modelcontextprotocol/core";

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

// src/cli.ts
var validLogLevels = Object.values(
  LoggingLevelSchema.enum
);
var CLI_CLIENT_NAME = "inspector-cli";
async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin) {
  if (relogin) {
    if (!("url" in serverConfig && serverConfig.url)) {
      throw new Error(
        "--relogin requires an HTTP/SSE server URL (no OAuth store entry for stdio)"
      );
    }
    await clearStoredAuthForRelogin(serverConfig.url);
  }
  const clientIdentity = {
    name: CLI_CLIENT_NAME,
    version: readInspectorVersion(import.meta.url)
  };
  const environment = {
    transport: createTransportNode,
    // Proxy support sits at the bottom of the fetch stack so InspectorClient's
    // wrappers compose over it — and so OAuth discovery/token requests, which
    // also run through `environment.fetch`, are proxied too (#2067). Undefined
    // when no proxy env var is set, which leaves the built-in fetch in place.
    fetch: createProxyFetch()
  };
  const redirectUrlProvider = new MutableRedirectUrlProvider();
  const autoOpenControl = { armed: false };
  if (isOAuthCapableServerConfig(serverConfig)) {
    redirectUrlProvider.redirectUrl = formatRunnerOAuthRedirectUrl(callbackUrlConfig);
    environment.oauth = {
      storage: new NodeOAuthStorage(),
      navigation: createCliOAuthNavigation({
        autoOpenControl,
        disableAutoOpen: storedAuthOnly
      }),
      redirectUrlProvider
    };
  }
  const clientAuthOptions = buildRunnerClientAuthOptions(
    clientConfig,
    serverSettings,
    cliAuthOverrides
  );
  const inspectorClient = new InspectorClient(serverConfig, {
    environment,
    clientIdentity,
    initialLoggingLevel: "debug",
    progress: false,
    sample: false,
    elicit: false,
    // Advertise the roots configured for this server in mcp.json, exactly as
    // web does (`App.tsx`) so both answer `roots/list` with the same content.
    // Passing the option (even empty) is what negotiates `capabilities.roots`
    // at `initialize` and registers the `roots/list` handler. Omitting it meant
    // a server that asks for roots on its own — `server-filesystem` does, at
    // `initialize` — got -32601, and `--method roots/set` could not announce
    // the change at all: the SDK refuses `roots/list_changed` from a client
    // that never declared it, which `setRoots` logged as a send failure (#1797).
    roots: cleanRoots(serverSettings?.roots ?? []),
    // Per-server default `_meta` from mcp.json, exactly as web (`App.tsx`) and
    // the TUI pass it — the setting belongs to the server, not to the client
    // that happens to read it, and `InspectorClient` only reads the option
    // rather than falling back to `serverSettings.metadata` (#2093). Already a
    // JSON object (#1910), so there is no pair-array flattening left to do;
    // `{}` means "no defaults". `--metadata` stays per-invocation and wins on a
    // key collision, since call-time keys override defaults in `mergeMeta`.
    ...serverSettings?.metadata && Object.keys(serverSettings.metadata).length > 0 && {
      defaultMetadata: serverSettings.metadata
    },
    serverSettings,
    // Per-server protocol era (SEP §7.8) from mcp.json → SDK versionNegotiation.
    // Absent era defaults to legacy in the InspectorClient constructor (#1626).
    ...serverSettings?.protocolEra && {
      versionNegotiation: eraToVersionNegotiation(serverSettings.protocolEra)
    },
    ...clientAuthOptions
  });
  try {
    await connectInspectorWithOAuth(
      inspectorClient,
      serverConfig,
      redirectUrlProvider,
      callbackUrlConfig,
      serverSettings,
      { storedAuthOnly, autoOpenControl }
    );
    const outcome = await withCliAuthRecoveryRetry(
      inspectorClient,
      serverConfig,
      redirectUrlProvider,
      callbackUrlConfig,
      serverSettings,
      () => runMethod(inspectorClient, args),
      { storedAuthOnly, autoOpenControl }
    );
    await consumeMethodOutcome(outcome, args);
  } finally {
    await inspectorClient.disconnect();
  }
}
function normalizeServerUrl2(serverUrl) {
  try {
    return new URL(serverUrl).href;
  } catch {
    return serverUrl;
  }
}
async function readOAuthSnapshot(statePath) {
  const { readFile: readFile2 } = await import("fs/promises");
  try {
    const text = await readFile2(statePath, "utf8");
    const snapshot = parseOAuthPersistBlob(text);
    if (snapshot) return snapshot;
  } catch {
  }
  return { servers: {}, idpSessions: {} };
}
async function readOAuthServers(statePath) {
  return (await readOAuthSnapshot(statePath)).servers;
}
function findStoredServerState(servers, serverUrl) {
  const normalized = normalizeServerUrl2(serverUrl);
  if (servers[normalized])
    return { key: normalized, state: servers[normalized] };
  if (servers[serverUrl]) return { key: serverUrl, state: servers[serverUrl] };
  return void 0;
}
function findStoredToken(servers, serverUrl) {
  return findStoredServerState(servers, serverUrl)?.state.tokens?.access_token;
}
async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
  const refresh = deps.refresh ?? refreshAuthorization;
  const discover = deps.discover ?? discoverAuthorizationServerMetadata4;
  const snapshot = await readOAuthSnapshot(statePath);
  const servers = snapshot.servers;
  const found = findStoredServerState(servers, serverUrl);
  const refreshToken = found?.state.tokens?.refresh_token;
  const clientInformation = found?.state.clientInformation;
  if (!found || !refreshToken) {
    throw new CliExitCodeError(
      EXIT_CODES.AUTH_REQUIRED,
      `No stored refresh token for ${normalizeServerUrl2(serverUrl)} in ${statePath}. Complete the OAuth flow in the web inspector first.`,
      { code: "no_stored_token", url: serverUrl }
    );
  }
  if (!clientInformation) {
    throw new CliExitCodeError(
      EXIT_CODES.AUTH_REQUIRED,
      `Stored auth for ${normalizeServerUrl2(serverUrl)} has a refresh token but no client information; cannot refresh. Re-authorize in the web inspector.`,
      { code: "no_client_information", url: serverUrl }
    );
  }
  let authServerUrl;
  let metadata = found.state.serverMetadata ?? void 0;
  if (found.state.serverMetadata?.issuer) {
    authServerUrl = new URL(found.state.serverMetadata.issuer);
  } else {
    const discovered = await discoverAuthorizationServerMetadataFromCandidates(
      getAuthorizationServerUrlCandidates(serverUrl),
      discover
    );
    authServerUrl = discovered?.authorizationServerUrl ?? getAuthorizationServerUrl(serverUrl);
    metadata = discovered?.metadata;
  }
  let tokens;
  try {
    tokens = await refresh(authServerUrl, {
      metadata,
      clientInformation,
      refreshToken,
      resource: new URL(serverUrl)
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    throw new CliExitCodeError(
      EXIT_CODES.AUTH_REQUIRED,
      `Failed to refresh the stored OAuth token for ${normalizeServerUrl2(serverUrl)}: ${message}. Re-authorize in the web inspector.`,
      { code: "refresh_failed", url: serverUrl }
    );
  }
  servers[found.key] = { ...found.state, tokens };
  await writeStoreFile(statePath, serializeOAuthPersistBlob(snapshot));
  return tokens.access_token;
}
async function waitForStoredToken(serverUrl, statePath, timeoutSec) {
  const key = normalizeServerUrl2(serverUrl);
  const deadline = Date.now() + timeoutSec * 1e3;
  for (; ; ) {
    const servers = await readOAuthServers(statePath);
    const token = findStoredToken(servers, serverUrl);
    if (token) return token;
    if (Date.now() >= deadline) {
      const stored = Object.keys(servers);
      throw new CliExitCodeError(
        EXIT_CODES.AUTH_REQUIRED,
        `--wait-for-auth timed out after ${timeoutSec}s; no stored OAuth token for ${key} in ${statePath}.` + (stored.length > 0 ? ` Stored keys: ${stored.join(", ")}.` : " No tokens stored yet."),
        { code: "auth_wait_timeout", url: serverUrl }
      );
    }
    await new Promise((r) => setTimeout(r, 500));
  }
}
function deepLinkTransport(serverUrl, transport) {
  if (transport === "sse") return "sse";
  if (transport === "http") return "http";
  try {
    if (new URL(serverUrl).pathname.endsWith("/sse")) return "sse";
  } catch {
  }
  return "http";
}
function buildHandoff(serverUrl, statePath, transport) {
  const host = process.env.HOST || "127.0.0.1";
  const linkHost = isAllInterfacesHost(host) ? "localhost" : canonicalUrlHost(host);
  const clientPort = process.env.CLIENT_PORT || "6274";
  const sandboxPort = process.env.MCP_SANDBOX_PORT || "6275";
  const appOriginPort = process.env.MCP_APP_ORIGIN_PORT || "6278";
  const apiToken = process.env.MCP_INSPECTOR_API_TOKEN || void 0;
  const normalizedUrl = normalizeServerUrl2(serverUrl);
  const params = new URLSearchParams({
    serverUrl: normalizedUrl,
    transport: deepLinkTransport(serverUrl, transport)
  });
  if (apiToken) params.set("autoConnect", apiToken);
  return {
    serverUrl: normalizedUrl,
    deepLink: `http://${linkHost}:${clientPort}/?${params.toString()}`,
    portForwardCmd: `coder port-forward <workspace> --tcp ${clientPort}:${clientPort} --tcp ${sandboxPort}:${sandboxPort} --tcp ${appOriginPort}:${appOriginPort}`,
    oauthStatePath: statePath,
    apiToken: apiToken ?? null,
    note: apiToken === void 0 ? "MCP_INSPECTOR_API_TOKEN is not set; the deep-link autoConnect gate will reject \u2014 launch the web inspector with a known token first." : void 0
  };
}
function parseKeyValuePair2(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.`
    );
  }
  let parsedValue;
  try {
    parsedValue = JSON.parse(val);
  } catch {
    parsedValue = val;
  }
  if (!isSerializableJson(parsedValue)) {
    throw new Error(
      `Invalid value for "${key}": numbers must be finite (a literal like 1e400 overflows to Infinity and cannot be sent).`
    );
  }
  return { ...previous, [key]: parsedValue };
}
async function parseArgs(argv) {
  const program = new Command();
  program.exitOverride((err) => {
    if (err.exitCode !== 0) throw err;
  });
  const rawArgs = argv ?? process.argv;
  const scriptArgs = rawArgs.slice(2);
  const dashDashIndex = scriptArgs.indexOf("--");
  let targetArgs = [];
  let optionArgs;
  if (dashDashIndex >= 0) {
    targetArgs = scriptArgs.slice(0, dashDashIndex);
    optionArgs = scriptArgs.slice(dashDashIndex + 1);
  } else {
    let i = 0;
    while (i < scriptArgs.length && !scriptArgs[i].startsWith("-")) {
      targetArgs.push(scriptArgs[i]);
      i++;
    }
    optionArgs = scriptArgs.slice(i);
  }
  const preArgs = [
    rawArgs[0] ?? "node",
    rawArgs[1] ?? "inspector-cli",
    ...optionArgs
  ];
  program.name("inspector-cli").allowUnknownOption().argument(
    "[target...]",
    "Command and arguments or URL of the MCP server (or use --config and --server)"
  ).option(
    "--catalog <path>",
    "Writable catalog file (created if missing; default: ~/.mcp-inspector/mcp.json, or MCP_CATALOG_PATH)"
  ).option(
    "--config <path>",
    "Read-only session config file (served as-is, never written or seeded; errors if absent)"
  ).option("--server <name>", "Server name from config/catalog file").option(
    "-e <env>",
    "Environment variables for the server (KEY=VALUE)",
    parseKeyValuePair,
    {}
  ).option("--method <method>", "Method to invoke").option("--tool-name <toolName>", "Tool name (for tools/call method)").option(
    "--tool-arg <pairs...>",
    "Tool argument as key=value pair",
    parseKeyValuePair2,
    {}
  ).option("--uri <uri>", "URI of the resource (for resources/read method)").option(
    "--prompt-name <promptName>",
    "Name of the prompt (for prompts/get method)"
  ).option(
    "--prompt-args <pairs...>",
    "Prompt arguments as key=value pairs",
    parseKeyValuePair2,
    {}
  ).option(
    "--log-level <level>",
    "Logging level (for logging/setLevel method)",
    (value) => {
      if (!validLogLevels.includes(value)) {
        throw new Error(
          `Invalid log level: ${value}. Valid levels are: ${validLogLevels.join(", ")}`
        );
      }
      return value;
    }
  ).option("--cwd <path>", "Working directory for stdio server process").option(
    "--transport <type>",
    "Transport type (sse, http, or stdio). Auto-detected from URL: /mcp \u2192 http, /sse \u2192 sse, commands \u2192 stdio",
    (value) => {
      const validTransports = ["sse", "http", "stdio"];
      if (!validTransports.includes(value)) {
        throw new Error(
          `Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`
        );
      }
      return value;
    }
  ).option("--server-url <url>", "Server URL for SSE/HTTP transport").option(
    "--header <headers...>",
    'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
    parseHeaderPair,
    {}
  ).option(
    "--metadata <pairs...>",
    "General metadata as key=value pairs (applied to all methods)",
    parseKeyValuePair2,
    {}
  ).option(
    "--tool-metadata <pairs...>",
    "Tool-specific metadata as key=value pairs (for tools/call method only)",
    parseKeyValuePair2,
    {}
  ).option(
    "--app-info",
    "Probe the tool's MCP App UI metadata (resourceUri, csp, permissions, domain) and emit it as one JSON line; exit 2 when the tool has no app. Use with --method tools/call --tool-name <name> (the tool itself is not invoked) or --method tools/list (one NDJSON line per tool)."
  ).option(
    "--strict",
    "Report tool-schema portability problems in full (path, issue, suggested fix) on stderr, and exit 6 if any is error-severity. Use with --method tools/list. Without it, a one-line count is printed instead."
  ).option(
    "--connect-timeout <ms>",
    `Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`,
    (v) => {
      const n = Number(v);
      if (!Number.isFinite(n) || n < 0) {
        throw new Error(`--connect-timeout must be a non-negative number.`);
      }
      return n;
    }
  ).option(
    "--format <format>",
    "Output format: text (default; pretty-printed) or json (one JSON object on stdout, no banners).",
    (v) => {
      if (v !== "text" && v !== "json") {
        throw new Error(`--format must be 'text' or 'json'.`);
      }
      return v;
    }
  ).option(
    "--tool-args-json <json>",
    `Tool arguments as a single JSON object (e.g. '{"zip":"10001"}'). Values are passed verbatim \u2014 no key=value coercion. Mutually exclusive with --tool-arg.`
  ).option(
    "--client-config <path>",
    "Install-level client config (default: ~/.mcp-inspector/storage/client.json, or MCP_CLIENT_CONFIG_PATH)"
  ).option(
    "--client-id <id>",
    "OAuth client ID (static client) for HTTP servers"
  ).option(
    "--client-secret <secret>",
    "OAuth client secret (for confidential clients)"
  ).option(
    "--client-metadata-url <url>",
    "OAuth Client ID Metadata Document URL (CIMD) for HTTP servers"
  ).option(
    "--callback-url <url>",
    `OAuth redirect/callback listener URL; must be loopback (default: ${DEFAULT_RUNNER_OAUTH_CALLBACK_URL}, or MCP_OAUTH_CALLBACK_URL)`
  ).option(
    "--use-stored-auth",
    "Read the OAuth access token for --server-url from the OAuth state file (written by the web inspector) and inject it as Authorization: Bearer."
  ).option(
    "--stored-auth-only",
    "Never start interactive OAuth; use the shared store if present, otherwise fail with auth_required. Preferred for CI/non-interactive runs. No-op when the server does not require auth."
  ).option(
    "--relogin",
    "Delete stored OAuth for this server URL from the shared store before connect (HTTP/SSE URL keys only); interactive login runs only if the server requires auth. Rejected for stdio (no URL-keyed store entry)"
  ).option(
    "--wait-for-auth <sec>",
    "Poll the OAuth state file until a token for --server-url appears (or the timeout elapses), then proceed as if --use-stored-auth were set. Use after handing off to a human to complete OAuth in a browser.",
    (v) => {
      const n = Number(v);
      if (!Number.isFinite(n) || n <= 0) {
        throw new Error(
          `--wait-for-auth must be a positive number of seconds.`
        );
      }
      return n;
    }
  ).option(
    "--list-stored-auth",
    "Print the server URLs that have a stored OAuth token (one JSON object on stdout) and exit. No server connection is made."
  ).option(
    "--print-handoff",
    "Print a JSON handoff block (deepLink, portForwardCmd, oauthStatePath, apiToken) for --server-url and exit. No server connection is made."
  );
  program.parse(preArgs);
  const options = program.opts();
  if (options.relogin) {
    if (options.storedAuthOnly) {
      throw new Error("--relogin cannot be combined with --stored-auth-only");
    }
    if (options.useStoredAuth || options.waitForAuth !== void 0) {
      throw new Error(
        "--relogin cannot be combined with --use-stored-auth or --wait-for-auth"
      );
    }
    if (options.listStoredAuth || options.printHandoff) {
      throw new Error(
        "--relogin cannot be combined with --list-stored-auth or --print-handoff"
      );
    }
    if (options.method === "servers/list" || options.method === "servers/show") {
      throw new Error(
        "--relogin cannot be combined with --method servers/list or servers/show (no OAuth connect)"
      );
    }
  }
  if (options.strict) {
    if (options.method !== "tools/list") {
      throw new Error("--strict requires --method tools/list.");
    }
    if (options.appInfo) {
      throw new Error(
        "--strict cannot be combined with --app-info; run tools/list twice, once for each."
      );
    }
  }
  const oauthStatePath = getStateFilePath();
  if (options.listStoredAuth) {
    const servers = await readOAuthServers(oauthStatePath);
    const withToken = Object.entries(servers).filter(([, v]) => Boolean(v.tokens?.access_token)).map(([k]) => k);
    await awaitableLog(
      JSON.stringify({ oauthStatePath, storedServerUrls: withToken }) + "\n"
    );
    return { shortCircuit: true };
  }
  if (options.printHandoff) {
    if (!options.serverUrl) {
      throw new Error("--print-handoff requires --server-url");
    }
    await awaitableLog(
      JSON.stringify(
        buildHandoff(options.serverUrl, oauthStatePath, options.transport)
      ) + "\n"
    );
    return { shortCircuit: true };
  }
  if (!options.method) {
    throw new Error(
      "Method is required. Use --method to specify the method to invoke."
    );
  }
  const isCatalogMethod = options.method === "servers/list" || options.method === "servers/show";
  if (!isCatalogMethod && !isOneShotMethod(options.method)) {
    throw new Error(
      `Unsupported method: ${options.method}. Supported --cli methods: ${ONE_SHOT_METHODS.join(", ")}, servers/list, servers/show.`
    );
  }
  const adHoc = targetArgs.length > 0 || Boolean(options.transport) || Boolean(options.serverUrl?.trim());
  const envCatalog = adHoc ? void 0 : process.env.MCP_CATALOG_PATH;
  const serverOptions = {
    // `?.trim() ||` (not `??`) so an explicit empty `--catalog ""` still falls
    // back to MCP_CATALOG_PATH — keeps CLI and TUI flag resolution identical.
    catalogPath: options.catalog?.trim() || envCatalog,
    configPath: options.config?.trim() || void 0,
    target: targetArgs.length > 0 ? targetArgs : void 0,
    transport: options.transport,
    serverUrl: options.serverUrl,
    cwd: options.cwd,
    env: options.e,
    // `--header` is merged into the resolved server's settings (overriding any
    // file-level headers); file timeouts/OAuth are preserved. See #1482.
    headers: options.header
  };
  if (options.method === "servers/list") {
    const servers = await listServerEntries(serverOptions);
    await writeFormattedResult(
      { servers },
      options.format === "json" ? "json" : "text"
    );
    return { shortCircuit: true };
  }
  if (options.method === "servers/show") {
    if (!options.server?.trim()) {
      throw new Error(
        "servers/show requires --server <name> to select a catalog entry."
      );
    }
    const server = await showServerEntry(options.server, serverOptions);
    await writeFormattedResult(
      server,
      options.format === "json" ? "json" : "text"
    );
    return { shortCircuit: true };
  }
  if (options.waitForAuth !== void 0 || options.useStoredAuth) {
    if (!options.serverUrl) {
      throw new Error(
        `${options.waitForAuth !== void 0 ? "--wait-for-auth" : "--use-stored-auth"} requires --server-url`
      );
    }
    let token;
    if (options.waitForAuth !== void 0) {
      token = await waitForStoredToken(
        options.serverUrl,
        oauthStatePath,
        options.waitForAuth
      );
    } else {
      const servers = await readOAuthServers(oauthStatePath);
      const stored = findStoredServerState(servers, options.serverUrl);
      if (stored?.state.tokens?.refresh_token) {
        const storedAccess = stored.state.tokens.access_token;
        try {
          token = await refreshStoredAuthToken(
            options.serverUrl,
            oauthStatePath
          );
        } catch (err) {
          if (!storedAccess) throw err;
          token = storedAccess;
        }
      } else {
        const found = findStoredToken(servers, options.serverUrl);
        if (!found) {
          const key = normalizeServerUrl2(options.serverUrl);
          const storedKeys = Object.keys(servers);
          throw new CliExitCodeError(
            EXIT_CODES.AUTH_REQUIRED,
            `No stored OAuth token for ${key} in ${oauthStatePath}. Complete the OAuth flow in the web inspector first.` + (storedKeys.length > 0 ? ` Stored keys: ${storedKeys.join(", ")}.` : ""),
            { code: "no_stored_token", url: options.serverUrl }
          );
        }
        token = found;
      }
    }
    serverOptions.headers = {
      ...serverOptions.headers ?? {},
      Authorization: `Bearer ${token}`
    };
  }
  const entries = await loadServerEntries(serverOptions);
  const selected = selectServerEntry(entries, options.server);
  const serverConfig = selected.config;
  const serverSettings = withConnectTimeout(
    selected.settings,
    options.connectTimeout ?? (adHoc ? DEFAULT_CONNECT_TIMEOUT_MS : void 0)
  );
  if (options.appInfo && options.method !== "tools/call" && options.method !== "tools/list") {
    throw new Error(
      "--app-info requires --method tools/call (with --tool-name) or --method tools/list."
    );
  }
  let toolArg = options.toolArg;
  if (options.toolArgsJson !== void 0) {
    if (toolArg && Object.keys(toolArg).length > 0) {
      throw new Error(
        "--tool-args-json cannot be combined with --tool-arg; pick one."
      );
    }
    let parsed;
    try {
      parsed = JSON.parse(options.toolArgsJson);
    } catch (e) {
      throw new Error(
        `--tool-args-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,
        { cause: e }
      );
    }
    if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
      throw new Error("--tool-args-json must be a JSON object.");
    }
    toolArg = parsed;
  }
  const methodArgs = {
    method: options.method,
    toolName: options.toolName,
    toolArg,
    uri: options.uri,
    promptName: options.promptName,
    promptArgs: options.promptArgs,
    logLevel: options.logLevel,
    // `--metadata`/`--tool-metadata` values are parsed as JSON, and `_meta`
    // takes any JSON — so they go through unflattened (#1910). They used to be
    // squeezed through `metaValueToString`, which sent `{"a":1}` as the
    // *string* `'{"a":1}'`.
    metadata: options.metadata,
    toolMeta: options.toolMetadata,
    appInfo: options.appInfo === true,
    strict: options.strict === true,
    format: options.format
  };
  return {
    serverConfig,
    serverSettings,
    methodArgs,
    clientConfigPath: options.clientConfig,
    clientId: options.clientId,
    clientSecret: options.clientSecret,
    clientMetadataUrl: options.clientMetadataUrl,
    callbackUrl: options.callbackUrl,
    storedAuthOnly: options.storedAuthOnly === true,
    relogin: options.relogin === true
  };
}
async function runCli(argv) {
  const parsed = await parseArgs(argv ?? process.argv);
  if (parsed.shortCircuit) return;
  const {
    serverConfig,
    serverSettings,
    methodArgs,
    clientConfigPath,
    clientId,
    clientSecret,
    clientMetadataUrl,
    callbackUrl,
    storedAuthOnly,
    relogin
  } = parsed;
  const clientConfig = await loadRunnerClientConfig({ clientConfigPath });
  let callbackUrlConfig;
  try {
    callbackUrlConfig = parseRunnerOAuthCallbackUrl(callbackUrl);
  } catch (err) {
    throw new CliExitCodeError(
      EXIT_CODES.USAGE,
      err instanceof Error ? err.message : String(err)
    );
  }
  await callMethod(
    serverConfig,
    serverSettings,
    methodArgs,
    clientConfig,
    {
      clientId,
      clientSecret,
      clientMetadataUrl
    },
    callbackUrlConfig,
    storedAuthOnly === true,
    relogin === true
  );
}

// src/index.ts
var __filename = fileURLToPath2(import.meta.url);
var isMain = process.argv[1] !== void 0 && resolve6(process.argv[1]) === resolve6(__filename);
if (isMain) {
  runCli(process.argv).then(() => process.exit(0)).catch(handleError);
}
export {
  handleError,
  runCli,
  validLogLevels
};
/* v8 ignore next 2 -- @preserve: the non-Error arm is unreachable via
   proper-lockfile, which rejects only with Errors. */
/* v8 ignore next -- @preserve: node validates the cost parameters
   synchronously (an out-of-grammar `N` throws from the call itself,
   which the corrupt-KDF test exercises), so this asynchronous channel
   is left for allocation failure — not provokable deterministically. */
/* v8 ignore next -- @preserve: unreachable, `encryptedEnvelopeProblem`
   above returns a reason for anything `decodeParts` would reject. Kept as
   the type narrowing `parts` needs. */
/* v8 ignore next -- @preserve: the walk can only run out of ancestors if
   the filesystem root itself does not exist, which would mean the process
   has no cwd to have resolved against. Kept as the loop's termination
   guard rather than trusting that invariant. */
