"use strict";
const errors = require("./errors-_b8pNwA7.cjs");
const paths = require("./paths-C6z4_Obm.cjs");
const vscode = require("vscode");
require("@opentelemetry/api");
require("node-machine-id");
const fs = require("fs");
const path = require("path");
const zod = require("zod");
const resolveRegistryEntries = require("./resolve-registry-entries-C7jM70XK.cjs");
const JSONC = require("comment-json");
const crypto = require("crypto");
const http = require("http");
require("os");
function _interopNamespaceDefault(e) {
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
  if (e) {
    for (const k in e) {
      if (k !== "default") {
        const d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: () => e[k]
        });
      }
    }
  }
  n.default = e;
  return Object.freeze(n);
}
const vscode__namespace = /* @__PURE__ */ _interopNamespaceDefault(vscode);
const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
const JSONC__namespace = /* @__PURE__ */ _interopNamespaceDefault(JSONC);
const http__namespace = /* @__PURE__ */ _interopNamespaceDefault(http);
const OAuthConfigSchema = zod.z.object({
  /** Pre-registered OAuth client ID. When provided, skips Dynamic Client Registration (DCR). */
  clientId: zod.z.string().optional(),
  /** Custom redirect URI for OAuth callback (e.g., "127.0.0.1:8080"). If not specified, a random port is used. */
  redirectUri: zod.z.string().optional()
});
const MCPOptionsSchema = zod.z.object({
  // Local (STDIO) options
  command: zod.z.string().optional(),
  args: zod.z.array(zod.z.string()).optional(),
  env: zod.z.record(zod.z.string()).optional(),
  cwd: zod.z.string().optional(),
  // Remote (StreamableHTTP / SSE) options
  url: zod.z.string().optional(),
  headers: zod.z.record(zod.z.string()).optional(),
  // Common options
  type: zod.z.string().optional(),
  timeout: zod.z.number().positive().optional(),
  disabled: zod.z.boolean().default(false),
  autoApprove: zod.z.array(zod.z.string()).optional(),
  disabledTools: zod.z.array(zod.z.string()).optional(),
  /** OAuth configuration for servers requiring pre-registered clients or custom redirect URIs. */
  oauth: OAuthConfigSchema.optional(),
  /** OAuth scopes to request during authorization (e.g., ["search:read", "channels:read"]). */
  oauthScopes: zod.z.array(zod.z.string()).optional()
});
const MCPJsonConfigSchema = zod.z.object({
  mcpServers: zod.z.record(MCPOptionsSchema),
  powers: zod.z.object({
    mcpServers: zod.z.record(MCPOptionsSchema)
  }).optional()
});
function isHttpsOrLocalhost(urlString) {
  try {
    const url = new URL(urlString);
    if (url.protocol === "https:") {
      return true;
    }
    const hostname = url.hostname;
    return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
  } catch {
    return false;
  }
}
const APPROVED_ENV_VARS_KEY = "mcpApprovedEnvVars";
let hasShownWarningThisSession = false;
const globalUnapprovedVarsFound = /* @__PURE__ */ new Set();
async function showUnapprovedVarsWarningIfNeeded() {
  if (globalUnapprovedVarsFound.size > 0 && !hasShownWarningThisSession) {
    hasShownWarningThisSession = true;
    const unapprovedVars = Array.from(globalUnapprovedVarsFound);
    errors.mcpLogger.warn(`Found unapproved environment variables in MCP config: ${unapprovedVars.join(", ")}`);
    const message = `🔒 Security Warning: Your MCP configuration contains environment variables that have not been approved: ${unapprovedVars.join(", ")}. These variables will not be expanded unless you approve them. Expanding untrusted environment variables could pose security risks.`;
    const selection = await vscode__namespace.window.showErrorMessage(message, "Approve & Allow");
    if (selection === "Approve & Allow") {
      const currentApproved = vscode__namespace.workspace.getConfiguration("kiroAgent").get(APPROVED_ENV_VARS_KEY, []);
      const updatedApproved = [.../* @__PURE__ */ new Set([...currentApproved, ...unapprovedVars])];
      await vscode__namespace.workspace.getConfiguration("kiroAgent").update(APPROVED_ENV_VARS_KEY, updatedApproved, vscode__namespace.ConfigurationTarget.Global);
      errors.mcpLogger.info(`User approved environment variables: ${unapprovedVars.join(", ")}`);
      hasShownWarningThisSession = false;
      const configLocation = paths.getActiveMcpConfigLocation(getWorkspaceDirs());
      const allConfigPaths = [
        ...configLocation.workspaceConfigPaths,
        ...configLocation.userConfigPath ? [configLocation.userConfigPath] : []
      ];
      for (const configPath of allConfigPaths) {
        try {
          const now = /* @__PURE__ */ new Date();
          fs__namespace.utimesSync(configPath, now, now);
        } catch (e) {
          errors.mcpLogger.error(
            `Failed to trigger config reload for ${configPath}: ${e instanceof Error ? e.message : String(e)}`
          );
        }
      }
      await vscode__namespace.window.showInformationMessage(
        "✅ Variables approved. Environment variables are now being expanded and MCP servers are reconnecting."
      );
    }
  }
}
function loadPowersMcpConfig() {
  const userConfigPath = path__namespace.join(paths.getHomeKiroPath(), "settings", "mcp.json");
  if (!fs__namespace.existsSync(userConfigPath)) {
    return null;
  }
  try {
    const rawContent = fs__namespace.readFileSync(userConfigPath, "utf-8");
    const parsed = JSONC__namespace.parse(rawContent);
    if (parsed && typeof parsed === "object" && "powers" in parsed && parsed.powers && typeof parsed.powers === "object" && "mcpServers" in parsed.powers && typeof parsed.powers.mcpServers === "object") {
      const expandedPowers = expandEnvironmentVariables(parsed.powers);
      const expandedPowersTyped = expandedPowers;
      const powersConfigToValidate = {
        mcpServers: expandedPowersTyped.mcpServers
      };
      const result = MCPJsonConfigSchema.safeParse(powersConfigToValidate);
      if (result.success) {
        const validServers = {};
        Object.entries(result.data.mcpServers).forEach((entry) => {
          const validationResult = deepValidateMCPServerOptions(entry[0], entry[1]);
          if (validationResult.isValid) {
            validServers[entry[0]] = entry[1];
          } else {
            errors.mcpLogger.warn(validationResult.invalidMessage || "", entry[0]);
            void surfaceConfigError(validationResult.invalidMessage || "", "powers", userConfigPath);
          }
        });
        const serverNames = Object.keys(validServers);
        errors.mcpLogger.debug(
          `[Powers Debug] loadPowersMcpConfig found ${serverNames.length} valid power servers: ${serverNames.join(", ")}`
        );
        return { mcpServers: validServers };
      } else {
        void surfaceConfigError(`Invalid powers config format: ${result.error.message}`, "powers", userConfigPath);
        return null;
      }
    }
    errors.mcpLogger.warn("[Powers Debug] No powers.mcpServers section found in user mcp.json");
    return null;
  } catch (error) {
    const errorMsg = `Error loading powers config: ${error instanceof Error ? error.message : String(error)}`;
    errors.mcpLogger.error("[Powers Debug] " + errorMsg);
    void surfaceConfigError(errorMsg, "powers", userConfigPath);
    return null;
  }
}
const mcpServerSources = /* @__PURE__ */ new Map();
function loadMcpConfig() {
  globalUnapprovedVarsFound.clear();
  hasShownWarningThisSession = false;
  mcpServerSources.clear();
  const configLocation = paths.getActiveMcpConfigLocation(getWorkspaceDirs());
  const userConfig = loadIndividualMcpConfig("user", configLocation.userConfigPath);
  const workspaceConfigs = configLocation.workspaceConfigPaths.map(
    (path2) => loadIndividualMcpConfig("workspace", path2)
  );
  const powersConfig = loadPowersMcpConfig();
  const finalConfig = {
    mcpServers: {}
  };
  if (userConfig) {
    for (const serverName of Object.keys(userConfig.mcpServers)) {
      finalConfig.mcpServers[serverName] = userConfig.mcpServers[serverName];
      mcpServerSources.set(serverName, "user");
    }
  }
  for (const workspaceConfig of workspaceConfigs) {
    if (workspaceConfig) {
      for (const serverName of Object.keys(workspaceConfig.mcpServers)) {
        finalConfig.mcpServers[serverName] = workspaceConfig.mcpServers[serverName];
        mcpServerSources.set(serverName, "workspace");
      }
    }
  }
  if (powersConfig) {
    for (const serverName of Object.keys(powersConfig.mcpServers)) {
      finalConfig.mcpServers[serverName] = powersConfig.mcpServers[serverName];
      mcpServerSources.set(serverName, "power");
    }
  }
  if (Object.keys(finalConfig.mcpServers).length === 0) {
    return null;
  }
  const powerServers = Array.from(mcpServerSources.entries()).filter(([_, source]) => source === "power").map(([name, _]) => name);
  if (powerServers.length > 0) {
    errors.mcpLogger.debug(`[Powers Debug] Loaded ${powerServers.length} power MCP servers: ${powerServers.join(", ")}`);
  }
  void showUnapprovedVarsWarningIfNeeded();
  return finalConfig;
}
function expandEnvironmentVariables(value, options) {
  if (typeof value === "string") {
    const expandAll = options?.expandAll === true;
    const fallbackToEmpty = options?.fallbackToEmpty === true;
    const envOverride = options?.env;
    const approvedVars = expandAll ? void 0 : vscode__namespace.workspace.getConfiguration("kiroAgent").get(APPROVED_ENV_VARS_KEY, []);
    return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (match, varName) => {
      if (approvedVars && !approvedVars.includes(varName)) {
        globalUnapprovedVarsFound.add(varName);
        return match;
      }
      const envValue = envOverride?.[varName] ?? process.env[varName];
      if (envValue !== void 0) {
        if (envValue.trim().startsWith("()")) {
          return fallbackToEmpty ? "" : match;
        }
        errors.mcpLogger.debug(`Expanded environment variable: ${varName}`);
        return envValue;
      }
      return fallbackToEmpty ? "" : match;
    });
  }
  if (Array.isArray(value)) {
    return value.map((item) => expandEnvironmentVariables(item, options));
  }
  if (value && typeof value === "object") {
    const expanded = {};
    for (const [key, val] of Object.entries(value)) {
      expanded[key] = expandEnvironmentVariables(val, options);
    }
    return expanded;
  }
  return value;
}
function loadIndividualMcpConfig(sourceName, path2) {
  if (!path2) return null;
  try {
    const configContent = fs__namespace.readFileSync(path2, "utf8");
    const parsedJson = JSONC__namespace.parse(configContent);
    const expandedJson = expandEnvironmentVariables(parsedJson);
    const result = MCPJsonConfigSchema.safeParse(expandedJson);
    if (result.success) {
      const validServers = {};
      Object.entries(result.data.mcpServers).forEach((entry) => {
        const validationResult = deepValidateMCPServerOptions(entry[0], entry[1]);
        if (validationResult.isValid) {
          validServers[entry[0]] = entry[1];
        } else {
          errors.mcpLogger.warn(validationResult.invalidMessage || "", entry[0]);
          void surfaceConfigError(validationResult.invalidMessage || "", sourceName, path2);
        }
      });
      return {
        mcpServers: validServers,
        powers: result.data.powers
      };
    } else {
      void surfaceConfigError(
        `Invalid mcp.json format in ${sourceName} directory: ${result.error.message}`,
        sourceName,
        path2
      );
      return null;
    }
  } catch (e) {
    void surfaceConfigError(
      `Error loading mcp.json from ${sourceName} directory: ${e instanceof Error ? e.message : String(e)}`,
      sourceName,
      path2
    );
    return null;
  }
}
async function surfaceConfigError(errorMsg, sourceName, configPath) {
  errors.mcpLogger.error(errorMsg);
  const selection = await vscode__namespace.window.showErrorMessage(errorMsg, "Ask Kiro", "Open Settings");
  if (selection === "Ask Kiro") {
    const message = `I'm getting this MCP configuration error: "${errorMsg}". Can you help me fix it?`;
    void vscode__namespace.commands.executeCommand("kiroAgent.focusChatInput", { prompt: message, clear: true });
  } else if (selection === "Open Settings") {
    if (sourceName === "workspace") {
      await vscode__namespace.commands.executeCommand("kiroAgent.openWorkspaceMcpConfig", false, configPath);
    } else {
      await vscode__namespace.commands.executeCommand("kiroAgent.openUserMcpConfig", false);
    }
  }
}
function deepValidateMCPServerOptions(serverName, options) {
  if (options.type === "registry") {
    const result = resolveRegistryEntries.RegistryServerConfigEntrySchema.safeParse(options);
    if (!result.success) {
      return {
        isValid: false,
        invalidMessage: `Invalid registry config for MCP Server ${serverName}: ${result.error.message}`
      };
    }
    return { isValid: true };
  }
  if (!options.disabled && !(options.command || options.url)) {
    return {
      isValid: false,
      invalidMessage: `Enabled MCP Server ${serverName} must specify a command or URL, ignoring.`
    };
  }
  if (options.url) {
    try {
      new URL(options.url);
    } catch {
      return {
        isValid: false,
        invalidMessage: `Invalid MCP Server ${serverName} URL: ${options.url}, ignoring.`
      };
    }
    if (!isHttpsOrLocalhost(options.url)) {
      return {
        isValid: false,
        invalidMessage: `Remote MCP Servers must use https or localhost, ignoring.`
      };
    }
  }
  return {
    isValid: true
  };
}
async function addMCPServerConfig(serverName, config) {
  await vscode__namespace.commands.executeCommand("kiroAgent.openUserMcpConfig");
  const userConfigLocation = paths.getActiveMcpConfigLocation(getWorkspaceDirs());
  if (!userConfigLocation.userConfigPath) {
    void vscode__namespace.window.showErrorMessage("Failed to create User MCP server config");
    return;
  }
  const currentConfig = loadIndividualMcpConfig("user", userConfigLocation.userConfigPath);
  if (!currentConfig) {
    return;
  }
  if (serverName in currentConfig.mcpServers) {
    void vscode__namespace.window.showErrorMessage(`MCP Server ${serverName} already exists in user config`);
    return;
  }
  let newServerConfig;
  try {
    const parsed = JSONC__namespace.parse(config);
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
      throw new Error(`Invalid JSON object: ${config}`);
    }
    newServerConfig = {
      ...parsed,
      disabled: true
    };
  } catch (e) {
    void vscode__namespace.window.showErrorMessage(`Invalid MCP server config: ${e instanceof Error ? e.message : String(e)}`);
    return;
  }
  const newConfig = {
    ...currentConfig,
    mcpServers: {
      ...currentConfig.mcpServers,
      [serverName]: newServerConfig
    }
  };
  fs__namespace.writeFileSync(userConfigLocation.userConfigPath, JSONC__namespace.stringify(newConfig, null, 2));
  const selection = await vscode__namespace.window.showInformationMessage(
    `MCP Server "${serverName}" added to user config, would you like to enable it?`,
    "Enable"
  );
  if (selection === "Enable") {
    setMCPServerDisabled(serverName, false);
  }
}
function setMCPServerDisabled(serverName, disabled) {
  const disableServerTransform = (options) => {
    return { ...options, disabled };
  };
  applyServerConfigTransform(serverName, disableServerTransform);
}
function enableMCPTools(serverName, toolNames) {
  const enableToolsTransform = (options) => {
    const currentDisabledTools = options.disabledTools || [];
    const updatedDisabledTools = currentDisabledTools.filter((val) => !toolNames.includes(val));
    return {
      ...options,
      disabledTools: updatedDisabledTools
    };
  };
  applyServerConfigTransform(serverName, enableToolsTransform);
}
function disableMCPTools(serverName, toolNames) {
  const disableToolsTransform = (options) => {
    const currentDisabledTools = options.disabledTools || [];
    const toolsToDisable = toolNames.filter((it) => !currentDisabledTools.includes(it));
    const updatedDisabledTools = currentDisabledTools.concat(toolsToDisable);
    return {
      ...options,
      disabledTools: updatedDisabledTools
    };
  };
  applyServerConfigTransform(serverName, disableToolsTransform);
}
async function addMCPToolToAutoApproveConfig(serverName, toolName) {
  const autoApproveToolsTransform = (options) => {
    return {
      ...options,
      autoApprove: (options.autoApprove || []).concat(toolName)
    };
  };
  applyServerConfigTransform(serverName, autoApproveToolsTransform);
  const addedMessage = `Tool ${toolName} added to auto-approve list`;
  errors.mcpLogger.info(addedMessage, serverName);
  const selection = await vscode__namespace.window.showInformationMessage(addedMessage, "Open Settings");
  if (selection === "Open Settings") {
    const matchingConfig = findConfigFileForServer(serverName);
    if (matchingConfig.sourceName === "workspace") {
      await vscode__namespace.commands.executeCommand("kiroAgent.openWorkspaceMcpConfig");
    } else {
      await vscode__namespace.commands.executeCommand("kiroAgent.openUserMcpConfig");
    }
  }
}
function applyServerConfigTransform(serverName, transform) {
  const matchingConfig = findConfigFileForServer(serverName);
  const rawContent = fs__namespace.readFileSync(matchingConfig.path, "utf-8");
  const parsed = JSONC__namespace.parse(rawContent);
  const fullConfig = parsed && typeof parsed === "object" ? parsed : {};
  if (matchingConfig.sourceName === "powers") {
    if (!fullConfig.powers) {
      fullConfig.powers = { mcpServers: {} };
    }
    if (!fullConfig.powers.mcpServers) {
      fullConfig.powers.mcpServers = {};
    }
    const existingConfig = fullConfig.powers.mcpServers[serverName] ?? {};
    fullConfig.powers.mcpServers[serverName] = transform(existingConfig);
  } else {
    if (!fullConfig.mcpServers) {
      fullConfig.mcpServers = {};
    }
    const existingConfig = fullConfig.mcpServers[serverName] ?? {};
    fullConfig.mcpServers[serverName] = transform(existingConfig);
  }
  fs__namespace.writeFileSync(matchingConfig.path, JSONC__namespace.stringify(fullConfig, null, 2));
}
function findConfigFileForServer(serverName) {
  const configLocation = paths.getActiveMcpConfigLocation(getWorkspaceDirs());
  if (!configLocation.userConfigPath && configLocation.workspaceConfigPaths.length === 0) {
    throw new Error("No MCP config file found");
  }
  for (let i = configLocation.workspaceConfigPaths.length - 1; i >= 0; i--) {
    const workspacePath = configLocation.workspaceConfigPaths[i];
    const config = loadIndividualMcpConfig("workspace", workspacePath);
    if (config && serverName in config.mcpServers) {
      return { path: workspacePath, config, sourceName: "workspace" };
    }
  }
  if (configLocation.userConfigPath) {
    const config = loadIndividualMcpConfig("user", configLocation.userConfigPath);
    if (config && serverName in config.mcpServers) {
      return { path: configLocation.userConfigPath, config, sourceName: "user" };
    }
    const rawContent = fs__namespace.readFileSync(configLocation.userConfigPath, "utf-8");
    const parsed = JSONC__namespace.parse(rawContent);
    const fullConfig = parsed && typeof parsed === "object" ? parsed : {};
    if (fullConfig.powers?.mcpServers?.[serverName]) {
      const powersConfig = { mcpServers: fullConfig.powers.mcpServers };
      return { path: configLocation.userConfigPath, config: powersConfig, sourceName: "powers" };
    }
  }
  throw new Error(`Server ${serverName} not found in any MCP config file`);
}
function getWorkspaceDirs() {
  const workspaceFolders = vscode__namespace.workspace.workspaceFolders;
  return workspaceFolders?.map((folder) => folder.uri.fsPath);
}
function resetApprovedEnvVars() {
  void vscode__namespace.workspace.getConfiguration("kiroAgent").update(APPROVED_ENV_VARS_KEY, [], vscode__namespace.ConfigurationTarget.Global);
  hasShownWarningThisSession = false;
  errors.mcpLogger.info("Reset approved environment variables list");
}
class CredentialStorageManager {
  secretStorage;
  connectionHash;
  keyPrefix = "kiro.mcp";
  constructor(secretStorage, options) {
    this.secretStorage = secretStorage;
    this.connectionHash = this.calculateConnectionHash(options.url || "", options.headers || {});
  }
  /**
   * Canonicalizes a server URL for credential keying.
   *
   * Only components whose equivalence is defined by URL semantics are normalized: the parser
   * lowercases scheme and hostname and drops default ports. Path, query, username, and password
   * are case-sensitive in general (distinct tenants can differ only by path case), so they are
   * preserved exactly — lowercasing them would let distinct endpoints share OAuth credentials.
   * A malformed URL is used verbatim (trimmed) so its keying behavior is deterministic rather
   * than depending on a lossy transformation.
   */
  canonicalizeServerUrl(serverUrl) {
    const trimmed = serverUrl.trim();
    try {
      return new URL(trimmed).toString();
    } catch {
      return trimmed;
    }
  }
  /**
   * Calculate a unique hash from server URL and headers to isolate credentials
   * per server configuration.
   */
  calculateConnectionHash(serverUrl, headers) {
    const normalizedUrl = this.canonicalizeServerUrl(serverUrl);
    const sortedHeaders = Object.keys(headers).sort().map((key) => `${key}:${headers[key]}`).join("|");
    const combined = `${normalizedUrl}|${sortedHeaders}`;
    return crypto.createHash("sha256").update(combined).digest("hex");
  }
  /**
   * Get the storage key for client information
   */
  getClientKey() {
    return `${this.keyPrefix}.${this.connectionHash}.client`;
  }
  /**
   * Get the storage key for tokens
   */
  getTokensKey() {
    return `${this.keyPrefix}.${this.connectionHash}.tokens`;
  }
  /**
   * Get the storage key for code verifier
   */
  getVerifierKey() {
    return `${this.keyPrefix}.${this.connectionHash}.verifier`;
  }
  /**
   * Save OAuth client information to SecretStorage
   */
  async saveClientInfo(clientInfo) {
    try {
      const stored = {
        client_id: clientInfo.client_id,
        client_secret: clientInfo.client_secret,
        client_id_issued_at: clientInfo.client_id_issued_at,
        client_secret_expires_at: clientInfo.client_secret_expires_at,
        ..."token_endpoint_auth_method" in clientInfo && {
          token_endpoint_auth_method: clientInfo.token_endpoint_auth_method
        },
        created_at: Date.now()
      };
      await this.secretStorage.store(this.getClientKey(), JSON.stringify(stored));
    } catch (error) {
      throw new Error(`Failed to save client information: ${error instanceof Error ? error.message : String(error)}`);
    }
  }
  /**
   * Type guard to validate StoredClientInfo structure
   */
  isValidStoredClientInfo(obj) {
    return obj !== null && typeof obj === "object" && "client_id" in obj && typeof obj.client_id === "string" && obj.client_id.length > 0;
  }
  /**
   * Type guard to validate StoredTokens structure
   */
  isValidStoredTokens(obj) {
    return obj !== null && typeof obj === "object" && "access_token" in obj && "token_type" in obj && typeof obj.access_token === "string" && typeof obj.token_type === "string" && obj.access_token.length > 0 && obj.token_type.length > 0;
  }
  /**
   * Type guard to validate StoredVerifier structure
   */
  isValidStoredVerifier(obj) {
    return obj !== null && typeof obj === "object" && "code_verifier" in obj && typeof obj.code_verifier === "string" && obj.code_verifier.length > 0;
  }
  /**
   * Load OAuth client information from SecretStorage
   */
  async loadClientInfo() {
    try {
      const stored = await this.secretStorage.get(this.getClientKey());
      if (!stored) {
        return void 0;
      }
      const parsed = JSON.parse(stored);
      if (!this.isValidStoredClientInfo(parsed)) {
        await this.clearCredentials("client");
        return void 0;
      }
      return parsed;
    } catch {
      await this.clearCredentials("client");
      return void 0;
    }
  }
  /**
   * Save OAuth tokens to SecretStorage with timestamp metadata
   */
  async saveTokens(tokens) {
    try {
      const now = Date.now();
      const stored = {
        access_token: tokens.access_token,
        refresh_token: tokens.refresh_token,
        token_type: tokens.token_type,
        expires_in: tokens.expires_in,
        scope: tokens.scope,
        created_at: now,
        // Calculate expires_at if expires_in is provided
        expires_at: tokens.expires_in ? now + tokens.expires_in * 1e3 : void 0
      };
      await this.secretStorage.store(this.getTokensKey(), JSON.stringify(stored));
    } catch (error) {
      throw new Error(`Failed to save tokens: ${error instanceof Error ? error.message : String(error)}`);
    }
  }
  /**
   * Load OAuth tokens from SecretStorage
   */
  async loadTokens() {
    try {
      const stored = await this.secretStorage.get(this.getTokensKey());
      if (!stored) {
        return void 0;
      }
      const parsed = JSON.parse(stored);
      if (!this.isValidStoredTokens(parsed)) {
        await this.clearCredentials("tokens");
        return void 0;
      }
      return parsed;
    } catch {
      await this.clearCredentials("tokens");
      return void 0;
    }
  }
  /**
   * Save PKCE code verifier to SecretStorage
   */
  async saveCodeVerifier(verifier) {
    try {
      const stored = {
        code_verifier: verifier,
        created_at: Date.now()
      };
      await this.secretStorage.store(this.getVerifierKey(), JSON.stringify(stored));
    } catch (error) {
      throw new Error(`Failed to save code verifier: ${error instanceof Error ? error.message : String(error)}`);
    }
  }
  /**
   * Load PKCE code verifier from SecretStorage
   */
  async loadCodeVerifier() {
    try {
      const stored = await this.secretStorage.get(this.getVerifierKey());
      if (!stored) {
        return void 0;
      }
      const parsed = JSON.parse(stored);
      if (!this.isValidStoredVerifier(parsed)) {
        await this.clearCredentials("verifier");
        return void 0;
      }
      return parsed.code_verifier;
    } catch {
      await this.clearCredentials("verifier");
      return void 0;
    }
  }
  /**
   * Clear credentials from SecretStorage based on scope
   * @param scope - Which credentials to clear: 'all', 'client', 'tokens', or 'verifier'
   */
  async clearCredentials(scope) {
    try {
      switch (scope) {
        case "all":
          await Promise.all([
            this.secretStorage.delete(this.getClientKey()),
            this.secretStorage.delete(this.getTokensKey()),
            this.secretStorage.delete(this.getVerifierKey())
          ]);
          break;
        case "client":
          await this.secretStorage.delete(this.getClientKey());
          break;
        case "tokens":
          await this.secretStorage.delete(this.getTokensKey());
          break;
        case "verifier":
          await this.secretStorage.delete(this.getVerifierKey());
          break;
      }
    } catch (error) {
      throw new Error(`Failed to clear credentials: ${error instanceof Error ? error.message : String(error)}`);
    }
  }
}
function computeTokenExpiryBuffer(tokenLifetimeMs) {
  const MIN_BUFFER_MS = 5e3;
  const MAX_BUFFER_MS = 3e4;
  return Math.min(MAX_BUFFER_MS, Math.max(MIN_BUFFER_MS, tokenLifetimeMs * 0.05));
}
const DEFAULT_TOKEN_EXPIRY_BUFFER_MS = 1e4;
class MCPAuthProvider {
  /**
   * Creates a new MCP OAuth authentication provider.
   * @param secretStorage - VS Code's secret storage for persisting credentials across sessions.
   *                       If undefined, credentials will only be stored in memory.
   * @param options - MCP configuration options containing server-specific settings
   * @param startOAuth - Whether this provider should initiate OAuth flows. Set to false
   *                    if credentials are already available and no new flow is needed.
   */
  constructor(secretStorage, options, startOAuth) {
    this.startOAuth = startOAuth;
    this._baseClientMetadata = {
      redirect_uris: [],
      // Will be populated dynamically
      client_name: "kiro",
      grant_types: ["authorization_code", "refresh_token"],
      response_types: ["code"],
      ...options.oauthScopes?.length && { scope: options.oauthScopes.join(" ") }
    };
    this._state = crypto.randomUUID();
    if (secretStorage) {
      this.credentialStorage = new CredentialStorageManager(secretStorage, options);
    }
    if (options.oauth?.clientId) {
      this._preregisteredClientInfo = {
        client_id: options.oauth.clientId
      };
    }
  }
  /** The OAuth redirect URL for the authorization flow */
  _redirectUrl;
  /** Base OAuth client metadata configuration */
  _baseClientMetadata;
  /** OAuth state parameter for CSRF protection */
  _state;
  /** Optional credential storage manager for persistence */
  credentialStorage;
  /** In-memory cache for OAuth client information (session-scoped) */
  _clientInformation;
  /** In-memory cache for OAuth tokens (session-scoped) */
  _tokens;
  /** In-memory cache for PKCE code verifier (session-scoped) */
  _codeVerifier;
  /** Timestamp when tokens were last saved, used to compute expiry from expires_in */
  _tokensSavedAt;
  /** Pre-registered client info from config, used to skip DCR. */
  _preregisteredClientInfo;
  /**
   * Gets the OAuth redirect URL for the authorization flow.
   * @returns The redirect URL where the authorization server will send the user after authentication.
   *          Returns a placeholder URL if OAuth flow is not intended to start.
   * @throws {Error} If no redirect URL is available when OAuth flow should start
   */
  get redirectUrl() {
    if (!this.startOAuth) {
      return "http://localhost";
    }
    if (!this._redirectUrl) {
      throw new Error("No redirect URL available");
    }
    return this._redirectUrl;
  }
  /**
   * Sets the OAuth redirect URL for the authorization flow.
   * @param url - The redirect URL where the authorization server will send the user after authentication
   */
  set redirectUrl(url) {
    this._redirectUrl = url;
  }
  /**
   * Gets the OAuth client metadata including the current redirect URL.
   * @returns Complete OAuth client metadata with redirect URIs populated
   */
  get clientMetadata() {
    return {
      ...this._baseClientMetadata,
      redirect_uris: [this.redirectUrl]
    };
  }
  /**
   * Gets the OAuth state parameter for CSRF protection.
   * @returns A unique state string used to prevent cross-site request forgery attacks
   */
  state() {
    return this._state;
  }
  /**
   * Retrieves the OAuth client information (client ID, secret, etc.).
   *
   * First checks the in-memory cache, then falls back to persistent storage if available.
   * @returns Promise resolving to client information if available, undefined otherwise
   */
  async clientInformation() {
    if (this._preregisteredClientInfo) {
      return this._preregisteredClientInfo;
    }
    if (this._clientInformation) {
      return this._clientInformation;
    }
    if (this.credentialStorage) {
      this._clientInformation = await this.credentialStorage.loadClientInfo();
      return this._clientInformation;
    }
    return void 0;
  }
  /**
   * Saves OAuth client information to both memory and persistent storage.
   *
   * Extracts the essential client information from the full client data and stores it
   * in the in-memory cache and optionally in persistent storage.
   * @param clientInformation - Complete OAuth client information received from the authorization server
   */
  async saveClientInformation(clientInformation) {
    if (this._preregisteredClientInfo) {
      return;
    }
    const clientInfo = {
      client_id: clientInformation.client_id,
      client_secret: clientInformation.client_secret,
      client_id_issued_at: clientInformation.client_id_issued_at,
      client_secret_expires_at: clientInformation.client_secret_expires_at,
      // Preserve token_endpoint_auth_method so the SDK can select the correct
      // client authentication method (e.g. client_secret_post vs client_secret_basic)
      // for token requests. Without this, servers that require a specific method
      // (like Miro requiring client_secret_post) will reject the token exchange.
      ..."token_endpoint_auth_method" in clientInformation && {
        token_endpoint_auth_method: clientInformation.token_endpoint_auth_method
      }
    };
    this._clientInformation = clientInfo;
    if (this.credentialStorage) {
      await this.credentialStorage.saveClientInfo(clientInfo);
    }
  }
  /**
   * Retrieves the OAuth tokens (access token, refresh token, etc.).
   *
   * First checks the in-memory cache, then falls back to persistent storage if available.
   * @returns Promise resolving to OAuth tokens if available, undefined otherwise
   */
  async tokens() {
    if (this._tokens) {
      return this._tokens;
    }
    if (this.credentialStorage) {
      this._tokens = await this.credentialStorage.loadTokens();
      return this._tokens;
    }
    return void 0;
  }
  /**
   * Saves OAuth tokens to both memory and persistent storage.
   * @param tokens - OAuth tokens including access token, refresh token, and metadata
   */
  async saveTokens(tokens) {
    this._tokens = tokens;
    this._tokensSavedAt = Date.now();
    if (this.credentialStorage) {
      await this.credentialStorage.saveTokens(tokens);
    }
  }
  /**
   * Redirects the user to the OAuth authorization URL.
   *
   * Opens the authorization URL in the user's default browser if OAuth flow should start.
   * If the browser fails to open, shows an error message with the URL for manual copying.
   * @param authorizationUrl - The OAuth authorization URL to redirect the user to
   * @throws {Error} If the browser fails to open and manual intervention is required
   */
  async redirectToAuthorization(authorizationUrl) {
    try {
      if (this.startOAuth) {
        await vscode__namespace.env.openExternal(authorizationUrl.href);
      }
    } catch (error) {
      vscode__namespace.window.showErrorMessage(
        `Failed to open browser for authentication. Please copy and paste the URL into your browser: ${authorizationUrl}`
      );
      throw new Error(`Failed to open browser.`);
    }
  }
  /**
   * Saves the PKCE code verifier for the OAuth flow.
   *
   * The code verifier is used in the Proof Key for Code Exchange (PKCE) extension
   * to OAuth 2.0 for enhanced security in public clients.
   * @param codeVerifier - The randomly generated code verifier string
   */
  async saveCodeVerifier(codeVerifier) {
    this._codeVerifier = codeVerifier;
    if (this.credentialStorage) {
      await this.credentialStorage.saveCodeVerifier(codeVerifier);
    }
  }
  /**
   * Retrieves the PKCE code verifier for the OAuth flow.
   *
   * First checks the in-memory cache, then falls back to persistent storage if available.
   * @returns Promise resolving to the code verifier string
   * @throws {Error} If no code verifier is found in memory or storage
   */
  async codeVerifier() {
    if (this._codeVerifier) {
      return this._codeVerifier;
    }
    if (this.credentialStorage) {
      this._codeVerifier = await this.credentialStorage.loadCodeVerifier();
      if (this._codeVerifier) {
        return this._codeVerifier;
      }
    }
    throw new Error("Code verifier not found. Make sure to call saveCodeVerifier first.");
  }
  /**
   * Returns the token's expiry timestamp in milliseconds, or undefined if not available.
   * Used by MCPManagerSingleton to schedule push-based expiry notifications.
   */
  async getTokenExpiresAt() {
    const tokens = await this.tokens();
    if (!tokens || tokens.refresh_token) {
      return void 0;
    }
    const expiresAt = tokens.expires_at;
    if (typeof expiresAt === "number") {
      return expiresAt;
    }
    if (tokens.expires_in && this._tokensSavedAt) {
      return this._tokensSavedAt + tokens.expires_in * 1e3;
    }
    return void 0;
  }
  /**
   * Checks whether the current OAuth token is expired and cannot be refreshed by the SDK.
   *
   * Returns `true` only when the token's `expires_at` is in the past (or within the buffer)
   * AND no `refresh_token` is present (the SDK handles refresh when one exists).
   * Returns `false` for: no tokens, no `expires_at`, valid tokens, or tokens with a refresh_token.
   * @param bufferMs - Buffer in milliseconds before actual expiry to consider the token expired (default 30s)
   */
  async isTokenExpired(bufferMs) {
    const tokens = await this.tokens();
    if (!tokens) {
      return false;
    }
    if (tokens.refresh_token) {
      return false;
    }
    const tokenLifetimeMs = tokens.expires_in ? tokens.expires_in * 1e3 : void 0;
    const effectiveBuffer = bufferMs ?? (tokenLifetimeMs ? computeTokenExpiryBuffer(tokenLifetimeMs) : DEFAULT_TOKEN_EXPIRY_BUFFER_MS);
    const expiresAt = tokens.expires_at;
    if (typeof expiresAt === "number") {
      return expiresAt <= Date.now() + effectiveBuffer;
    }
    if (tokens.expires_in && this._tokensSavedAt) {
      const computedExpiresAt = this._tokensSavedAt + tokens.expires_in * 1e3;
      return computedExpiresAt <= Date.now() + effectiveBuffer;
    }
    return false;
  }
  /**
   * Returns whether the current tokens include a non-empty `refresh_token`.
   */
  async hasRefreshToken() {
    const tokens = await this.tokens();
    return !!tokens?.refresh_token;
  }
  /**
   * Invalidates and clears credentials based on the specified scope.
   *
   * Clears credentials from both the in-memory cache and persistent storage.
   * This is useful for logout functionality or when credentials become invalid.
   * @param scope - The scope of credentials to invalidate:
   *                - 'all': Clear all credentials (client info, tokens, code verifier)
   *                - 'client': Clear only client information
   *                - 'tokens': Clear only OAuth tokens
   *                - 'verifier': Clear only the code verifier
   */
  async invalidateCredentials(scope) {
    switch (scope) {
      case "all":
        this._clientInformation = void 0;
        this._tokens = void 0;
        this._codeVerifier = void 0;
        this._tokensSavedAt = void 0;
        break;
      case "client":
        this._clientInformation = void 0;
        break;
      case "tokens":
        this._tokens = void 0;
        this._tokensSavedAt = void 0;
        break;
      case "verifier":
        this._codeVerifier = void 0;
        break;
    }
    if (this.credentialStorage) {
      await this.credentialStorage.clearCredentials(scope);
    }
  }
}
class OAuthRedirectServer {
  server;
  callbackUrl;
  callbackPromise = null;
  callbackResolve = null;
  callbackReject = null;
  /**
   * Terminal callback result that arrived before any waiter registered.
   *
   * The browser can complete the redirect while `client.connect()` is still unwinding, i.e.
   * before `waitForCallback()` runs. Buffering the first terminal outcome makes delivery
   * order-independent: whichever of the callback and the waiter arrives first, the waiter gets
   * exactly the original result, exactly once.
   */
  bufferedOutcome = null;
  serverCloseTimeout = 5e3;
  // 5 seconds timeout for server close
  /**
   * Starts the OAuth redirect server. Uses the specified port or an available random port.
   * @param port - Optional port to bind to. If not specified, a random available port is used.
   * @returns Promise that resolves with the URL for callback when server is ready
   */
  async start(port) {
    errors.mcpLogger.info("Starting server for MCP OAuth redirect...");
    try {
      this.callbackUrl = await this.attemptServerStart(port);
      errors.mcpLogger.info(`Server for MCP OAuth redirect started on ${this.callbackUrl}`);
      return this.callbackUrl;
    } catch (error) {
      throw new Error(
        `Failed to start OAuth redirect server: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }
  /**
   * Attempts to start the server once.
   */
  attemptServerStart(port) {
    return new Promise((resolve, reject) => {
      this.server = http__namespace.createServer(this.handleRequest.bind(this));
      this.server.on("error", (error) => reject(error));
      this.server.on("listening", () => {
        const address = this.server?.address();
        if (address && typeof address === "object") {
          this.callbackUrl = `http://localhost:${address.port}/oauth/callback`;
          resolve(this.callbackUrl);
        } else {
          reject(new Error("Failed to get server address after successful listen"));
        }
      });
      try {
        this.server.listen(port ?? 0, "localhost");
      } catch (error) {
        reject(new Error(`Failed to start listening: ${error instanceof Error ? error.message : "Unknown error"}`));
      }
    });
  }
  /**
   * Stops the OAuth redirect server and cleans up resources with timeout protection.
   * @returns Promise that resolves when server is fully stopped
   */
  async stop() {
    errors.mcpLogger.info("Stopping server for MCP OAuth redirect");
    if (!this.server) {
      return;
    }
    return new Promise((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        void this.forceCleanup().then(resolve, reject);
      }, this.serverCloseTimeout);
      this.server?.close((error) => {
        clearTimeout(timeoutId);
        this.cleanup();
        if (error) {
          reject(new Error(`Error during graceful server shutdown: ${error.message}`));
        } else {
          resolve();
        }
      });
    });
  }
  /**
   * Forces immediate cleanup of server resources without waiting for graceful shutdown.
   */
  async forceCleanup() {
    if (this.server) {
      this.server.closeAllConnections();
      try {
        await new Promise((resolve) => {
          if (this.server?.listening) {
            this.server.close(() => resolve());
          } else {
            resolve();
          }
        });
      } catch {
      }
    }
    this.cleanup();
  }
  /**
   * Gets the callback URL for OAuth redirects.
   * @returns The full callback URL or undefined if server is not started
   */
  getCallbackUrl() {
    return this.callbackUrl;
  }
  /**
   * Waits for an OAuth callback to be received.
   *
   * If a terminal callback already arrived before this call, that buffered result is consumed
   * and delivered immediately; otherwise a pending promise is registered for the callback
   * handler to settle. Either way each terminal result is delivered exactly once.
   * @returns Promise that resolves with the authorization code and state
   */
  async waitForCallback() {
    const buffered = this.bufferedOutcome;
    if (buffered) {
      this.bufferedOutcome = null;
      if (buffered.kind === "success") {
        return buffered.data;
      }
      throw buffered.error;
    }
    if (!this.callbackPromise) {
      this.callbackPromise = new Promise((resolve, reject) => {
        this.callbackResolve = resolve;
        this.callbackReject = reject;
      });
    }
    return this.callbackPromise;
  }
  /**
   * Delivers a terminal callback result to the registered waiter, or buffers it for the next
   * waiter when none is registered yet. The first terminal result wins; later callbacks are
   * answered in the browser but never overwrite an undelivered result.
   */
  settleCallback(outcome) {
    if (this.callbackResolve && this.callbackReject) {
      if (outcome.kind === "success") {
        this.callbackResolve(outcome.data);
      } else {
        this.callbackReject(outcome.error);
      }
      this.resetCallbacks();
      return;
    }
    if (!this.bufferedOutcome) {
      this.bufferedOutcome = outcome;
    }
  }
  /**
   * Handles incoming HTTP requests to the OAuth redirect server.
   * Currently provides basic routing for the OAuth callback endpoint.
   */
  handleRequest(request, response) {
    const parsedUrl = new URL(request.url || "", this.callbackUrl);
    if (parsedUrl.pathname === "/oauth/callback") {
      this.handleOAuthCallback(request, response, parsedUrl.searchParams);
    } else {
      response.writeHead(404, { "Content-Type": "text/plain" });
      response.end("Not Found");
    }
  }
  /**
   * Handles OAuth callback requests.
   * Extracts authorization code and state parameters, validates the request,
   * and resolves the callback promise with the extracted data.
   */
  handleOAuthCallback(_request, response, queryParams) {
    try {
      if (queryParams.has("error")) {
        const error = {
          error: queryParams.get("error") || "",
          description: queryParams.get("error_description") || void 0,
          errorUri: queryParams.get("error_uri") || void 0,
          state: queryParams.get("state") || void 0
        };
        this.sendErrorResponse(response, error);
        this.settleCallback({
          kind: "error",
          error: new Error(
            `OAuth authorization failed: ${error.error} - ${error.description || "No description provided"}`
          )
        });
        return;
      }
      const code = queryParams.get("code");
      const state = queryParams.get("state");
      if (!code || !state) {
        this.sendValidationErrorResponse(response, "code and state parameters are required");
        this.settleCallback({ kind: "error", error: new Error("OAuth callback missing authorization code or state") });
        return;
      }
      this.sendSuccessResponse(response);
      this.settleCallback({ kind: "success", data: { code, state } });
    } catch (error) {
      this.sendInternalErrorResponse(response);
      this.settleCallback({
        kind: "error",
        error: error instanceof Error ? error : new Error("Unknown error processing OAuth callback")
      });
    }
  }
  /**
   * Sends a success response to the OAuth callback request.
   */
  sendSuccessResponse(response) {
    response.writeHead(200, { "Content-Type": "text/html" });
    response.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>OAuth Authorization Successful</title>
          <style>
            body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
            .success { color: #964cd3ff; }
            .container { max-width: 500px; margin: 0 auto; }
          </style>
        </head>
        <body>
          <div class="container">
            <h1 class="success">Authorization Successful</h1>
            <p>You have successfully authorized.</p>
            <p>You can now close this window and return to Kiro.</p>
          </div>
        </body>
      </html>
    `);
  }
  /**
   * Sends an error response for OAuth authorization errors.
   */
  sendErrorResponse(response, error) {
    response.writeHead(400, { "Content-Type": "text/html" });
    response.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>OAuth Authorization Failed</title>
          <style>
            body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
            .error { color: #dc3545; }
            .container { max-width: 500px; margin: 0 auto; }
            .error-details { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; }
          </style>
        </head>
        <body>
          <div class="container">
            <h1 class="error">Authorization Failed</h1>
            <p>The OAuth authorization was not successful.</p>
            <div class="error-details">
              <strong>Error:</strong> ${this.escapeHtml(error.error)}<br>
              ${error.description ? `<strong>Description:</strong> ${this.escapeHtml(error.description)}<br>` : ""}
            </div>
            <p>Please close this window and try again.</p>
          </div>
        </body>
      </html>
    `);
  }
  /**
   * Sends a validation error response for malformed requests.
   */
  sendValidationErrorResponse(response, message) {
    response.writeHead(400, { "Content-Type": "text/html" });
    response.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>Invalid OAuth Callback</title>
          <style>
            body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
            .error { color: #dc3545; }
            .container { max-width: 500px; margin: 0 auto; }
          </style>
        </head>
        <body>
          <div class="container">
            <h1 class="error">Invalid Request</h1>
            <p>${this.escapeHtml(message)}</p>
            <p>Please close this window and try again.</p>
          </div>
        </body>
      </html>
    `);
  }
  /**
   * Sends an internal server error response.
   */
  sendInternalErrorResponse(response) {
    response.writeHead(500, { "Content-Type": "text/html" });
    response.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>Server Error</title>
          <style>
            body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
            .error { color: #dc3545; }
            .container { max-width: 500px; margin: 0 auto; }
          </style>
        </head>
        <body>
          <div class="container">
            <h1 class="error">Server Error</h1>
            <p>An internal server error occurred while processing the OAuth callback.</p>
            <p>Please close this window and try again.</p>
          </div>
        </body>
      </html>
    `);
  }
  /**
   * Escapes HTML characters to prevent XSS attacks.
   */
  escapeHtml(text) {
    const map = {
      "&": "&amp;",
      "<": "&lt;",
      ">": "&gt;",
      '"': "&quot;",
      "'": "&#039;"
    };
    return text.replace(/[&<>"']/g, (m) => map[m]);
  }
  resetCallbacks() {
    this.callbackPromise = null;
    this.callbackResolve = null;
    this.callbackReject = null;
  }
  /**
   * Cleans up server resources and resets internal state.
   *
   * An unconsumed buffered result is deliberately preserved: a callback that already arrived
   * is a completed authorization, and stopping the listener must not discard it before the
   * waiter has had a chance to consume it.
   */
  cleanup() {
    this.server = void 0;
    this.callbackUrl = void 0;
    if (this.callbackReject) {
      this.callbackReject(new Error("OAuth server stopped before callback was received"));
    }
    this.resetCallbacks();
  }
}
exports.CredentialStorageManager = CredentialStorageManager;
exports.DEFAULT_TOKEN_EXPIRY_BUFFER_MS = DEFAULT_TOKEN_EXPIRY_BUFFER_MS;
exports.MCPAuthProvider = MCPAuthProvider;
exports.MCPJsonConfigSchema = MCPJsonConfigSchema;
exports.MCPOptionsSchema = MCPOptionsSchema;
exports.OAuthConfigSchema = OAuthConfigSchema;
exports.OAuthRedirectServer = OAuthRedirectServer;
exports.addMCPServerConfig = addMCPServerConfig;
exports.addMCPToolToAutoApproveConfig = addMCPToolToAutoApproveConfig;
exports.computeTokenExpiryBuffer = computeTokenExpiryBuffer;
exports.deepValidateMCPServerOptions = deepValidateMCPServerOptions;
exports.disableMCPTools = disableMCPTools;
exports.enableMCPTools = enableMCPTools;
exports.expandEnvironmentVariables = expandEnvironmentVariables;
exports.findConfigFileForServer = findConfigFileForServer;
exports.isHttpsOrLocalhost = isHttpsOrLocalhost;
exports.loadMcpConfig = loadMcpConfig;
exports.mcpServerSources = mcpServerSources;
exports.resetApprovedEnvVars = resetApprovedEnvVars;
exports.setMCPServerDisabled = setMCPServerDisabled;
