UNPKG

add-mcp

Version:

Add MCP servers to your favorite coding agents with a single command.

2,395 lines • 81.7 kB
#!/usr/bin/env node
import {
  DEFAULT_FIND_REGISTRY_LABEL,
  DEFAULT_FIND_REGISTRY_URL,
  agents,
  buildAgentSelectionChoices,
  buildServerConfig,
  claudeCopilotGithubShadowError,
  describeOptionalField,
  detectGlobalAgents,
  detectProjectAgents,
  extractServerIdentity,
  findMatchingServers,
  getAgentTypes,
  getCommonInstallScopes,
  getConfigKey,
  getConfigPath,
  getFindRegistries,
  getLastSelectedAgents,
  getProjectCapableAgents,
  installServer,
  installServerForAgent,
  isBearerTokenEnvName,
  isRemoteSource,
  isTransportSupported,
  listInstalledServers,
  listOpenCodeServers,
  normalizeStoredCommand,
  parseSource,
  relocateOpenCodeServer,
  removeOpenCodeServer,
  removeServerFromConfig,
  rewriteCopilotCliConfig,
  saveFindRegistries,
  selectAgentsInteractive,
  supportsProjectConfig,
  updateGitignoreWithPaths
} from "./chunk-JZK2SME6.js";

// src/index.ts
import { program } from "commander";
import * as p2 from "@clack/prompts";
import chalk from "chalk";
import { homedir } from "os";

// src/types.ts
var agentAliases = {
  "cline-vscode": "cline",
  codeium: "windsurf",
  cascade: "windsurf",
  droid: "factory-droid",
  factory: "factory-droid",
  gemini: "gemini-cli",
  "github-copilot": "vscode",
  grok: "grok-build",
  "hermes-agent": "hermes",
  kilo: "kilo-code",
  kilocode: "kilo-code",
  kimi: "kimi-code",
  kiro: "kiro-cli",
  mastra: "mastracode",
  "pi-agent": "pi"
};

// src/find.ts
import * as p from "@clack/prompts";

// src/template.ts
var TEMPLATE_PATTERN = /\$\{([^}]+)\}/g;
function findTemplateVars(value) {
  const vars = [];
  let match;
  while ((match = TEMPLATE_PATTERN.exec(value)) !== null) {
    vars.push(match[1]);
  }
  TEMPLATE_PATTERN.lastIndex = 0;
  return vars;
}
function substituteTemplatePlaceholders(value, replacement) {
  return value.replace(/\$\{([^}]+)\}/g, () => replacement);
}
async function resolveTemplates(value, ask) {
  const vars = findTemplateVars(value);
  if (vars.length === 0) return { resolved: value, cancelled: false };
  let result = value;
  for (const varName of vars) {
    const answer = await ask(varName);
    if (typeof answer === "symbol") return { resolved: value, cancelled: true };
    const entered = typeof answer === "string" ? answer : "";
    result = result.replace(`\${${varName}}`, entered);
  }
  return { resolved: result, cancelled: false };
}
async function resolveRecordTemplates(record, ask) {
  const resolved = {};
  for (const [key, value] of Object.entries(record)) {
    const result = await resolveTemplates(value, ask);
    if (result.cancelled) return { resolved: record, cancelled: true };
    resolved[key] = result.resolved;
  }
  return { resolved, cancelled: false };
}
async function resolveArrayTemplates(values, ask) {
  const resolved = [];
  for (const value of values) {
    const result = await resolveTemplates(value, ask);
    if (result.cancelled) return { resolved: values, cancelled: true };
    resolved.push(result.resolved);
  }
  return { resolved, cancelled: false };
}
function hasTemplateVars(values) {
  const strings = Array.isArray(values) ? values : Object.values(values);
  return strings.some((v) => findTemplateVars(v).length > 0);
}

// src/package-arguments.ts
var ARG_PLACEHOLDER = "<your-variable-value-here>";
function normalizeNamedFlag(name) {
  const t = name.trim();
  if (t.startsWith("-")) return t;
  return `--${t}`;
}
function definitionsFromPackage(pkg) {
  return pkg.packageArguments ?? [];
}
function isNamedArg(d) {
  if (d.type === "positional") return false;
  if (d.type === "named") return true;
  return Boolean(d.name?.trim());
}
function labelForArg(d, index) {
  if (isNamedArg(d)) {
    return `Flag ${normalizeNamedFlag(d.name)}`;
  }
  return d.valueHint?.trim() || d.description?.trim() || `Positional argument #${index + 1}`;
}
function hasTemplates(s) {
  return Boolean(s && findTemplateVars(s).length > 0);
}
function buildPackageArgumentsArgvNonInteractive(definitions, placeholder = ARG_PLACEHOLDER) {
  const out = [];
  for (let i = 0; i < definitions.length; i++) {
    const d = definitions[i];
    if (isNamedArg(d)) {
      const flag = normalizeNamedFlag(d.name);
      let val = d.value?.trim() ?? "";
      if (hasTemplates(d.value)) {
        val = substituteTemplatePlaceholders(d.value, placeholder);
      } else if (!val && d.default?.trim()) {
        val = d.default.trim();
      }
      if (!val) {
        if (d.isRequired === true) {
          out.push(flag, placeholder);
        }
        continue;
      }
      out.push(flag, val);
    } else {
      let val = d.value?.trim() ?? "";
      if (hasTemplates(d.value)) {
        val = substituteTemplatePlaceholders(d.value, placeholder);
        pushPositionalSegments(out, d, val);
        continue;
      }
      if (val) {
        pushPositionalSegments(out, d, val);
        continue;
      }
      if (d.default?.trim()) {
        pushPositionalSegments(out, d, d.default.trim());
        continue;
      }
      pushPositionalSegments(out, d, placeholder);
    }
  }
  return out;
}
function pushPositionalSegments(out, d, raw) {
  if (d.isRepeated === true) {
    const parts = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
    if (parts.length === 0) {
      out.push(raw);
      return;
    }
    for (const p3 of parts) {
      out.push(p3);
    }
    return;
  }
  out.push(raw);
}
async function buildPackageArgumentsArgvInteractive(definitions, prompt) {
  const out = [];
  for (let i = 0; i < definitions.length; i++) {
    const d = definitions[i];
    const label = labelForArg(d, i);
    if (isNamedArg(d)) {
      const flag = normalizeNamedFlag(d.name);
      let val2 = d.value ?? "";
      if (hasTemplates(d.value)) {
        const res = await resolveTemplates(d.value, async (varName) => {
          const answer = await prompt({
            label: `${label} (${varName})`,
            isRequired: true,
            placeholder: `<${varName}>`
          });
          return answer;
        });
        if (res.cancelled) return { cancelled: true };
        val2 = res.resolved;
        out.push(flag, val2);
        continue;
      }
      if (val2.trim()) {
        out.push(flag, val2.trim());
        continue;
      }
      const defVal2 = d.default?.trim() ?? "";
      const raw2 = await prompt({
        label,
        isRequired: d.isRequired === true,
        placeholder: defVal2 || ARG_PLACEHOLDER
      });
      if (typeof raw2 === "symbol") return { cancelled: true };
      const trimmed2 = typeof raw2 === "string" ? raw2.trim() : "";
      if (!trimmed2) {
        if (d.isRequired === true) {
          out.push(flag, defVal2 || ARG_PLACEHOLDER);
        } else if (defVal2) {
          out.push(flag, defVal2);
        }
        continue;
      }
      out.push(flag, trimmed2);
      continue;
    }
    let val = d.value ?? "";
    if (hasTemplates(d.value)) {
      const res = await resolveTemplates(d.value, async (varName) => {
        const answer = await prompt({
          label: `${label} (${varName})`,
          isRequired: true,
          placeholder: `<${varName}>`
        });
        return answer;
      });
      if (res.cancelled) return { cancelled: true };
      val = res.resolved;
      pushPositionalSegments(out, d, val);
      continue;
    }
    if (val.trim()) {
      pushPositionalSegments(out, d, val.trim());
      continue;
    }
    const defVal = d.default?.trim() ?? "";
    if (defVal) {
      pushPositionalSegments(out, d, defVal);
      continue;
    }
    if (d.isRequired !== true) {
      const raw2 = await prompt({
        label,
        isRequired: false,
        placeholder: ARG_PLACEHOLDER
      });
      if (typeof raw2 === "symbol") return { cancelled: true };
      const trimmed2 = typeof raw2 === "string" ? raw2.trim() : "";
      if (!trimmed2) {
        pushPositionalSegments(out, d, ARG_PLACEHOLDER);
      } else {
        pushPositionalSegments(out, d, trimmed2);
      }
      continue;
    }
    const raw = await prompt({
      label,
      isRequired: true,
      placeholder: ARG_PLACEHOLDER
    });
    if (typeof raw === "symbol") return { cancelled: true };
    const trimmed = typeof raw === "string" ? raw.trim() : "";
    pushPositionalSegments(
      out,
      d,
      trimmed.length > 0 ? trimmed : ARG_PLACEHOLDER
    );
  }
  return { argv: out, cancelled: false };
}

// src/find.ts
function buildPlaceholderValue(kind) {
  return kind === "header" ? "<your-header-value-here>" : "<your-variable-value-here>";
}
function normalize(value) {
  return value.trim().toLowerCase();
}
function tokenize(value) {
  return normalize(value).split(/[^a-z0-9]+/i).filter((token) => token.length > 0);
}
var TRUSTED_NAMESPACE_PREFIXES = [
  "com.supabase/",
  "io.github.github/",
  "com.postman/",
  "com.stripe/",
  "com.vercel/",
  "io.github.vercel/",
  "com.notion/",
  "app.linear/",
  "com.atlassian/",
  "com.cloudflare.",
  "io.github.getsentry/",
  "io.github.mongodb-js/",
  "io.github.railwayapp/"
];
var NOISY_NAMESPACE_PREFIXES = ["ai.smithery/"];
var SMITHERY_PREFIX = "ai.smithery/";
function resolveTemplateUrl(templateUrl, values) {
  return templateUrl.replace(/\{([^}]+)\}/g, (fullMatch, rawName) => {
    const key = String(rawName);
    const replacement = values[key];
    return replacement && replacement.length > 0 ? replacement : fullMatch;
  });
}
function rankRegistryEntry(query, entry) {
  const normalizedQuery = normalize(query);
  const queryTokens = tokenize(normalizedQuery);
  const name = normalize(entry.name);
  const title = normalize(entry.title ?? "");
  const description = normalize(entry.description);
  const haystack = `${name} ${title} ${description}`;
  const haystackTokens = new Set(tokenize(haystack));
  let score = 0;
  if (name === normalizedQuery) score += 800;
  if (name.includes(normalizedQuery)) score += 350;
  if (title.includes(normalizedQuery)) score += 250;
  if (description.includes(normalizedQuery)) score += 120;
  for (const token of queryTokens) {
    if (token.length === 0) continue;
    if (haystackTokens.has(token)) {
      score += 60;
    } else if (token.length >= 3 && haystack.includes(token)) {
      score += 20;
    }
  }
  if (TRUSTED_NAMESPACE_PREFIXES.some((prefix) => name.startsWith(prefix))) {
    score += 500;
  }
  if (NOISY_NAMESPACE_PREFIXES.some((prefix) => name.startsWith(prefix))) {
    score -= 500;
  }
  return score;
}
function rankRegistryEntries(query, entries) {
  return [...entries].sort((a, b) => {
    const scoreDiff = rankRegistryEntry(query, b) - rankRegistryEntry(query, a);
    if (scoreDiff !== 0) return scoreDiff;
    return a.name.localeCompare(b.name);
  });
}
function isSmitheryEntry(entry) {
  return normalize(entry.name).startsWith(SMITHERY_PREFIX);
}
function filterSmitheryWhenAlternativesExist(entries) {
  if (entries.length === 0) return entries;
  const hasNonSmithery = entries.some((entry) => !isSmitheryEntry(entry));
  if (!hasNonSmithery) return entries;
  return entries.filter((entry) => !isSmitheryEntry(entry));
}
var OFFICIAL_REGISTRY_BASE_URL = "https://registry.modelcontextprotocol.io";
function resolveOfficialRegistryServersUrl() {
  return `${OFFICIAL_REGISTRY_BASE_URL}/v0.1/servers`;
}
function getDefaultFindRegistries() {
  return [
    {
      url: DEFAULT_FIND_REGISTRY_URL,
      label: DEFAULT_FIND_REGISTRY_LABEL
    },
    {
      url: resolveOfficialRegistryServersUrl(),
      label: "Official Anthropic registry"
    }
  ];
}
function formatRegistryFailure(failure) {
  const { registry, detail } = failure;
  const defaults = getDefaultFindRegistries();
  const isKnown = defaults.some((d) => d.url === registry.url);
  if (isKnown && registry.label) {
    return `"${registry.label}" (${registry.url}) is unavailable \u2014 ${detail}`;
  }
  return `Registry ${registry.url} is unavailable \u2014 ${detail}`;
}
function toEntry(item) {
  const server = item?.server;
  if (!server?.name || !server?.description || !server?.version) {
    return null;
  }
  const npmPackage = (server.packages ?? []).find(
    (pkg) => pkg.registryType === "npm"
  );
  const hasRemotes = Array.isArray(server.remotes) && server.remotes.length > 0;
  if (!npmPackage && !hasRemotes) {
    return null;
  }
  return {
    name: server.name,
    title: server.title,
    description: server.description,
    version: server.version,
    repositoryUrl: server.repository?.url,
    remotes: server.remotes,
    package: npmPackage
  };
}
function buildRegistryRequestUrl(registryUrl, query) {
  const params = new URLSearchParams({
    version: "latest",
    limit: "100"
  });
  if (query.length > 0) {
    params.set("search", query);
    params.set("source", "cli");
  }
  const url = new URL(registryUrl);
  for (const [key, value] of params.entries()) {
    url.searchParams.set(key, value);
  }
  return url.toString();
}
async function searchRegistry(query, registries) {
  const trimmedQuery = normalize(query);
  const deduped = /* @__PURE__ */ new Map();
  const failedRegistries = [];
  for (const registry of registries) {
    try {
      const requestUrl = buildRegistryRequestUrl(registry.url, trimmedQuery);
      const response = await fetch(requestUrl);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const payload = await response.json();
      for (const item of payload.servers ?? []) {
        const entry = toEntry(item);
        if (!entry) continue;
        const key = `${entry.name}@${entry.version}`;
        if (!deduped.has(key)) {
          deduped.set(key, entry);
        }
      }
    } catch (error) {
      const detail = error instanceof Error ? error.message : "unknown error";
      failedRegistries.push({ registry, detail });
    }
  }
  return {
    entries: [...deduped.values()],
    failedRegistries
  };
}
function resolveServerName(entry) {
  const title = entry.title?.trim();
  if (title && title.length > 0) {
    return title.toLowerCase();
  }
  const cleaned = entry.name.toLowerCase().replace(/mcp/g, "").replace(/com/g, "");
  const tokens = cleaned.split(/[^a-z0-9]+/i).filter((token) => token.length > 0);
  if (tokens.length > 0) {
    return tokens.join("-");
  }
  const fallback = entry.name.toLowerCase().split(/[^a-z0-9]+/i).filter((token) => token.length > 0).join("-");
  return fallback || "server";
}
function remoteToTransport(type) {
  return type === "sse" ? "sse" : "http";
}
function pickRemote(entry, preferredTransport) {
  const remotes = entry.remotes ?? [];
  if (remotes.length === 0) return null;
  const preferred = preferredTransport === "sse" ? remotes.find((remote) => remote.type === "sse") : remotes.find((remote) => remote.type === "streamable-http");
  return preferred ?? remotes[0] ?? null;
}
function formatPackageTarget(pkg) {
  return pkg.identifier;
}
function transportLabel(entry) {
  const parts = [];
  if (entry.package) parts.push("stdio");
  if (entry.remotes && entry.remotes.length > 0) parts.push("remote");
  return parts.length > 0 ? parts.join(", ") : "unknown";
}
function formatRemoteTarget(url) {
  return url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/\/$/, "");
}
function formatFindResultTarget(entry) {
  const remote = pickRemote(entry);
  if (remote) {
    return formatRemoteTarget(remote.url);
  }
  if (entry.package) {
    return formatPackageTarget(entry.package);
  }
  return entry.name;
}
function formatFindResultRow(entry) {
  const display = entry.title ?? entry.name;
  return `${display} (${formatFindResultTarget(entry)}) [${transportLabel(entry)}]`;
}
async function promptValue(field) {
  return p.text({
    message: `${field.label} ${field.isRequired ? "(required)" : "(optional)"}`,
    placeholder: field.placeholder
  });
}
async function collectPromptValues(fields, ask) {
  const values = {};
  for (const field of fields) {
    const raw = await ask(field);
    if (p.isCancel(raw)) {
      return { values, cancelled: true };
    }
    const value = raw != null && typeof raw === "string" ? raw.trim() : "";
    if (value.length > 0) {
      values[field.key] = value;
    } else if (field.isRequired) {
      values[field.key] = field.placeholder;
    }
  }
  return { values, cancelled: false };
}
function variableFields(variables) {
  if (!variables) return [];
  return Object.entries(variables).map(([key, definition]) => ({
    key,
    label: `Variable ${key}`,
    isRequired: definition.isRequired === true,
    placeholder: buildPlaceholderValue("variable")
  }));
}
function headerFields(headers) {
  if (!headers) return [];
  return headers.map((header) => ({
    key: header.name,
    label: `Header ${header.name}`,
    isRequired: header.isRequired === true,
    placeholder: buildPlaceholderValue("header")
  }));
}
function packageVariableFields(variables) {
  if (!variables) return [];
  return variables.filter((variable) => variable.name && variable.name.trim().length > 0).map((variable) => ({
    key: variable.name,
    label: `Environment variable ${variable.name}`,
    isRequired: variable.isRequired === true,
    placeholder: buildPlaceholderValue("variable")
  }));
}
function resolveNonInteractiveRemote(remote) {
  const variableValues = {};
  for (const [key, def] of Object.entries(remote.variables ?? {})) {
    if (def.isRequired) {
      variableValues[key] = buildPlaceholderValue("variable");
    }
  }
  const headerValues = {};
  for (const header of remote.headers ?? []) {
    if (header.isRequired) {
      headerValues[header.name] = buildPlaceholderValue("header");
    }
  }
  return {
    url: resolveTemplateUrl(remote.url, variableValues),
    headers: Object.keys(headerValues).length > 0 ? headerValues : void 0
  };
}
async function resolveInteractiveRemote(remote) {
  const variableResult = await collectPromptValues(
    variableFields(remote.variables),
    promptValue
  );
  if (variableResult.cancelled) return null;
  const headerResult = await collectPromptValues(
    headerFields(remote.headers),
    promptValue
  );
  if (headerResult.cancelled) return null;
  return {
    url: resolveTemplateUrl(remote.url, variableResult.values),
    headers: Object.keys(headerResult.values).length > 0 ? headerResult.values : void 0
  };
}
function resolveNonInteractivePackage(pkg) {
  const env = {};
  for (const field of packageVariableFields(pkg.environmentVariables)) {
    if (field.isRequired) {
      env[field.key] = field.placeholder;
    }
  }
  const headers = {};
  for (const field of headerFields(pkg.headers)) {
    if (field.isRequired) {
      headers[field.key] = field.placeholder;
    }
  }
  const argv = buildPackageArgumentsArgvNonInteractive(
    definitionsFromPackage(pkg),
    buildPlaceholderValue("variable")
  );
  return {
    env: Object.keys(env).length > 0 ? env : void 0,
    headers: Object.keys(headers).length > 0 ? headers : void 0,
    args: argv.length > 0 ? argv : void 0
  };
}
async function resolveInteractivePackage(pkg) {
  const promptPackageArg = async (info) => p.text({
    message: `${info.label} ${info.isRequired ? "(required)" : "(optional)"}`,
    placeholder: info.placeholder
  });
  const envResult = await collectPromptValues(
    packageVariableFields(pkg.environmentVariables),
    promptValue
  );
  if (envResult.cancelled) return null;
  const headerResult = await collectPromptValues(
    headerFields(pkg.headers),
    promptValue
  );
  if (headerResult.cancelled) return null;
  const argvResult = await buildPackageArgumentsArgvInteractive(
    definitionsFromPackage(pkg),
    promptPackageArg
  );
  if (argvResult.cancelled) return null;
  return {
    env: Object.keys(envResult.values).length > 0 ? envResult.values : void 0,
    headers: Object.keys(headerResult.values).length > 0 ? headerResult.values : void 0,
    args: argvResult.argv.length > 0 ? argvResult.argv : void 0
  };
}
async function buildInstallPlanForEntry(entry, options) {
  const remote = pickRemote(entry, options.preferredTransport);
  const pkg = entry.package ?? null;
  const hasRemote = remote !== null;
  const hasPackage = pkg !== null;
  if (!hasRemote && !hasPackage) {
    return null;
  }
  let mode;
  if (hasRemote && hasPackage) {
    if (options.yes) {
      mode = "remote";
    } else {
      const selected = await p.select({
        message: `Install mode for ${entry.name}`,
        initialValue: "remote",
        options: [
          { value: "remote", label: "Remote", hint: "Recommended default" },
          {
            value: "package",
            label: "Stdio package",
            hint: "Local stdio package"
          }
        ]
      });
      if (p.isCancel(selected)) return null;
      mode = selected;
    }
  } else {
    mode = hasRemote ? "remote" : "package";
  }
  if (mode === "package" && pkg) {
    const resolved2 = options.yes ? resolveNonInteractivePackage(pkg) : await resolveInteractivePackage(pkg);
    if (!resolved2) return null;
    return {
      target: formatPackageTarget(pkg),
      serverName: resolveServerName(entry),
      env: resolved2.env,
      headers: resolved2.headers,
      args: resolved2.args
    };
  }
  if (!remote) return null;
  const resolved = options.yes ? resolveNonInteractiveRemote(remote) : await resolveInteractiveRemote(remote);
  if (!resolved) return null;
  return {
    target: resolved.url,
    serverName: resolveServerName(entry),
    transport: remoteToTransport(remote.type),
    headers: resolved.headers
  };
}
async function offerFallbackSearch(query, alreadyQueried) {
  const defaults = getDefaultFindRegistries();
  const queriedUrls = new Set(alreadyQueried.map((r) => r.url));
  const candidates = defaults.filter((r) => !queriedUrls.has(r.url));
  if (candidates.length === 0) {
    return null;
  }
  let selectedRegistries;
  if (candidates.length === 1) {
    const candidate = candidates[0];
    const confirmed = await p.confirm({
      message: `Search "${candidate.label ?? candidate.url}" (${candidate.url}) instead?`
    });
    if (p.isCancel(confirmed) || !confirmed) {
      return null;
    }
    selectedRegistries = [candidate];
  } else {
    const selected = await p.multiselect({
      message: "Search other registries instead?",
      options: candidates.map((r) => ({
        value: r.url,
        label: r.label ?? r.url
      })),
      required: true
    });
    if (p.isCancel(selected)) {
      return null;
    }
    selectedRegistries = candidates.filter(
      (r) => selected.includes(r.url)
    );
  }
  const fallbackResult = await searchRegistry(query, selectedRegistries);
  if (fallbackResult.failedRegistries.length > 0) {
    for (const failure of fallbackResult.failedRegistries) {
      p.log.error(formatRegistryFailure(failure));
    }
  }
  if (fallbackResult.entries.length === 0) {
    if (fallbackResult.failedRegistries.length === 0) {
      p.log.warn(`No MCP servers found for "${query}"`);
    }
    return null;
  }
  return fallbackResult.entries;
}
var FIND_PAGE_SIZE = 15;
var LOAD_MORE_SENTINEL = "__load_more__";
async function selectEntryWithPagination(visibleEntries, message) {
  let page = 0;
  while (true) {
    const start = page * FIND_PAGE_SIZE;
    const slice = visibleEntries.slice(start, start + FIND_PAGE_SIZE);
    const hasMore = visibleEntries.length > start + FIND_PAGE_SIZE;
    const remaining = visibleEntries.length - (start + FIND_PAGE_SIZE);
    const options = slice.map((entryOption) => ({
      value: entryOption.name,
      label: formatFindResultRow(entryOption),
      hint: entryOption.description
    }));
    if (hasMore) {
      options.push({
        value: LOAD_MORE_SENTINEL,
        label: `\u25BC Show more results (${remaining} remaining)`
      });
    }
    const selected = await p.select({ message, options });
    if (p.isCancel(selected)) return null;
    if (selected === LOAD_MORE_SENTINEL) {
      page++;
      continue;
    }
    return visibleEntries.find((e) => e.name === selected) ?? null;
  }
}
async function runFind(query, options) {
  const registries = options.registries && options.registries.length > 0 ? options.registries : [
    {
      url: resolveOfficialRegistryServersUrl(),
      label: "Official Anthropic registry"
    }
  ];
  const isBrowseMode = query.trim().length === 0;
  const result = await searchRegistry(query, registries);
  let entries = result.entries;
  const { failedRegistries } = result;
  if (failedRegistries.length > 0 && entries.length > 0) {
    for (const failure of failedRegistries) {
      p.log.warn(formatRegistryFailure(failure));
    }
  }
  if (entries.length === 0 && failedRegistries.length > 0) {
    for (const failure of failedRegistries) {
      p.log.error(formatRegistryFailure(failure));
    }
    if (!options.yes && !isBrowseMode) {
      const fallbackEntries = await offerFallbackSearch(query, registries);
      if (fallbackEntries) {
        entries = fallbackEntries;
      }
    }
  }
  if (entries.length === 0) {
    if (failedRegistries.length === 0) {
      p.log.warn(
        isBrowseMode ? "No MCP servers found in the configured registries" : `No MCP servers found for "${query}"`
      );
    }
    return null;
  }
  const visibleEntries = isBrowseMode ? filterSmitheryWhenAlternativesExist(entries) : filterSmitheryWhenAlternativesExist(rankRegistryEntries(query, entries));
  const count = visibleEntries.length;
  const message = isBrowseMode ? "Browse MCP servers" : `Found ${count} server${count === 1 ? "" : "s"} for "${query}" \u2014 pick one`;
  const entry = options.yes ? visibleEntries[0] ?? null : await selectEntryWithPagination(visibleEntries, message);
  if (!entry) {
    return null;
  }
  return buildInstallPlanForEntry(entry, options);
}

// package.json
var package_default = {
  name: "add-mcp",
  version: "2.6.1",
  description: "Add MCP servers to your favorite coding agents with a single command.",
  author: "Andre Landgraf <andre@neon.tech>",
  license: "Apache-2.0",
  type: "module",
  main: "./dist/lib.js",
  types: "./dist/lib.d.ts",
  exports: {
    ".": {
      types: "./dist/lib.d.ts",
      import: "./dist/lib.js"
    },
    "./package.json": "./package.json"
  },
  bin: {
    "add-mcp": "dist/index.js"
  },
  files: [
    "dist",
    "README.md"
  ],
  scripts: {
    fmt: "prettier --write .",
    build: "tsup src/index.ts src/lib.ts --format esm --dts --clean",
    dev: "tsx src/index.ts",
    test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/formats-utils.test.ts && tsx tests/opencode-config.test.ts && tsx tests/lib.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
    "test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/formats-utils.test.ts && tsx tests/opencode-config.test.ts && tsx tests/lib.test.ts",
    "registry:sort": "tsx scripts/sort-registry.ts",
    "registry:sync": "node scripts/sync-integrations-sh.mjs",
    "registry:verify": "tsx scripts/verify-registry.ts",
    "test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
    typecheck: "tsc --noEmit",
    fallow: "fallow",
    prepublishOnly: "npm run build"
  },
  keywords: [
    "cli",
    "mcp",
    "model-context-protocol",
    "ai-agents",
    "antigravity",
    "cline",
    "claude-code",
    "claude-desktop",
    "codex",
    "cursor",
    "fx",
    "github-copilot-cli",
    "gemini-cli",
    "factory-droid",
    "goose",
    "grok",
    "grok-build",
    "hermes",
    "junie",
    "kilo-code",
    "kimi-code",
    "kiro",
    "kiro-cli",
    "mastracode",
    "mcporter",
    "opencode",
    "pi",
    "vscode",
    "windsurf",
    "zed"
  ],
  repository: {
    type: "git",
    url: "git+https://github.com/neon-solutions/add-mcp.git"
  },
  homepage: "https://github.com/neon-solutions/add-mcp#readme",
  bugs: {
    url: "https://github.com/neon-solutions/add-mcp/issues"
  },
  dependencies: {
    "@clack/prompts": "^0.9.1",
    "@iarna/toml": "^2.2.5",
    chalk: "^5.4.1",
    commander: "^13.1.0",
    "jsonc-parser": "^3.3.1",
    yaml: "^2.9.1"
  },
  devDependencies: {
    "@types/js-yaml": "^4.0.9",
    "@types/node": "^22.10.0",
    fallow: "^2.61.0",
    "js-yaml": "^4.1.0",
    prettier: "^3.8.1",
    tsup: "^8.3.5",
    tsx: "^4.19.2",
    typescript: "^5.7.2",
    zod: "^4.3.6"
  },
  engines: {
    node: ">=18"
  }
};

// src/index.ts
var version = package_default.version;
var RESET = "\x1B[0m";
var DIM = "\x1B[38;5;102m";
var TEXT = "\x1B[38;5;145m";
var LOGO_LINES = [
  " \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557       \u2588\u2588\u2588\u2557   \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
  "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557      \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
  "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551  \u2588\u2588\u2551\u2588\u2588\u2551  \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551     \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
  "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551  \u2588\u2588\u2551\u2588\u2588\u2551  \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551     \u2588\u2588\u2554\u2550\u2550\u2550\u255D ",
  "\u2588\u2588\u2551  \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D      \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551     ",
  "\u255A\u2550\u255D  \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D       \u255A\u2550\u255D     \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D     "
];
var GRAYS = [
  "\x1B[38;5;250m",
  "\x1B[38;5;248m",
  "\x1B[38;5;245m",
  "\x1B[38;5;243m",
  "\x1B[38;5;240m",
  "\x1B[38;5;238m"
];
function showLogo() {
  console.log();
  LOGO_LINES.forEach((line, i) => {
    console.log(`${GRAYS[i]}${line}${RESET}`);
  });
}
function shortenPath(fullPath) {
  const home = homedir();
  if (fullPath.startsWith(home)) {
    return fullPath.replace(home, "~");
  }
  return fullPath;
}
function resolveAgentType(input) {
  const lower = input.toLowerCase();
  if (lower in agents) {
    return lower;
  }
  if (lower in agentAliases) {
    return agentAliases[lower];
  }
  return null;
}
async function ensureFindRegistriesConfigured() {
  const configured = await getFindRegistries();
  if (configured.length > 0) {
    return configured;
  }
  const defaultRegistry = getDefaultFindRegistries()[0];
  if (!defaultRegistry) {
    throw new Error("No default find registry is configured");
  }
  const selectedRegistries = [defaultRegistry];
  await saveFindRegistries(selectedRegistries);
  p2.log.info(
    `Using ${defaultRegistry.label ?? defaultRegistry.url}. Saved to ${shortenPath(getConfigPath())} - you can remove or update it any time.`
  );
  return selectedRegistries;
}
function extractOptions(raw) {
  if (typeof raw.optsWithGlobals === "function") {
    return raw.optsWithGlobals();
  }
  if (typeof raw.opts === "function") {
    return raw.opts();
  }
  return raw;
}
function extractSubcommandOptionsFromArgv() {
  const argv = process.argv.slice(2);
  const result = {};
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i];
    if (!arg) continue;
    if (arg === "-y" || arg === "--yes") {
      result.yes = true;
      continue;
    }
    if (arg === "-g" || arg === "--global") {
      result.global = true;
      continue;
    }
    if (arg === "--all") {
      result.all = true;
      continue;
    }
    if (arg === "--gitignore") {
      result.gitignore = true;
      continue;
    }
    if (arg === "--auto-approve") {
      result.autoApprove = true;
      continue;
    }
    if (arg === "--approve-tool") {
      const tools = result.approveTool ? [...result.approveTool] : [];
      let j = i + 1;
      while (j < argv.length) {
        const value = argv[j];
        if (!value || value.startsWith("-")) break;
        tools.push(value);
        j += 1;
      }
      if (tools.length > 0) {
        result.approveTool = tools;
      }
      i = j - 1;
      continue;
    }
    if ((arg === "-h" || arg === "--header") && argv[i + 1]) {
      const headers = result.header ? [...result.header] : [];
      headers.push(argv[i + 1]);
      result.header = headers;
      i += 1;
      continue;
    }
    if (arg === "--env" && argv[i + 1]) {
      const env = result.env ? [...result.env] : [];
      env.push(argv[i + 1]);
      result.env = env;
      i += 1;
      continue;
    }
    if (arg === "--args" && argv[i + 1]) {
      const args = result.args ? [...result.args] : [];
      args.push(argv[i + 1]);
      result.args = args;
      i += 1;
      continue;
    }
    if ((arg === "-n" || arg === "--name") && argv[i + 1]) {
      result.name = argv[i + 1];
      i += 1;
      continue;
    }
    if (arg === "-a" || arg === "--agent") {
      const agents2 = result.agent ? [...result.agent] : [];
      let j = i + 1;
      while (j < argv.length) {
        const value = argv[j];
        if (!value || value.startsWith("-")) break;
        agents2.push(value);
        j += 1;
      }
      if (agents2.length > 0) {
        result.agent = agents2;
      }
      i = j - 1;
    }
  }
  return result;
}
function inferFindPreferredTransport(options) {
  if (!options.agent || options.agent.length === 0) {
    return void 0;
  }
  const resolvedAgents = options.agent.map((value) => resolveAgentType(value)).filter((value) => value !== null);
  if (resolvedAgents.length === 0) {
    return void 0;
  }
  const supportsHttp = resolvedAgents.some(
    (agent) => isTransportSupported(agent, "http")
  );
  if (supportsHttp) {
    return void 0;
  }
  const supportsSse = resolvedAgents.every(
    (agent) => isTransportSupported(agent, "sse")
  );
  return supportsSse ? "sse" : void 0;
}
function collect(value, previous) {
  return previous.concat([value]);
}
function looksLikeEatenShellVar(invalidEntries, separator) {
  for (const entry of invalidEntries) {
    const separatorIndex = entry.indexOf(separator);
    if (separatorIndex <= 0) continue;
    const key = entry.slice(0, separatorIndex).trim();
    const value = entry.slice(separatorIndex + 1).trim();
    if (key && !value) return true;
  }
  return false;
}
function parseHeaders(values) {
  const headers = {};
  const invalid = [];
  for (const entry of values) {
    const separatorIndex = entry.indexOf(":");
    if (separatorIndex <= 0) {
      invalid.push(entry);
      continue;
    }
    const key = entry.slice(0, separatorIndex).trim();
    const value = entry.slice(separatorIndex + 1).trim();
    if (!key || !value) {
      invalid.push(entry);
      continue;
    }
    headers[key] = value;
  }
  return { headers, invalid };
}
function parseEnv(values) {
  const env = {};
  const invalid = [];
  for (const entry of values) {
    const separatorIndex = entry.indexOf("=");
    if (separatorIndex <= 0) {
      invalid.push(entry);
      continue;
    }
    const key = entry.slice(0, separatorIndex).trim();
    const value = entry.slice(separatorIndex + 1).trim();
    if (!key || !value) {
      invalid.push(entry);
      continue;
    }
    env[key] = value;
  }
  return { env, invalid };
}
function omitEmptyStringValues(record) {
  return Object.fromEntries(
    Object.entries(record).filter(
      ([, v]) => typeof v === "string" && v.trim().length > 0
    )
  );
}
program.name("add-mcp").description(
  "Install MCP servers for coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex, and more \u2014 run list-agents for the full list)"
).version(version).helpOption("--help", "display help for command").argument("[target]", "MCP server URL (remote) or package name (local stdio)").option(
  "-g, --global",
  "Install globally (user-level) instead of project-level"
).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
  "-n, --name <name>",
  "Server name (auto-inferred from target if not provided)"
).option(
  "-t, --transport <type>",
  "Transport type for remote servers (http, sse)"
).option("--type <type>", "Alias for --transport").option(
  "-h, --header <header>",
  "HTTP header for remote servers (repeatable, 'Key: Value'). Placeholders ${VAR} prompt interactively when not using --yes. Use single quotes so your shell does not expand the ${VAR}.",
  collect,
  []
).option(
  "--env <env>",
  "Environment variable for local stdio servers (repeatable, 'KEY=VALUE'). Placeholders ${VAR} prompt interactively when not using --yes. Use single quotes so your shell does not expand the ${VAR}.",
  collect,
  []
).option(
  "--args <arg>",
  "Argument for local stdio servers (repeatable). Placeholders ${VAR} prompt interactively when not using --yes. Use single quotes so your shell does not expand the ${VAR}.",
  collect,
  []
).option(
  "--timeout <ms>",
  "Request timeout in milliseconds for remote servers. Only applied to agents that support it (e.g. Claude Code, Gemini CLI, Grok Build); dropped with a warning elsewhere."
).option(
  "--scopes <scopes>",
  "OAuth scopes to request for remote servers (comma-separated). Only applied to agents that support it (e.g. Cursor, Gemini CLI); dropped with a warning elsewhere."
).option("--oauth-scopes <scopes>", "Alias for --scopes").option(
  "--bearer-token-env <name>",
  "Environment variable name whose value fx sends as a bearer token for remote servers. Written as bearer_token_env for fx; dropped with a warning elsewhere."
).option(
  "--auto-approve",
  "Auto-approve MCP tool calls for agents that support it (Codex, Claude Code). Dropped with a warning for other agents."
).option(
  "--approve-tool <tool>",
  "Tool name to auto-approve when --auto-approve is set (repeatable; defaults to all tools)",
  collect,
  []
).option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(async (target, options) => {
  await main(target, options);
});
program.command("list-agents").description("List all supported coding agents").action(() => {
  listAgents();
});
async function runFindCommand(keyword, rawOptions) {
  const options = {
    ...extractOptions(rawOptions),
    ...extractSubcommandOptionsFromArgv()
  };
  const query = (keyword ?? "").trim();
  const registries = await ensureFindRegistriesConfigured();
  if (!registries) {
    p2.cancel("Find cancelled");
    process.exit(0);
  }
  const installPlan = await runFind(query, {
    yes: options.yes,
    registries,
    preferredTransport: inferFindPreferredTransport(options)
  });
  if (!installPlan) {
    p2.cancel("Find cancelled");
    process.exit(0);
  }
  const mergedOptions = {
    ...options,
    name: options.name || installPlan.serverName,
    transport: installPlan.transport,
    header: installPlan.headers ? Object.entries(installPlan.headers).map(
      ([key, value]) => `${key}: ${value}`
    ) : options.header,
    env: installPlan.env ? Object.entries(installPlan.env).map(([key, value]) => `${key}=${value}`) : options.env,
    args: installPlan.args ?? options.args
  };
  await main(installPlan.target, mergedOptions);
}
program.command("find [keyword]").description(
  "Find MCP servers from integrations.sh registry data (omit keyword to browse)"
).option(
  "-g, --global",
  "Install globally (user-level) instead of project-level"
).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
  "-n, --name <name>",
  "Server name override (defaults to catalog entry name)"
).option("-y, --yes", "Skip confirmation prompts").option(
  "--auto-approve",
  "Auto-approve MCP tool calls for agents that support it (Codex, Claude Code)"
).option(
  "--approve-tool <tool>",
  "Tool name to auto-approve when --auto-approve is set (repeatable; defaults to all tools)",
  collect,
  []
).option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(
  async (keyword, options) => {
    await runFindCommand(keyword, options);
  }
);
program.command("search [keyword]").description("Alias for find").option(
  "-g, --global",
  "Install globally (user-level) instead of project-level"
).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
  "-n, --name <name>",
  "Server name override (defaults to catalog entry name)"
).option("-y, --yes", "Skip confirmation prompts").option(
  "--auto-approve",
  "Auto-approve MCP tool calls for agents that support it (Codex, Claude Code)"
).option(
  "--approve-tool <tool>",
  "Tool name to auto-approve when --auto-approve is set (repeatable; defaults to all tools)",
  collect,
  []
).option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(
  async (keyword, options) => {
    await runFindCommand(keyword, options);
  }
);
program.command("list").description("List installed MCP servers across detected agents").option("-g, --global", "List global configs instead of project-level").option("-a, --agent <agent>", "Filter to specific agent(s)", collect, []).action(async (rawOptions) => {
  const options = {
    ...extractOptions(rawOptions),
    ...extractSubcommandOptionsFromArgv()
  };
  await runListCommand(options);
});
program.command("remove <query>").description("Remove an MCP server from agent configurations").option("-g, --global", "Remove from global configs instead of project-level").option("-a, --agent <agent>", "Filter to specific agent(s)", collect, []).option("-y, --yes", "Remove all matches without prompting").action(
  async (query, rawOptions) => {
    const options = {
      ...extractOptions(rawOptions),
      ...extractSubcommandOptionsFromArgv()
    };
    await runRemoveCommand(query, options);
  }
);
program.command("sync").description(
  "Synchronize server names and installations across all detected agents"
).option("-g, --global", "Sync global configs instead of project-level").option("-y, --yes", "Skip confirmation prompts").action(async (rawOptions) => {
  const options = {
    ...extractOptions(rawOptions),
    ...extractSubcommandOptionsFromArgv()
  };
  await runSyncCommand(options);
});
program.command("unify").description("Alias for sync").option("-g, --global", "Sync global configs instead of project-level").option("-y, --yes", "Skip confirmation prompts").action(async (rawOptions) => {
  const options = {
    ...extractOptions(rawOptions),
    ...extractSubcommandOptionsFromArgv()
  };
  await runSyncCommand(options);
});
program.parse();
async function runListCommand(options) {
  showLogo();
  console.log();
  const explicitAgents = resolveAgentFlags(options.agent);
  const agentServersList = await listInstalledServers({
    global: options.global,
    agents: explicitAgents.length > 0 ? explicitAgents : void 0
  });
  if (agentServersList.length === 0) {
    const hint = options.global ? "No agents detected globally. Use -a to target a specific agent." : "No agents detected in this project. Use -g for global or -a to target a specific agent.";
    p2.log.info(hint);
    console.log();
    return;
  }
  let hadError = false;
  for (const agentServers of agentServersList) {
    if (agentServers.error) {
      p2.log.error(`${agentServers.displayName}: ${agentServers.error}`);
      hadError = true;
      continue;
    }
    if (!agentServers.detected) {
      console.log(
        `${TEXT}${agentServers.displayName}:${RESET} ${DIM}not detected${RESET}`
      );
      continue;
    }
    if (agentServers.servers.length === 0) {
      console.log(
        `${TEXT}${agentServers.displayName}:${RESET} ${DIM}no servers configured${RESET}`
      );
      continue;
    }
    console.log(`${TEXT}${agentServers.displayName}:${RESET}`);
    for (const server of agentServers.servers) {
      const identityHint = server.identity ? ` ${DIM}(${server.identity})${RESET}` : "";
      console.log(
        `  ${DIM}-${RESET} ${TEXT}${server.serverName}${RESET}${identityHint}`
      );
    }
  }
  console.log();
  if (hadError) {
    process.exitCode = 1;
  }
}
async function runRemoveCommand(query, options) {
  showLogo();
  console.log();
  const explicitAgents = resolveAgentFlags(options.agent);
  const agentServersList = await listInstalledServers({
    global: options.global,
    agents: explicitAgents.length > 0 ? explicitAgents : void 0
  });
  const hadReadError = reportAgentReadErrors(agentServersList);
  const matches = findMatchingServers(
    agentsWithReadableConfigs(agentServersList),
    query
  );
  if (matches.length === 0) {
    if (!hadReadError) {
      p2.log.info(`No matching servers found for '${query}'`);
    }
    if (hadReadError) {
      process.exitCode = 1;
    }
    console.log();
    return;
  }
  if (matches.some(
    (server) => (server.agentType === "claude-code" || server.agentType === "github-copilot-cli") && server.configPath.endsWith(".mcp.json")
  )) {
    p2.log.warn(
      "`.mcp.json` is shared by Claude Code and GitHub Copilot CLI. Removing a server from either removes it for both."
    );
  }
  const matchOptions = matches.map((m, i) => ({
    value: i,
    label: `${m.serverName} (${agents[m.agentType].displayName})`,
    hint: m.identity || m.configPath
  }));
  let selectedIndices;
  if (options.yes) {
    selectedIndices = matches.map((_, i) => i);
    p2.log.info(
      `Removing ${matches.length} server${matches.length !== 1 ? "s" : ""} matching '${query}'`
    );
  } else {
    const selected = await p2.multiselect({
      message: `Select servers to remove (${matches.length} match${matches.length !== 1 ? "es" : ""} found)`,
      options: matchOptions,
      required: false,
      initialValues: matches.map((_, i) => i)
    });
    if (p2.isCancel(selected)) {
      p2.log.info("No changes made");
      console.log();
      return;
    }
    selectedIndices = selected;
    if (selectedIndices.length === 0) {
      p2.log.info("No changes made");
      console.log();
      return;
    }
  }
  let removedCount = 0;
  const affectedAgents = /* @__PURE__ */ new Set();
  let mutationFailed = false;
  for (const idx of selectedIndices) {
    const server = matches[idx];
    const agent = agents[server.agentType];
    try {
      if (server.agentType === "opencode") {
        removeOpenCodeServer(server.configPath, server.serverName);
      } else {
        removeServerFromConfig(
          server.configPath,
          agent.format,
          getConfigKeyForServer(server),
          server.serverName
        );
        rewriteCopilotCliConfig(server.agentType, server.configPath);
      }
      removedCount++;
      affectedAgents.add(agent.displayName);
    } catch (error) {
      mutationFailed = true;
      p2.log.error(
        `Failed to remove ${server.serverName} from ${agent.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }
  if (removedCount > 0) {
    p2.log.success(
      `Removed ${removedCount} server${removedCount !== 1 ? "s" : ""} from ${affectedAgents.size} agent${affectedAgents.size !== 1 ? "s" : ""}`
    );
  }
  if (hadReadError || mutationFailed) {
    process.exitCode = 1;
  }
  console.log();
}
function failedConfigPaths(agentServersList) {
  return new Set(
    agentServersList.filter((agentServers) => agentServers.error).map((agentServers) => agentServers.configPath)
  );
}
function agentsWithReadableConfigs(agentServersList) {
  const failedPaths = failedConfigPaths(agentServersList);
  return agentServersList.filter(
    (agentServers) => !agentServers.error && !failedPaths.has(agentServers.configPath)
  );
}
function reportAgentReadErrors(agentServersList) {
  let hadError = false;
  for (const agentServers of agentServersList) {
    if (agentServers.error) {
      p2.log.error(`${agentServers.displayName}: ${agentServers.error}`);
      hadError = true;
    }
  }
  return hadError;
}
function getConfigKeyForServer(server) {
  return server.configKey;
}
function deepEqual(a, b) {
  if (a === b) return true;
  if (a === null || b === null) return false;
  if (a === void 0 || b === void 0) return a === b;
  if (typeof a !== typeof b) return false;
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    return a.every((val, i) => deepEqual(val, b[i]));
  }
  if (typeof a === "object" && typeof b === "object") {
    const aObj = a;
    const bObj = b;
    const aKeys = Object.keys(aObj).sort();
    const bKeys = Object.keys(bObj).sort();
    if (!deepEqual(aKeys, bKeys)) return false;
    return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
  }
  return false;
}
function pickCanonicalName(entries) {
  const nameFreq = /* @__PURE__ */ new Map();
  for (const entry of entries) {
    nameFreq.set(entry.serverName, (nameFreq.get(entry.serverName) ?? 0) + 1);
  }
  const names = [...nameFreq.entries()];
  names.sort(([nameA, freqA], [nameB, freqB]) => {
    if (nameA.length !== nameB.length) return nameA.length - nameB.length;
    if (freqA !== freqB) return freqB - freqA;
    return nameA.localeCompare(nameB);
  });
  return names[0][0];
}
function extractConflictFields(config) {
  return {
    headers: config.headers ?? config.http_headers ?? null,
    env: config.env ?? config.envs ?? config.environment ?? null,
    args: normalizeStoredCommand(config).args
  };
}
function buildSyncGroups(agentServersList) {
  const byIdentity = /* @__PURE__ */ new Map();
  for (const agentServers of agentServersList) {
    for (const server of agentServers.servers) {
      if (!server.identity) continue;
      const existing = byIdentity.get(server.identity) ?? [];
      existing.push(server);
      byIdentity.set(server.identity, existing);
    }
  }
  const groups = [];
  for (const [identity, entries] of byIdentity) {
    const fieldSets = entries.map((e) => extractConflictFields(e.config));
    const reference = fieldSets[0];
    let hasConflict = false;
    let conflictReason;
    for (let i = 1; i < fieldSets.length; i++) {
      const other = fieldSets[i];
      if (!deepEqual(reference.headers, other.headers)) {
        hasConflict = true;
        conflictReason = `headers differ between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
        break;
      }
      if (!deepEqual(reference.env, other.env)) {
        hasConflict = true;
        conflictReason = `env differs between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
        break;
      }
      if (!deepEqual(reference.args, other.args)) {
        hasConflict = true;
        conflictReason = `args differ between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
        break;
      }
    }
    groups.push({
      identity,
      entries,
      canonicalName: pickCanonicalName(entries),
      canonicalConfig: entries[0].config,
      hasConflict,
      conflictReason
    });
  }
  return groups;
}
async function runSyncCommand(options) {
  showLogo();
  console.log();
  const agentServersList = await listInstalledServers({
    global: options.global
  });
  const hadReadError = reportAgentReadErrors(agentServersList);
  if (hadReadError) {
    process.exitCode = 1;
  }
  const readable = agentsWithReadableConfigs(agentServersList);
  if (hadReadError && readable.length < 2) {
    console.log();
    return;
  }
  if (readable.length < 2) {
    p2.log.info("Need at least 2 detected agents to sync");
    console.log();
    return;
  }
  const groups = buildSyncGroups(readable);
  const detectedAgentTypes = new Set(readable.map((a) => a.agentType));
  const renames = [];
  const additions = [];
  const skipped = [];
  for (const group of groups) {
    if (group.hasConflict) {
      skipped.push(group);
      continue;
    }
    const presentAgents = new Set(group.entries.map((e) => e.agentType));
    for (const entry of group.entries) {
      if (entry.serverName !== group.canonicalName) {
        renames.push({
          group,
          agentType: entry.agentType,
          oldName: entry.serverName
        });
      }
    }
    for (const agentType of detectedAgentTypes) {
      if (!presentAgents.has(agentType)) {
        additions.push({ group, agentType });
      }
    }
  }
  const scope = options.global ? "global" : "local";
  const layoutError = scope === "local" && detectedAgentTypes.has("github-copilot-cli") && detectedAgentTypes.has("claude-code") ? claudeCopilotGithubShadowError(process.cwd()) : null;
  const isBlocked = (agentType) => Boolean(layoutError) && (agentType === "github-copilot-cli" || agentType === "claude-code");
  const actionRenames = renames.filter((r) => !isBlocked(r.agentType));
  const actionAdditions = additions.filter((a) => !isBlocked(a.agentType));
  const blockedRenames = renames.filter((r) => isBlocked(r.agentType));
  const blockedAdditions = additions.filter((a) => isBlocked(a.agentType));
  if (actionRenames.length === 0 && actionAdditions.length === 0 && blockedRenames.length === 0 && blockedAdditions.length === 0 && skipped.length === 0) {
    if (!hadReadError) {
      p2.log.info("All servers are already in sync");
    }
    console.log();
    return;
  }
  const planLines = [];
  if (actionRenames.length > 0) {
    planLines.push(chalk.cyan("Renames:"));
    for (const r of actionRenames) {
      planLines.push(
        `  ${agents[r.agentType].displayName}: ${r.oldName} \u2192 ${r.group.canonicalName}`
      );
    }
  }
  if (actionAdditions.length > 0) {
    planLines.push(chalk.cyan("Additions:"));
    for (const a of actionAdditions) {
      planLines.push(
        `  ${agents[a.agentType].displayName}: + ${a.group.canonicalName} (${a.group.identity})`
      );
    }
  }
  if (blockedRenames.length > 0 || blockedAdditions.length > 0) {
    planLines.push(chalk.yellow("Blocked:"));
    for (const r of blockedRenames) {
      planLines.push(
        `  ${agents[r.agentType].displayName}: ${r.oldName} \u2192 ${r.group.canonicalName}`
      );
    }
    for (const a of blockedAdditions) {
      planLines.push(
        `  ${agents[a.agentType].displayName}: + ${a.group.canonicalName} (${a.group.identity})`
      );
    }
    if (layoutError) {
      planLines.push(`  ${layoutError}`);
    }
  }
  if (skipped.length > 0) {
    planLines.push(chalk.yellow("Skipped (conflicts):"));
    for (const s of skipped) {
      planLines.push(`  ${s.identity}: ${s.conflictReason}`);
    }
  }
  if (actionRenames.length === 0 && actionAdditions.length === 0) {
    p2.note(planLines.join("\n"), "Sync Plan");
    if (layoutError) {
      p2.log.error(layoutError);
      process.exitCode = 1;
    } else if (!hadReadError) {
      p2.log.info(
        "All servers are already in sync (some skipped due to conflicts)"
      );
    }
    console.log();
    return;
  }
  p2.note(planLines.join("\n"), "Sync Plan");
  if (!options.yes) {
    const confirmed = await p2.confirm({
      message: "Proceed with sync?"
    });
    if (p2.isCancel(confirmed) || !confirmed) {
      p2.log.info("No changes made");
      console.log();
      return;
    }
  }
  let changeCount = 0;
  let mutationFailed = false;
  const renamed = /* @__PURE__ */ new Set();
  for (const rename of actionRenames) {
    const { group, agentType, oldName } = rename;
    if (agentType === "opencode") {
      const source = group.entries.find(
        (item) => item.agentType === "opencode" && item.serverName === oldName
      );
      if (!source) {
        mutationFailed = true;
        p2.log.error(
          `Failed to write ${group.canonicalName} to ${agents.opencode.displayName}: missing source entry ${oldName}`
        );
        continue;
      }
      try {
        relocateOpenCodeServer(source.configPath, oldName, group.canonicalName);
        changeCount++;
        renamed.add(rename);
      } catch (error) {
        mutationFailed = true;
        p2.log.error(
          `Failed to write ${group.canonicalName} to ${agents.opencode.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
        );
      }
      continue;
    }
    const result = installServerForAgent(
      group.canonicalName,
      buildServerConfigFromStored(group.canonicalConfig),
      agentType,
      { local: scope === "local" }
    );
    if (result.success) {
      changeCount++;
      renamed.add(rename);
    } else {
      mutationFailed = true;
      p2.log.error(
        `Failed to write ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
      );
    }
  }
  for (const addition of actionAdditions) {
    const { group, agentType } = addition;
    if (agentType === "opencode") {
      const opencode = readable.find((item) => item.agentType === "opencode");
      if (opencode) {
        const dest = listOpenCodeServers(opencode.configPath).find(
          (entry) => entry.serverName === group.canonicalName
        );
        if (dest) {
          if (extractServerIdentity(dest.config) === group.identity) {
            continue;
          }
          mutationFailed = true;
          p2.log.error(
            `Failed to add ${group.canonicalName} to ${agents.opencode.displayName}: already has a different server named "${group.canonicalName}"`
          );
          continue;
        }
      }
    }
    const result = installServerForAgent(
      group.canonicalName,
      buildServerConfigFromStored(group.canonicalConfig),
      agentType,
      { local: scope === "local" }
    );
    if (result.success) {
      changeCount++;
    } else {
      mutationFailed = true;
      p2.log.error(
        `Failed to add ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
      );
    }
  }
  for (const rename of actionRenames) {
    if (!renamed.has(rename)) {
      continue;
    }
    const { group, agentType, oldName } = rename;
    const agentConfig = agents[agentType];
    const entry = group.entries.find((e) => e.agentType === agentType);
    if (!entry) continue;
    try {
      if (agentType === "opencode") {
        continue;
      }
      removeServerFromConfig(
        entry.configPath,
        agentConfig.format,
        getConfigKey(agentConfig, { local: scope === "local" }),
        oldName
      );
      rewriteCopilotCliConfig(agentType, entry.configPath);
    } catch (error) {
      mutationFailed = true;
      p2.log.error(
        `Failed to remove old alias ${oldName} from ${agentConfig.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }
  if (layoutError || mutationFailed) {
    if (layoutError) {
      p2.log.error(layoutError);
    }
    process.exitCode = 1;
  }
  p2.log.success(
    `Synced ${changeCount} server${changeCount !== 1 ? "s" : ""} across ${detectedAgentTypes.size} agent${detectedAgentTypes.size !== 1 ? "s" : ""}`
  );
  console.log();
}
var TRANSPORT_ALIASES = {
  http: "http",
  sse: "sse",
  streamable_http: "http",
  streamableHttp: "http",
  "streamable-http": "http",
  remote: "http"
};
function normalizeTransportType(raw) {
  if (typeof raw === "string" && raw in TRANSPORT_ALIASES) {
    return TRANSPORT_ALIASES[raw];
  }
  return "http";
}
function buildServerConfigFromStored(config) {
  const httpUrl = typeof config.httpUrl === "string" && config.httpUrl.length > 0 ? config.httpUrl : void 0;
  const url = httpUrl ?? (typeof config.url === "string" ? config.url : typeof config.uri === "string" ? config.uri : typeof config.serverUrl === "string" ? config.serverUrl : void 0);
  if (url) {
    const result2 = {
      type: httpUrl ? "http" : normalizeTransportType(config.type ?? config.transport),
      url
    };
    const headers = config.headers && typeof config.headers === "object" ? config.headers : config.http_headers && typeof config.http_headers === "object" ? config.http_headers : void 0;
    if (headers && Object.keys(headers).length > 0) {
      result2.headers = headers;
    }
    return result2;
  }
  const { command, args } = normalizeStoredCommand(config);
  const env = config.env && typeof config.env === "object" ? config.env : config.envs && typeof config.envs === "object" ? config.envs : config.environment && typeof config.environment === "object" ? config.environment : void 0;
  const result = {};
  if (command) result.command = command;
  if (args.length > 0) result.args = args;
  if (env && Object.keys(env).length > 0) result.env = env;
  return result;
}
function resolveAgentFlags(agentFlags) {
  if (!agentFlags || agentFlags.length === 0) return [];
  const resolved = [];
  const invalid = [];
  for (const input of agentFlags) {
    const agentType = resolveAgentType(input);
    if (agentType) {
      resolved.push(agentType);
    } else {
      invalid.push(input);
    }
  }
  if (invalid.length > 0) {
    p2.log.error(`Invalid agents: ${invalid.join(", ")}`);
    p2.log.info(`Valid agents: ${getAgentTypes().join(", ")}`);
    process.exit(1);
  }
  return resolved;
}
function listAgents() {
  showLogo();
  console.log();
  console.log(`${DIM}Supported agents:${RESET}`);
  console.log();
  const allAgentTypes = getAgentTypes();
  const aliasesByAgent = {};
  for (const [alias, target] of Object.entries(agentAliases)) {
    if (!aliasesByAgent[target]) {
      aliasesByAgent[target] = [];
    }
    aliasesByAgent[target].push(alias);
  }
  const nameColWidth = Math.max(
    "Argument".length,
    ...allAgentTypes.map((t) => t.length)
  );
  const clientColWidth = Math.max(
    "MCP Client".length,
    ...allAgentTypes.map((t) => agents[t].displayName.length)
  );
  const aliasColWidth = Math.max(
    "Aliases".length,
    ...allAgentTypes.map(
      (t) => (aliasesByAgent[t] ? aliasesByAgent[t].join(", ") : "").length
    )
  );
  const pad = (str, width) => str + " ".repeat(Math.max(0, width - str.length));
  const header = `  ${pad("Argument", nameColWidth)}  ${pad("MCP Client", clientColWidth)}  ${pad("Aliases", aliasColWidth)}  Local  Global`;
  const separator = `  ${"-".repeat(nameColWidth)}  ${"-".repeat(clientColWidth)}  ${"-".repeat(aliasColWidth)}  -----  ------`;
  console.log(`${DIM}${header}${RESET}`);
  console.log(`${DIM}${separator}${RESET}`);
  for (const agentType of allAgentTypes) {
    const agent = agents[agentType];
    const hasLocal = supportsProjectConfig(agentType);
    const localMark = hasLocal ? "  \u2713  " : "  -  ";
    const globalMark = "  \u2713  ";
    const aliasStr = aliasesByAgent[agentType] ? aliasesByAgent[agentType].join(", ") : "";
    console.log(
      `  ${TEXT}${pad(agentType, nameColWidth)}${RESET}  ${DIM}${pad(agent.displayName, clientColWidth)}${RESET}  ${DIM}${pad(aliasStr, aliasColWidth)}${RESET}  ${TEXT}${localMark}${RESET} ${TEXT}${globalMark}${RESET}`
    );
  }
  console.log();
}
async function main(target, options) {
  showLogo();
  if (!target) {
    console.log();
    console.log(`${DIM}Add MCP servers to your favorite coding agents${RESET}`);
    console.log();
    console.log(
      `  ${DIM}$${RESET} ${TEXT}npx add-mcp ${DIM}<url>${RESET}              ${DIM}Install remote MCP server${RESET}`
    );
    console.log(
      `  ${DIM}$${RESET} ${TEXT}npx add-mcp ${DIM}<package>${RESET}          ${DIM}Install npm package${RESET}`
    );
    console.log(
      `  ${DIM}$${RESET} ${TEXT}npx add-mcp ${DIM}<url> ${TEXT}-g${RESET}            ${DIM}Install globally${RESET}`
    );
    console.log(
      `  ${DIM}$${RESET} ${TEXT}npx add-mcp ${DIM}<url> ${TEXT}-a cursor${RESET}    ${DIM}Install to specific agent${RESET}`
    );
    console.log(
      `  ${DIM}$${RESET} ${TEXT}npx add-mcp find ${DIM}<keyword>${RESET}     ${DIM}Search integrations.sh MCP servers${RESET}`
    );
    console.log();
    console.log(
      `${DIM}Supports:${RESET} Claude Code, Cursor, VS Code, OpenCode, and more`
    );
    console.log();
    console.log(
      `${DIM}Learn more at${RESET} ${TEXT}https://github.com/neondatabase/add-mcp${RESET}`
    );
    console.log();
    process.exit(0);
  }
  console.log();
  const spinner2 = p2.spinner();
  spinner2.start("Parsing source...");
  const parsed = parseSource(target);
  const isRemote = isRemoteSource(parsed);
  const sourceType = isRemote ? "remote" : "local";
  spinner2.stop(`Source: ${chalk.cyan(parsed.value)} (${sourceType})`);
  const headerValues = options.header ?? [];
  const headerResult = parseHeaders(headerValues);
  if (headerResult.invalid.length > 0) {
    const hint = looksLikeEatenShellVar(headerResult.invalid, ":") ? " (looks like your shell expanded a ${VAR} to an empty string; use single quotes: --header 'Key: ${VAR}' to pass the template literally)" : "";
    p2.log.error(
      `Invalid --header value(s): ${headerResult.invalid.join(", ")}. Use 'Key: Value' format.${hint}`
    );
    process.exit(1);
  }
  const headerKeys = Object.keys(headerResult.headers);
  const hasHeaderValues = headerKeys.length > 0;
  if (hasHeaderValues && !isRemote) {
    p2.log.warn("--header is only used for remote URLs, ignoring");
  }
  const envValues = options.env ?? [];
  const envResult = parseEnv(envValues);
  if (envResult.invalid.length > 0) {
    const hint = looksLikeEatenShellVar(envResult.invalid, "=") ? " (looks like your shell expanded a ${VAR} to an empty string; use single quotes: --env 'KEY=${VAR}' to pass the template literally)" : "";
    p2.log.error(
      `Invalid --env value(s): ${envResult.invalid.join(", ")}. Use 'KEY=VALUE' format.${hint}`
    );
    process.exit(1);
  }
  const envKeys = Object.keys(envResult.env);
  const hasEnvValues = envKeys.length > 0;
  if (hasEnvValues && isRemote) {
    p2.log.warn(
      "--env is only used for local/package/command installs, ignoring"
    );
  }
  const argsValues = options.args ?? [];
  const hasArgsValues = argsValues.length > 0;
  if (hasArgsValues && isRemote) {
    p2.log.warn(
      "--args is only used for local/package/command installs, ignoring"
    );
  }
  const promptTemplateVar = (varName) => p2.text({
    message: `Enter value for ${varName}`,
    placeholder: `<${varName}>`
  });
  let resolvedArgs = [...argsValues];
  if (!options.yes && hasHeaderValues && hasTemplateVars(headerResult.headers)) {
    const result = await resolveRecordTemplates(
      headerResult.headers,
      promptTemplateVar
    );
    if (result.cancelled) {
      p2.cancel("Cancelled");
      process.exit(0);
    }
    for (const [key, value] of Object.entries(result.resolved)) {
      headerResult.headers[key] = value;
    }
  }
  if (!options.yes && hasEnvValues && hasTemplateVars(envResult.env)) {
    const result = await resolveRecordTemplates(
      envResult.env,
      promptTemplateVar
    );
    if (result.cancelled) {
      p2.cancel("Cancelled");
      process.exit(0);
    }
    for (const [key, value] of Object.entries(result.resolved)) {
      envResult.env[key] = value;
    }
  }
  if (!options.yes && hasArgsValues && hasTemplateVars(resolvedArgs)) {
    const result = await resolveArrayTemplates(resolvedArgs, promptTemplateVar);
    if (result.cancelled) {
      p2.cancel("Cancelled");
      process.exit(0);
    }
    resolvedArgs = result.resolved;
  }
  const headersForConfig = isRemote && hasHeaderValues ? omitEmptyStringValues(headerResult.headers) : void 0;
  const envForConfig = !isRemote && hasEnvValues ? omitEmptyStringValues(envResult.env) : void 0;
  const argsForConfig = !isRemote && hasArgsValues ? resolvedArgs.filter((a) => a.trim().length > 0) : void 0;
  const serverName = options.name || parsed.inferredName;
  p2.log.info(`Server name: ${chalk.cyan(serverName)}`);
  const transportValue = options.transport || options.type;
  let resolvedTransport;
  if (transportValue) {
    const validTransports = ["http", "sse"];
    if (!validTransports.includes(transportValue)) {
      p2.log.error(
        `Invalid transport: ${transportValue}. Valid options: ${validTransports.join(", ")}`
      );
      process.exit(1);
    }
    resolvedTransport = transportValue;
    if (!isRemoteSource(parsed)) {
      p2.log.warn("--transport is only used for remote URLs, ignoring");
    }
  }
  let resolvedTimeout;
  if (options.timeout !== void 0) {
    const parsedTimeout = Number(options.timeout);
    if (!Number.isInteger(parsedTimeout) || parsedTimeout <= 0) {
      p2.log.error(
        `Invalid --timeout value: ${options.timeout}. Provide a positive integer (milliseconds).`
      );
      process.exit(1);
    }
    if (isRemote) {
      resolvedTimeout = parsedTimeout;
    } else {
      p2.log.warn("--timeout is only used for remote URLs, ignoring");
    }
  }
  const scopesValue = options.scopes ?? options.oauthScopes;
  let resolvedScopes;
  if (scopesValue !== void 0) {
    const parsedScopes = scopesValue.split(",").map((scope) => scope.trim()).filter((scope) => scope.length > 0);
    if (parsedScopes.length === 0) {
      p2.log.error(
        `Invalid --scopes value: ${scopesValue}. Provide one or more comma-separated scopes.`
      );
      process.exit(1);
    }
    if (isRemote) {
      resolvedScopes = parsedScopes;
    } else {
      p2.log.warn("--scopes is only used for remote URLs, ignoring");
    }
  }
  const approveTools = [...new Set(options.approveTool ?? [])];
  const autoApproveTools = options.autoApprove || approveTools.length > 0 ? approveTools : void 0;
  let resolvedBearerTokenEnv;
  if (options.bearerTokenEnv !== void 0) {
    const name = options.bearerTokenEnv.trim();
    if (name.length === 0) {
      p2.log.error("Invalid --bearer-token-env. The name cannot be empty.");
      process.exit(1);
    }
    if (!isBearerTokenEnvName(name)) {
      p2.log.error(
        `--bearer-token-env takes an environment variable name ([A-Za-z_][A-Za-z0-9_]*). "${name}" is not a valid name.`
      );
      process.exit(1);
    }
    if (isRemote) {
      resolvedBearerTokenEnv = name;
    } else {
      p2.log.warn("--bearer-token-env is only used for remote URLs, ignoring");
    }
  }
  const serverConfig = buildServerConfig(parsed, {
    transport: resolvedTransport,
    headers: headersForConfig && Object.keys(headersForConfig).length > 0 ? headersForConfig : void 0,
    env: envForConfig && Object.keys(envForConfig).length > 0 ? envForConfig : void 0,
    args: argsForConfig && argsForConfig.length > 0 ? argsForConfig : void 0,
    timeout: resolvedTimeout,
    oauthScopes: resolvedScopes,
    autoApproveTools,
    bearerTokenEnv: resolvedBearerTokenEnv
  });
  let targetAgents;
  const allAgentTypes = getAgentTypes();
  let agentRouting = /* @__PURE__ */ new Map();
  if (options.agent && options.agent.length > 0) {
    const resolved = [];
    const invalid = [];
    for (const input of options.agent) {
      const agentType = resolveAgentType(input);
      if (agentType) {
        resolved.push(agentType);
      } else {
        invalid.push(input);
      }
    }
    if (invalid.length > 0) {
      p2.log.error(`Invalid agents: ${invalid.join(", ")}`);
      p2.log.info(`Valid agents: ${allAgentTypes.join(", ")}`);
      process.exit(1);
    }
    targetAgents = resolved;
  } else if (options.all) {
    targetAgents = allAgentTypes;
    p2.log.info(`Installing to all ${targetAgents.length} agents`);
  } else {
    spinner2.start("Detecting agents...");
    let detectedAgents;
    if (options.global) {
      detectedAgents = await detectGlobalAgents();
      for (const agent of detectedAgents) {
        agentRouting.set(agent, "global");
      }
    } else {
      const projectAgents = detectProjectAgents();
      detectedAgents = projectAgents;
      for (const agent of projectAgents) {
        agentRouting.set(agent, "local");
      }
    }
    spinner2.stop(
      `Detected ${detectedAgents.length} agent${detectedAgents.length !== 1 ? "s" : ""}`
    );
    if (detectedAgents.length === 0) {
      if (options.yes) {
        if (options.global) {
          targetAgents = allAgentTypes;
          for (const agent of targetAgents) {
            agentRouting.set(agent, "global");
          }
          p2.log.info(
            `Installing to ${targetAgents.length} agents globally (none detected)`
          );
        } else {
          targetAgents = getProjectCapableAgents();
          for (const agent of targetAgents) {
            agentRouting.set(agent, "local");
          }
          p2.log.info(
            `Installing to ${targetAgents.length} project-capable agents (none detected)`
          );
        }
      } else {
        const availableAgents = allAgentTypes;
        p2.log.warn(
          options.global ? "No coding agents detected." : "No agents detected in this project."
        );
        const selected = await selectAgentsInteractive(availableAgents, {
          global: options.global
        });
        if (p2.isCancel(selected)) {
          p2.cancel("Installation cancelled");
          process.exit(0);
        }
        targetAgents = selected;
      }
    } else if (options.yes) {
      targetAgents = detectedAgents;
      const agentNames = detectedAgents.map((a) => chalk.cyan(agents[a].displayName)).join(", ");
      p2.log.info(`Installing to: ${agentNames}`);
    } else {
      const availableAgents = allAgentTypes;
      let lastSelected;
      try {
        lastSelected = await getLastSelectedAgents();
      } catch {
      }
      const { choices: agentChoices, initialValues } = buildAgentSelectionChoices({
        availableAgents,
        detectedAgents,
        agentRouting,
        lastSelected
      });
      const selected = await p2.multiselect({
        message: "Select agents to install to",
        options: agentChoices,
        required: true,
        initialValues
      });
      if (p2.isCancel(selected)) {
        p2.cancel("Installation cancelled");
        process.exit(0);
      }
      targetAgents = selected;
    }
  }
  const requiredTransport = isRemoteSource(parsed) ? resolvedTransport ?? "http" : "stdio";
  const unsupportedAgents = targetAgents.filter(
    (a) => !isTransportSupported(a, requiredTransport)
  );
  if (unsupportedAgents.length > 0) {
    const unsupportedNames = unsupportedAgents.map((a) => agents[a].displayName).join(", ");
    const hints = unsupportedAgents.map((a) => agents[a].unsupportedTransportMessage).filter(Boolean);
    if (options.all) {
      p2.log.warn(
        `Skipping agents that don't support ${requiredTransport} transport: ${unsupportedNames}`
      );
      for (const hint of hints) {
        p2.log.info(hint);
      }
      targetAgents = targetAgents.filter(
        (a) => isTransportSupported(a, requiredTransport)
      );
      if (targetAgents.length === 0) {
        p2.log.error("No agents support this transport type");
        process.exit(1);
      }
    } else {
      p2.log.error(
        `The following agents don't support ${requiredTransport} transport: ${unsupportedNames}`
      );
      for (const hint of hints) {
        p2.log.info(hint);
      }
      process.exit(1);
    }
  }
  if (options.global) {
    agentRouting = /* @__PURE__ */ new Map();
    for (const agent of targetAgents) {
      agentRouting.set(agent, "global");
    }
  } else {
    const commonScopes = getCommonInstallScopes(targetAgents);
    if (commonScopes.length === 0) {
      p2.log.error("No agents selected");
      process.exit(1);
    }
    let installScope = commonScopes[0];
    if (commonScopes.length > 1 && !options.yes) {
      const scope = await p2.select({
        message: "Installation scope",
        options: [
          {
            value: "local",
            label: "Project",
            hint: "Install in current directory (committed with your project)"
          },
          {
            value: "global",
            label: "Global",
            hint: "Install in home directory (available across all projects)"
          }
        ]
      });
      if (p2.isCancel(scope)) {
        p2.cancel("Installation cancelled");
        process.exit(0);
      }
      installScope = scope;
    } else if (installScope === "global") {
      p2.log.info("Selected agents require global installation");
    }
    agentRouting = /* @__PURE__ */ new Map();
    for (const agent of targetAgents) {
      agentRouting.set(agent, installScope);
    }
  }
  const summaryLines = [];
  summaryLines.push(`${chalk.cyan("Server:")} ${serverName}`);
  summaryLines.push(`${chalk.cyan("Type:")} ${sourceType}`);
  if (autoApproveTools) {
    summaryLines.push(
      `${chalk.cyan("Auto-approve:")} ${autoApproveTools.length === 0 ? "All tools" : autoApproveTools.join(", ")}`
    );
  }
  const localAgents = targetAgents.filter(
    (a) => agentRouting.get(a) === "local"
  );
  const globalAgents = targetAgents.filter(
    (a) => agentRouting.get(a) === "global"
  );
  if (localAgents.length > 0) {
    summaryLines.push(`${chalk.cyan("Scope:")} Project`);
    summaryLines.push(
      `${chalk.cyan("Agents:")} ${localAgents.map((a) => agents[a].displayName).join(", ")}`
    );
  } else {
    summaryLines.push(`${chalk.cyan("Scope:")} Global`);
    summaryLines.push(
      `${chalk.cyan("Agents:")} ${globalAgents.map((a) => agents[a].displayName).join(", ")}`
    );
  }
  console.log();
  p2.note(summaryLines.join("\n"), "Installation Summary");
  if (!options.yes) {
    const confirmed = await p2.confirm({
      message: "Proceed with installation?"
    });
    if (p2.isCancel(confirmed) || !confirmed) {
      p2.cancel("Installation cancelled");
      process.exit(0);
    }
  }
  spinner2.start("Installing MCP server...");
  const results = installServer(serverName, serverConfig, targetAgents, {
    routing: agentRouting
  });
  spinner2.stop("Installation complete");
  console.log();
  const successful = [...results.entries()].filter(([_, r]) => r.success);
  const failed = [...results.entries()].filter(([_, r]) => !r.success);
  if (successful.length > 0) {
    const resultLines = [];
    for (const [agentType, result] of successful) {
      const agent = agents[agentType];
      const shortPath = shortenPath(result.path);
      resultLines.push(
        `${chalk.green("\u2713")} ${agent.displayName}: ${chalk.dim(shortPath)}`
      );
      for (const extraPath of result.extraPaths ?? []) {
        resultLines.push(
          `  ${chalk.dim("\u21B3 permissions:")} ${chalk.dim(shortenPath(extraPath))}`
        );
      }
    }
    p2.note(
      resultLines.join("\n"),
      chalk.green(
        `Installed to ${successful.length} agent${successful.length !== 1 ? "s" : ""}`
      )
    );
  }
  if (failed.length > 0) {
    console.log();
    p2.log.error(
      chalk.red(
        `Failed to install to ${failed.length} agent${failed.length !== 1 ? "s" : ""}`
      )
    );
    for (const [agentType, result] of failed) {
      const agent = agents[agentType];
      p2.log.message(
        `  ${chalk.red("\u2717")} ${agent.displayName}: ${chalk.dim(result.error)}`
      );
    }
  }
  const droppedByField = /* @__PURE__ */ new Map();
  for (const [agentType, result] of results) {
    for (const field of result.droppedFields ?? []) {
      const agentNames = droppedByField.get(field) ?? [];
      agentNames.push(agents[agentType].displayName);
      droppedByField.set(field, agentNames);
    }
  }
  for (const [field, agentNames] of droppedByField) {
    p2.log.warn(
      `${describeOptionalField(field)} is not supported by ${agentNames.join(", ")}; dropped from ${agentNames.length === 1 ? "that config" : "those configs"}.`
    );
  }
  if (results.get("fx")?.success && resolvedBearerTokenEnv && serverConfig.headers && Object.keys(serverConfig.headers).some(
    (name) => name.toLowerCase() === "authorization"
  )) {
    p2.log.warn(
      `Authorization header dropped from the fx config; fx reads the token from ${resolvedBearerTokenEnv}.`
    );
  }
  if (options.gitignore && localAgents.length === 0) {
    p2.log.warn(
      "--gitignore is only supported for project-scoped installations; ignoring."
    );
  } else if (options.gitignore) {
    const successfulPaths = successful.flatMap(([_, result]) => [
      result.path,
      ...result.extraPaths ?? []
    ]);
    const gitignoreUpdate = updateGitignoreWithPaths(successfulPaths);
    if (gitignoreUpdate.added.length > 0) {
      p2.log.info(
        `Added ${gitignoreUpdate.added.length} entr${gitignoreUpdate.added.length === 1 ? "y" : "ies"} to .gitignore`
      );
    } else {
      p2.log.info("No new local config paths to add to .gitignore");
    }
  }
  console.log();
  if (failed.length === 0) {
    p2.outro(chalk.green("Done!"));
    return;
  }
  process.exitCode = 1;
  if (successful.length === 0) {
    p2.outro(chalk.red("Failed"));
    return;
  }
  p2.outro(chalk.yellow("Installed with errors"));
}