UNPKG

add-mcp

Version:

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

2,973 lines • 92.1 kB
// src/config.ts
import { readFile, writeFile, mkdir, rm } from "fs/promises";
import { dirname, join } from "path";
import { homedir } from "os";
var CONFIG_DIR = "add-mcp";
var CONFIG_FILE = "config.json";
var CURRENT_VERSION = 1;
var LEGACY_AGENTS_DIR = ".agents";
var LEGACY_LOCK_FILE = ".mcp-lock.json";
var DEFAULT_FIND_REGISTRY_URL = "https://add-mcp.com/registry/api/v1/servers";
var DEFAULT_FIND_REGISTRY_LABEL = "add-mcp registry";
var LEGACY_FIND_REGISTRY_URL = "https://mcp.agent-tooling.dev/api/v1/servers";
var LEGACY_FIND_REGISTRY_LABEL = "integrations.sh MCP registry";
function migrateFindRegistryEntry(entry) {
  if (entry.url !== LEGACY_FIND_REGISTRY_URL) {
    return entry;
  }
  const label = !entry.label || entry.label === LEGACY_FIND_REGISTRY_LABEL ? DEFAULT_FIND_REGISTRY_LABEL : entry.label;
  return { url: DEFAULT_FIND_REGISTRY_URL, label };
}
function getXdgConfigHome() {
  return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
}
function getConfigPath() {
  return join(getXdgConfigHome(), CONFIG_DIR, CONFIG_FILE);
}
function getLegacyConfigPath() {
  return join(homedir(), LEGACY_AGENTS_DIR, LEGACY_LOCK_FILE);
}
async function readConfig() {
  const configPath = getConfigPath();
  try {
    const content = await readFile(configPath, "utf-8");
    const parsed = JSON.parse(content);
    if (typeof parsed.version !== "number") {
      return createEmptyConfig();
    }
    if (parsed.version < CURRENT_VERSION) {
      return createEmptyConfig();
    }
    return parsed;
  } catch {
  }
  const legacyPath = getLegacyConfigPath();
  try {
    const content = await readFile(legacyPath, "utf-8");
    const parsed = JSON.parse(content);
    if (typeof parsed.version !== "number" || parsed.version < CURRENT_VERSION) {
      return createEmptyConfig();
    }
    await writeConfig(parsed);
    await cleanupLegacyConfig();
    return parsed;
  } catch {
    return createEmptyConfig();
  }
}
async function writeConfig(config) {
  const configPath = getConfigPath();
  await mkdir(dirname(configPath), { recursive: true });
  const content = JSON.stringify(config, null, 2);
  await writeFile(configPath, content, "utf-8");
}
async function cleanupLegacyConfig() {
  const legacyPath = getLegacyConfigPath();
  try {
    await rm(legacyPath, { force: true });
  } catch {
  }
}
async function getLastSelectedAgents() {
  const config = await readConfig();
  return config.lastSelectedAgents;
}
async function saveSelectedAgents(agents2) {
  const config = await readConfig();
  config.lastSelectedAgents = agents2;
  await writeConfig(config);
}
async function getFindRegistries() {
  const config = await readConfig();
  if (!config.findRegistries) return [];
  const normalized = config.findRegistries.map(normalizeFindRegistryEntry);
  const migrated = [];
  const seen = /* @__PURE__ */ new Set();
  for (const entry of normalized.map(migrateFindRegistryEntry)) {
    if (seen.has(entry.url)) continue;
    seen.add(entry.url);
    migrated.push(entry);
  }
  const changed = migrated.length !== normalized.length || migrated.some(
    (entry, i) => entry.url !== normalized[i]?.url || entry.label !== normalized[i]?.label
  );
  if (changed) {
    try {
      config.findRegistries = migrated;
      await writeConfig(config);
    } catch {
    }
  }
  return migrated;
}
function normalizeFindRegistryEntry(raw) {
  const legacy = raw;
  const url = legacy.url ?? legacy.serversUrl;
  if (!url || typeof url !== "string") {
    throw new Error("Registry entry missing url");
  }
  return {
    url,
    ...legacy.label ? { label: legacy.label } : {}
  };
}
async function saveFindRegistries(registries) {
  const config = await readConfig();
  config.findRegistries = registries;
  await writeConfig(config);
}
function createEmptyConfig() {
  return {
    version: CURRENT_VERSION
  };
}

// src/schema.ts
var OPTIONAL_FIELD_SPECS = {
  timeout: {
    label: "request timeout",
    isSet: (config) => typeof config.timeout === "number",
    clear: (config) => {
      delete config.timeout;
    }
  },
  scopes: {
    label: "OAuth scopes",
    isSet: (config) => Array.isArray(config.oauthScopes) && config.oauthScopes.length > 0,
    clear: (config) => {
      delete config.oauthScopes;
    }
  },
  autoApprove: {
    label: "tool auto-approval",
    // An empty array is meaningful ("approve all tools"), so presence alone
    // counts as set — unlike scopes where an empty list means "nothing".
    isSet: (config) => Array.isArray(config.autoApproveTools),
    clear: (config) => {
      delete config.autoApproveTools;
    }
  },
  bearerTokenEnv: {
    label: "bearer token env var",
    isSet: (config) => resolvedBearerTokenEnv(config) !== void 0,
    clear: (config) => {
      delete config.bearerTokenEnv;
    }
  }
};
var BEARER_TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
function isBearerTokenEnvName(name) {
  return BEARER_TOKEN_ENV_NAME.test(name);
}
function resolvedBearerTokenEnv(config) {
  if (typeof config.bearerTokenEnv !== "string") {
    return void 0;
  }
  const name = config.bearerTokenEnv.trim();
  return isBearerTokenEnvName(name) ? name : void 0;
}
function invalidBearerTokenEnvMessage(value) {
  const name = value.trim();
  if (name.length === 0) {
    return "Invalid bearerTokenEnv. The name cannot be empty.";
  }
  return `Invalid bearerTokenEnv. "${name}" is not an environment variable name ([A-Za-z_][A-Za-z0-9_]*).`;
}
var ALL_OPTIONAL_FIELDS = Object.keys(
  OPTIONAL_FIELD_SPECS
);
function describeOptionalField(field) {
  return OPTIONAL_FIELD_SPECS[field].label;
}
function applyFieldSupport(config, supportedFields) {
  const supported = new Set(supportedFields);
  const copy = { ...config };
  if (copy.oauthScopes) copy.oauthScopes = [...copy.oauthScopes];
  if (copy.autoApproveTools) copy.autoApproveTools = [...copy.autoApproveTools];
  if (typeof copy.bearerTokenEnv === "string") {
    const name = resolvedBearerTokenEnv(copy);
    if (name) {
      copy.bearerTokenEnv = name;
    } else {
      delete copy.bearerTokenEnv;
    }
  }
  const dropped = [];
  for (const field of ALL_OPTIONAL_FIELDS) {
    const spec = OPTIONAL_FIELD_SPECS[field];
    if (spec.isSet(copy) && !supported.has(field)) {
      spec.clear(copy);
      dropped.push(field);
    }
  }
  return { config: copy, dropped };
}

// src/formats/utils.ts
function deepMerge(target, source) {
  const result = { ...target };
  for (const key in source) {
    const sourceValue = source[key];
    const targetValue = result[key];
    if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
      result[key] = deepMerge(
        targetValue && typeof targetValue === "object" ? targetValue : {},
        sourceValue
      );
    } else {
      result[key] = sourceValue;
    }
  }
  return result;
}
function dropReplacedServers(existing, incoming, configKey) {
  const incomingServers = getNestedValue(incoming, configKey);
  const existingServers = getNestedValue(existing, configKey);
  if (!incomingServers || typeof incomingServers !== "object" || Array.isArray(incomingServers) || !existingServers || typeof existingServers !== "object" || Array.isArray(existingServers)) {
    return;
  }
  for (const name of Object.keys(incomingServers)) {
    delete existingServers[name];
  }
}
var ROOT_CONFIG_KEY = "";
function looksLikeServerEntry(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const entry = value;
  return typeof entry.command === "string" || typeof entry.url === "string" || typeof entry.type === "string";
}
function isConfigObject(value) {
  return !!value && typeof value === "object" && !Array.isArray(value);
}
function isBareServerMap(obj) {
  if (!isConfigObject(obj)) return false;
  if (obj.mcpServers !== void 0) return false;
  const values = Object.values(obj);
  return values.length > 0 && values.every(looksLikeServerEntry);
}
function jsoncPath(configKey) {
  return configKey === ROOT_CONFIG_KEY ? [] : configKey.split(".");
}
function getNestedValue(obj, path) {
  if (path === ROOT_CONFIG_KEY) {
    return obj;
  }
  const keys = path.split(".");
  let current = obj;
  for (const key of keys) {
    if (current && typeof current === "object" && key in current) {
      current = current[key];
    } else {
      return void 0;
    }
  }
  return current;
}

// src/opencode-config.ts
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
import * as jsonc2 from "jsonc-parser";

// src/formats/json.ts
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
import { dirname as dirname2 } from "path";
import * as jsonc from "jsonc-parser";
function detectIndent(text) {
  let result = null;
  jsonc.visit(text, {
    onObjectProperty: (_property, offset, _length, startLine, startCharacter) => {
      if (result === null && startLine > 0 && startCharacter > 0) {
        const lineStart2 = text.lastIndexOf("\n", offset - 1) + 1;
        const whitespace = text.slice(lineStart2, offset);
        result = {
          tabSize: startCharacter,
          insertSpaces: !whitespace.includes("	")
        };
      }
    }
  });
  return result || { tabSize: 2, insertSpaces: true };
}
function readJsonConfig(filePath) {
  if (!existsSync(filePath)) {
    return {};
  }
  const content = readFileSync(filePath, "utf-8");
  const parsed = jsonc.parse(content);
  return parsed;
}
function writeJsonConfig(filePath, config, configKey) {
  const dir = dirname2(filePath);
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }
  let originalContent = "";
  let existingConfig = {};
  if (existsSync(filePath)) {
    originalContent = readFileSync(filePath, "utf-8");
    const parsed = jsonc.parse(originalContent);
    if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
      existingConfig = parsed;
    }
  }
  const replaceDocument = configKey !== ROOT_CONFIG_KEY && isBareServerMap(existingConfig);
  if (replaceDocument) {
    existingConfig = { mcpServers: { ...existingConfig } };
  }
  dropReplacedServers(existingConfig, config, configKey);
  const mergedConfig = deepMerge(existingConfig, config);
  if (originalContent) {
    try {
      const configKeyPath = replaceDocument ? [] : jsoncPath(configKey);
      const newValue = replaceDocument ? mergedConfig : getNestedValue(mergedConfig, configKey);
      const edits = jsonc.modify(originalContent, configKeyPath, newValue, {
        formattingOptions: detectIndent(originalContent)
      });
      const updatedContent = jsonc.applyEdits(originalContent, edits);
      writeFileSync(filePath, updatedContent);
      return;
    } catch {
    }
  }
  writeFileSync(filePath, JSON.stringify(mergedConfig, null, 2));
}
function rewriteJsoncAsJson(filePath) {
  if (!existsSync(filePath)) {
    return;
  }
  const parsed = jsonc.parse(readFileSync(filePath, "utf-8"));
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    return;
  }
  writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
`);
}
function removeJsonConfigKey(filePath, configKey, serverName) {
  if (!existsSync(filePath)) {
    return;
  }
  const originalContent = readFileSync(filePath, "utf-8");
  const configKeyPath = jsoncPath(configKey);
  try {
    const edits = jsonc.modify(
      originalContent,
      [...configKeyPath, serverName],
      void 0,
      { formattingOptions: detectIndent(originalContent) }
    );
    const updatedContent = jsonc.applyEdits(originalContent, edits);
    writeFileSync(filePath, updatedContent);
    return;
  } catch {
  }
  const parsed = jsonc.parse(originalContent);
  const servers = getNestedValue(parsed, configKey);
  if (servers && typeof servers === "object" && serverName in servers) {
    delete servers[serverName];
  }
  writeFileSync(filePath, JSON.stringify(parsed, null, 2));
}
function setNestedValue(obj, path, value) {
  if (path === ROOT_CONFIG_KEY) {
    if (!value || typeof value !== "object" || Array.isArray(value)) {
      return;
    }
    for (const key of Object.keys(obj)) {
      delete obj[key];
    }
    Object.assign(obj, value);
    return;
  }
  const keys = path.split(".");
  const lastKey = keys.pop();
  if (!lastKey) return;
  let current = obj;
  for (const key of keys) {
    if (!current[key] || typeof current[key] !== "object") {
      current[key] = {};
    }
    current = current[key];
  }
  current[lastKey] = value;
}

// src/opencode-config.ts
var OPENCODE_LEGACY_CONFIG_KEY = "mcp";
var OPENCODE_NATIVE_CONFIG_KEY = "mcp.servers";
function isJsonObject(value) {
  return !!value && typeof value === "object" && !Array.isArray(value);
}
function isEnablementOnly(value) {
  return (typeof value.enabled === "boolean" || typeof value.disabled === "boolean") && value.type === void 0 && typeof value.url !== "string" && typeof value.command !== "string" && !Array.isArray(value.command);
}
function isOpenCodeServerEntry(value) {
  if (!isJsonObject(value)) {
    return false;
  }
  if (value.type === "local" || value.type === "remote") {
    return true;
  }
  if (typeof value.url === "string" && value.url.length > 0) {
    return true;
  }
  if (typeof value.command === "string" && value.command.length > 0) {
    return true;
  }
  if (Array.isArray(value.command) && value.command.some((part) => typeof part === "string")) {
    return true;
  }
  return isEnablementOnly(value);
}
function isTimeoutMetadata(value) {
  if (typeof value === "number") {
    return true;
  }
  if (!isJsonObject(value) || isOpenCodeServerEntry(value)) {
    return false;
  }
  return "startup" in value || "catalog" in value || "execution" in value || "request" in value;
}
function parseOpenCodeJsonc(configPath) {
  if (!existsSync2(configPath)) {
    return {};
  }
  const text = readFileSync2(configPath, "utf-8");
  const errors = [];
  const parsed = jsonc2.parse(text, errors, {
    allowTrailingComma: true
  });
  if (parsed === void 0 && jsonc2.stripComments(text).trim() === "" && errors.every((error) => error.error === jsonc2.ParseErrorCode.ValueExpected)) {
    return {};
  }
  if (errors.length > 0) {
    throw new Error(`Invalid JSON in ${configPath}`);
  }
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error(`${configPath} must be a JSON object`);
  }
  return parsed;
}
function classifyOpenCodeDocument(document, configPath) {
  if (!("mcp" in document)) {
    return { nativeMap: null, legacy: {}, timeoutIsMetadata: false };
  }
  const mcp = document.mcp;
  if (!isJsonObject(mcp)) {
    throw new Error(`${configPath} mcp must be a JSON object`);
  }
  let nativeMap = null;
  if ("servers" in mcp) {
    const servers = mcp.servers;
    if (!isJsonObject(servers)) {
      throw new Error(`${configPath} mcp.servers must be a JSON object`);
    }
    if (!isOpenCodeServerEntry(servers)) {
      nativeMap = servers;
    }
  }
  const legacy = {};
  for (const [name, value] of Object.entries(mcp)) {
    if (name === "servers" && nativeMap) {
      continue;
    }
    if (name === "timeout" && isTimeoutMetadata(value)) {
      continue;
    }
    if (isOpenCodeServerEntry(value)) {
      legacy[name] = value;
    }
  }
  return {
    nativeMap,
    legacy,
    timeoutIsMetadata: isTimeoutMetadata(mcp.timeout)
  };
}
function readOpenCodeLayout(configPath) {
  return classifyOpenCodeDocument(parseOpenCodeJsonc(configPath), configPath);
}
function listedNativeServers(nativeMap) {
  if (!nativeMap) {
    return {};
  }
  const listed = {};
  for (const [name, value] of Object.entries(nativeMap)) {
    if (isOpenCodeServerEntry(value)) {
      listed[name] = value;
    }
  }
  return listed;
}
function listOpenCodeServers(configPath) {
  const layout = readOpenCodeLayout(configPath);
  const nativeListed = listedNativeServers(layout.nativeMap);
  const names = /* @__PURE__ */ new Set([
    ...Object.keys(layout.legacy),
    ...Object.keys(layout.nativeMap ?? {})
  ]);
  const servers = [];
  for (const serverName of names) {
    if (Object.hasOwn(nativeListed, serverName)) {
      const nativeEntry = nativeListed[serverName];
      if (nativeEntry) {
        servers.push({
          serverName,
          config: nativeEntry,
          configKey: OPENCODE_NATIVE_CONFIG_KEY
        });
      }
      continue;
    }
    if (layout.nativeMap && Object.hasOwn(layout.nativeMap, serverName)) {
      continue;
    }
    const legacyEntry = layout.legacy[serverName];
    if (legacyEntry) {
      servers.push({
        serverName,
        config: legacyEntry,
        configKey: OPENCODE_LEGACY_CONFIG_KEY
      });
    }
  }
  return servers;
}
function slotHoldsNonServer(mcp, name) {
  if (!mcp || !(name in mcp)) {
    return false;
  }
  return !isOpenCodeServerEntry(mcp[name]);
}
function resolveOpenCodeUpsertKey(configPath, serverName) {
  const document = parseOpenCodeJsonc(configPath);
  const layout = classifyOpenCodeDocument(document, configPath);
  if (layout.nativeMap && Object.hasOwn(layout.nativeMap, serverName)) {
    return OPENCODE_NATIVE_CONFIG_KEY;
  }
  if (Object.hasOwn(layout.legacy, serverName)) {
    return OPENCODE_LEGACY_CONFIG_KEY;
  }
  if (layout.nativeMap && Object.keys(layout.legacy).length === 0) {
    return OPENCODE_NATIVE_CONFIG_KEY;
  }
  const mcp = isJsonObject(document.mcp) ? document.mcp : void 0;
  const collidingMetadata = (serverName === "servers" || serverName === "timeout") && slotHoldsNonServer(mcp, serverName);
  if (collidingMetadata) {
    if (layout.nativeMap) {
      return OPENCODE_NATIVE_CONFIG_KEY;
    }
    throw new Error(
      `${configPath} already uses mcp.${serverName} for OpenCode settings. Rename the server or move that setting before installing "${serverName}".`
    );
  }
  return OPENCODE_LEGACY_CONFIG_KEY;
}
var OAUTH_LEGACY_TO_NATIVE = {
  clientId: "client_id",
  clientSecret: "client_secret",
  callbackPort: "callback_port",
  redirectUri: "redirect_uri"
};
var OAUTH_NATIVE_TO_LEGACY = {
  client_id: "clientId",
  client_secret: "clientSecret",
  callback_port: "callbackPort",
  redirect_uri: "redirectUri"
};
function renameOAuthFields(oauth, names) {
  const next = {};
  const targetNames = new Set(Object.values(names));
  for (const [key, value] of Object.entries(oauth)) {
    const mapped = names[key];
    if (mapped) {
      if (!Object.hasOwn(next, mapped)) {
        next[mapped] = value;
      }
      continue;
    }
    if (targetNames.has(key) && Object.hasOwn(next, key)) {
      continue;
    }
    next[key] = value;
  }
  return next;
}
function translateTimeout(timeout, toNative) {
  if (toNative) {
    if (typeof timeout === "number") {
      return { request: timeout };
    }
    if (isJsonObject(timeout)) {
      return timeout;
    }
    return void 0;
  }
  if (typeof timeout === "number") {
    return timeout;
  }
  if (isJsonObject(timeout) && typeof timeout.request === "number") {
    return timeout.request;
  }
  return void 0;
}
function translateOpenCodeEntry(config, fromKey, toKey) {
  const next = { ...config };
  if (fromKey === toKey) {
    return next;
  }
  const toNative = toKey === OPENCODE_NATIVE_CONFIG_KEY;
  if (toNative) {
    const disabled = next.disabled === true || next.enabled === false;
    delete next.enabled;
    delete next.disabled;
    if (disabled) {
      next.disabled = true;
    }
  } else {
    if (next.disabled === true) {
      next.enabled = false;
    } else if (typeof next.enabled !== "boolean") {
      next.enabled = true;
    }
    delete next.disabled;
  }
  if ("timeout" in next) {
    const timeout = translateTimeout(next.timeout, toNative);
    if (timeout === void 0) {
      delete next.timeout;
    } else {
      next.timeout = timeout;
    }
  }
  if (isJsonObject(next.oauth)) {
    next.oauth = renameOAuthFields(
      next.oauth,
      toNative ? OAUTH_LEGACY_TO_NATIVE : OAUTH_NATIVE_TO_LEGACY
    );
  }
  return next;
}
function resolveOpenCodeRelocateKey(configPath, newName, sourceKey) {
  const document = parseOpenCodeJsonc(configPath);
  const layout = classifyOpenCodeDocument(document, configPath);
  const mcp = isJsonObject(document.mcp) ? document.mcp : void 0;
  const collidingMetadata = (newName === "servers" || newName === "timeout") && slotHoldsNonServer(mcp, newName);
  if (!collidingMetadata) {
    return sourceKey;
  }
  if (layout.nativeMap) {
    return OPENCODE_NATIVE_CONFIG_KEY;
  }
  throw new Error(
    `${configPath} already uses mcp.${newName} for OpenCode settings. Rename the server or move that setting before using "${newName}".`
  );
}
function openCodeServerIdentity(config) {
  if (typeof config.url === "string" && config.url.length > 0) {
    return config.url;
  }
  if (typeof config.command === "string" && config.command.length > 0) {
    return config.command;
  }
  if (Array.isArray(config.command)) {
    const parts = config.command.filter(
      (part) => typeof part === "string"
    );
    if (parts.length > 0) {
      return parts.join(" ");
    }
  }
  return "";
}
function relocateOpenCodeServer(configPath, oldName, newName) {
  if (oldName === newName) {
    return;
  }
  const listed = listOpenCodeServers(configPath);
  const existing = listed.find((entry) => entry.serverName === oldName);
  if (!existing) {
    throw new Error(`${configPath} has no OpenCode server named "${oldName}"`);
  }
  const dest = listed.find((entry) => entry.serverName === newName);
  if (dest) {
    const sourceId = openCodeServerIdentity(existing.config);
    const destId = openCodeServerIdentity(dest.config);
    if (sourceId && destId && sourceId === destId) {
      removeOpenCodeServer(configPath, oldName);
      return;
    }
    throw new Error(
      `${configPath} already has an OpenCode server named "${newName}". Rename that server first.`
    );
  }
  const destKey = resolveOpenCodeRelocateKey(
    configPath,
    newName,
    existing.configKey
  );
  const written = translateOpenCodeEntry(
    existing.config,
    existing.configKey,
    destKey
  );
  const document = {};
  setNestedValue(document, destKey, { [newName]: written });
  writeJsonConfig(configPath, document, destKey);
  removeOpenCodeServer(configPath, oldName);
}
function removeOpenCodeServer(configPath, serverName) {
  if (!existsSync2(configPath)) {
    return false;
  }
  const layout = readOpenCodeLayout(configPath);
  const inNative = !!(layout.nativeMap && Object.hasOwn(layout.nativeMap, serverName));
  const inLegacy = Object.hasOwn(layout.legacy, serverName);
  if (!inNative && !inLegacy) {
    return false;
  }
  if (inNative) {
    removeJsonConfigKey(configPath, OPENCODE_NATIVE_CONFIG_KEY, serverName);
  }
  if (inLegacy) {
    removeJsonConfigKey(configPath, OPENCODE_LEGACY_CONFIG_KEY, serverName);
  }
  return true;
}

// src/agents.ts
import * as p from "@clack/prompts";
import { homedir as homedir2 } from "os";
import { basename, dirname as dirname3, join as join2 } from "path";
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
import * as jsonc3 from "jsonc-parser";
var home = homedir2();
var defaultGrokHome = join2(home, ".grok");
function getGrokHome() {
  return process.env.GROK_HOME || defaultGrokHome;
}
var copilotHome = process.env.COPILOT_HOME || join2(home, ".copilot");
function getKimiCodeHome() {
  return process.env.KIMI_CODE_HOME || join2(home, ".kimi-code");
}
function getJunieHome() {
  return process.env.JUNIE_HOME || join2(home, ".junie");
}
function expandHermesHome(raw) {
  const expanded = raw.replace(
    /\$(\w+)|\$\{(\w+)\}|%(\w+)%/g,
    (match, bare, braced, percent) => {
      if (percent && process.platform !== "win32") {
        return match;
      }
      return process.env[bare ?? braced ?? percent ?? ""] ?? match;
    }
  );
  return expanded === "~" || /^~[\\/]/.test(expanded) ? join2(home, expanded.slice(1)) : expanded;
}
function getHermesRoot() {
  if (process.env.HERMES_HOME?.trim()) {
    return expandHermesHome(process.env.HERMES_HOME.trim());
  }
  if (process.platform === "win32") {
    return join2(
      process.env.LOCALAPPDATA || join2(home, "AppData", "Local"),
      "hermes"
    );
  }
  return join2(home, ".hermes");
}
var HERMES_PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,63}$/;
function getHermesHome() {
  const root = getHermesRoot();
  const activeProfilePath = join2(root, "active_profile");
  if (basename(dirname3(root)) === "profiles" || !existsSync3(activeProfilePath)) {
    return root;
  }
  let activeProfile;
  try {
    activeProfile = readFileSync3(activeProfilePath, "utf-8").trim();
  } catch (error) {
    throw new Error(
      `Could not read the Hermes profile selection at ${activeProfilePath}: ${error instanceof Error ? error.message : String(error)}`
    );
  }
  const profile = activeProfile.toLowerCase();
  if (!profile || profile === "default") {
    return root;
  }
  if (!HERMES_PROFILE_NAME.test(profile)) {
    throw new Error(
      `The Hermes profile selection at ${activeProfilePath} names "${activeProfile}", which is not a valid profile name.`
    );
  }
  return join2(root, "profiles", profile);
}
function getMastraCodeDir() {
  return join2(home, ".mastracode");
}
function getPiAgentDir() {
  return process.env.PI_CODING_AGENT_DIR || join2(home, ".pi", "agent");
}
function getKiloConfigDir() {
  return join2(process.env.XDG_CONFIG_HOME || join2(home, ".config"), "kilo");
}
function shortenPath(fullPath) {
  if (fullPath.startsWith(home)) {
    return fullPath.replace(home, "~");
  }
  return fullPath;
}
function getPlatformPaths() {
  const platform = process.platform;
  if (platform === "win32") {
    const appData = process.env.APPDATA || join2(home, "AppData", "Roaming");
    return {
      appSupport: appData,
      vscodePath: join2(appData, "Code", "User"),
      gooseConfigPath: join2(appData, "Block", "goose", "config", "config.yaml")
    };
  } else if (platform === "darwin") {
    return {
      appSupport: join2(home, "Library", "Application Support"),
      vscodePath: join2(home, "Library", "Application Support", "Code", "User"),
      gooseConfigPath: join2(home, ".config", "goose", "config.yaml")
    };
  } else {
    const configDir = process.env.XDG_CONFIG_HOME || join2(home, ".config");
    return {
      appSupport: configDir,
      vscodePath: join2(configDir, "Code", "User"),
      gooseConfigPath: join2(configDir, "goose", "config.yaml")
    };
  }
}
var { appSupport, vscodePath, gooseConfigPath } = getPlatformPaths();
var antigravityConfigPath = join2(
  home,
  ".gemini",
  "config",
  "mcp_config.json"
);
var clineCliConfigPath = join2(
  process.env.CLINE_DIR || join2(home, ".cline"),
  "data",
  "settings",
  "cline_mcp_settings.json"
);
var clineExtensionConfigPath = join2(
  vscodePath,
  "globalStorage",
  "saoudrizwan.claude-dev",
  "settings",
  "cline_mcp_settings.json"
);
var kiloVscodeStoragePath = join2(
  vscodePath,
  "globalStorage",
  "kilocode.kilo-code"
);
var windsurfConfigPath = join2(
  home,
  ".codeium",
  "windsurf",
  "mcp_config.json"
);
function buildStandardRemote(config) {
  const remote = {};
  if (config.type) remote.type = config.type;
  if (config.url) remote.url = config.url;
  if (config.headers && Object.keys(config.headers).length > 0) {
    remote.headers = config.headers;
  }
  if (typeof config.timeout === "number") {
    remote.timeout = config.timeout;
  }
  return remote;
}
function buildStandardLocal(config) {
  const local = {
    command: config.command,
    args: config.args || []
  };
  if (config.env && Object.keys(config.env).length > 0) {
    local.env = config.env;
  }
  return local;
}
function transformStandardConfig(_serverName, config) {
  return config.url ? buildStandardRemote(config) : buildStandardLocal(config);
}
function transformGeminiConfig(_serverName, config) {
  if (!config.url) {
    return buildStandardLocal(config);
  }
  const remote = buildStandardRemote(config);
  if (config.oauthScopes && config.oauthScopes.length > 0) {
    remote.oauth = { scopes: config.oauthScopes };
  }
  return remote;
}
function transformGooseConfig(serverName, config) {
  if (config.url) {
    const gooseType = config.type === "sse" ? "sse" : "streamable_http";
    return {
      name: serverName,
      description: "",
      type: gooseType,
      uri: config.url,
      headers: config.headers || {},
      enabled: true,
      timeout: 300
    };
  }
  return {
    name: serverName,
    description: "",
    cmd: config.command,
    args: config.args || [],
    enabled: true,
    envs: config.env || {},
    type: "stdio",
    timeout: 300
  };
}
function transformZedConfig(_serverName, config) {
  if (config.url) {
    return {
      source: "custom",
      type: config.type || "http",
      url: config.url,
      headers: config.headers || {}
    };
  }
  return {
    source: "custom",
    command: config.command,
    args: config.args || [],
    env: config.env || {}
  };
}
function transformOpenCodeConfig(_serverName, config, context) {
  const native = context?.configKey === OPENCODE_NATIVE_CONFIG_KEY;
  if (config.url) {
    const remote = {
      type: "remote",
      url: config.url,
      headers: config.headers
    };
    if (!native) {
      remote.enabled = true;
    }
    return remote;
  }
  const local = {
    type: "local",
    command: [config.command, ...config.args || []],
    environment: config.env || {}
  };
  if (!native) {
    local.enabled = true;
  }
  return local;
}
function transformKiloCodeConfig(serverName, config) {
  const entry = transformOpenCodeConfig(serverName, config);
  if (typeof config.timeout === "number") {
    entry.timeout = config.timeout;
  }
  return entry;
}
var KIMI_MAX_TIMEOUT_MS = 2147483647;
function kimiToolTimeoutMs(timeout) {
  if (typeof timeout !== "number" || !Number.isInteger(timeout)) {
    return void 0;
  }
  if (timeout < 1 || timeout > KIMI_MAX_TIMEOUT_MS) {
    return void 0;
  }
  return timeout;
}
function transformKimiCodeConfig(_serverName, config) {
  let entry;
  if (config.url) {
    entry = {
      transport: config.type === "sse" ? "sse" : "http",
      url: config.url
    };
    if (config.headers && Object.keys(config.headers).length > 0) {
      entry.headers = config.headers;
    }
  } else {
    entry = {
      transport: "stdio",
      command: config.command,
      args: config.args || []
    };
    if (config.env && Object.keys(config.env).length > 0) {
      entry.env = config.env;
    }
  }
  const toolTimeoutMs = kimiToolTimeoutMs(config.timeout);
  if (toolTimeoutMs !== void 0) {
    entry.toolTimeoutMs = toolTimeoutMs;
  }
  return entry;
}
function applyCodexApproval(target, autoApproveTools) {
  if (!autoApproveTools) return target;
  if (autoApproveTools.length === 0) {
    target.default_tools_approval_mode = "approve";
    return target;
  }
  target.tools = Object.fromEntries(
    autoApproveTools.map((tool) => [tool, { approval_mode: "approve" }])
  );
  return target;
}
function transformCodexConfig(_serverName, config) {
  if (config.url) {
    const remoteConfig = {
      type: config.type || "http",
      url: config.url
    };
    if (config.headers && Object.keys(config.headers).length > 0) {
      remoteConfig.http_headers = config.headers;
    }
    return applyCodexApproval(remoteConfig, config.autoApproveTools);
  }
  return applyCodexApproval(
    {
      command: config.command,
      args: config.args || [],
      env: config.env
    },
    config.autoApproveTools
  );
}
function transformGrokBuildConfig(_serverName, config) {
  if (config.url) {
    const remoteConfig = {
      url: config.url
    };
    if (config.headers && Object.keys(config.headers).length > 0) {
      remoteConfig.headers = config.headers;
    }
    if (typeof config.timeout === "number") {
      remoteConfig.tool_timeout_sec = config.timeout / 1e3;
    }
    return remoteConfig;
  }
  return buildStandardLocal(config);
}
function transformKiroCliConfig(_serverName, config) {
  if (!config.url) {
    return buildStandardLocal(config);
  }
  const remoteConfig = {
    url: config.url
  };
  if (config.headers && Object.keys(config.headers).length > 0) {
    remoteConfig.headers = config.headers;
  }
  if (typeof config.timeout === "number") {
    remoteConfig.timeout = config.timeout;
  }
  return remoteConfig;
}
function transformJunieConfig(_serverName, config) {
  if (!config.url) {
    return buildStandardLocal(config);
  }
  const remote = { url: config.url };
  if (config.headers && Object.keys(config.headers).length > 0) {
    remote.headers = config.headers;
  }
  return remote;
}
function transformFactoryDroidConfig(_serverName, config) {
  if (config.url) {
    return {
      ...buildStandardRemote(config),
      type: config.type === "sse" ? "sse" : "http"
    };
  }
  const local = {
    type: "stdio",
    ...buildStandardLocal(config)
  };
  if (typeof config.timeout === "number") {
    local.timeout = config.timeout;
  }
  return local;
}
function transformHermesConfig(_serverName, config) {
  let entry;
  if (config.url) {
    entry = { url: config.url };
    if (config.headers && Object.keys(config.headers).length > 0) {
      entry.headers = config.headers;
    }
    if (config.type === "sse") {
      entry.transport = "sse";
    }
  } else {
    entry = buildStandardLocal(config);
  }
  if (typeof config.timeout === "number") {
    entry.timeout = config.timeout / 1e3;
  }
  return entry;
}
function transformPiConfig(_serverName, config) {
  if (config.url) {
    const remote = { url: config.url };
    if (config.headers && Object.keys(config.headers).length > 0) {
      remote.headers = config.headers;
    }
    if (typeof config.timeout === "number") {
      remote.requestTimeoutMs = config.timeout;
    }
    return remote;
  }
  const local = buildStandardLocal(config);
  if (typeof config.timeout === "number") {
    local.requestTimeoutMs = config.timeout;
  }
  return local;
}
function transformMastraCodeConfig(_serverName, config) {
  if (!config.url) {
    return buildStandardLocal(config);
  }
  const remote = { url: config.url };
  if (config.headers && Object.keys(config.headers).length > 0) {
    remote.headers = config.headers;
  }
  if (config.oauthScopes && config.oauthScopes.length > 0) {
    remote.oauth = { scopes: config.oauthScopes };
  }
  return remote;
}
function headersWithoutAuthorization(headers) {
  const next = {};
  for (const [name, value] of Object.entries(headers)) {
    if (name.toLowerCase() !== "authorization") {
      next[name] = value;
    }
  }
  return next;
}
function hasAuthorizationHeader(headers) {
  return Object.keys(headers).some(
    (name) => name.toLowerCase() === "authorization"
  );
}
function transformFxConfig(_serverName, config) {
  if (config.url) {
    const bearerTokenEnv = resolvedBearerTokenEnv(config);
    const headers = config.headers ? bearerTokenEnv ? headersWithoutAuthorization(config.headers) : config.headers : void 0;
    if (headers && hasAuthorizationHeader(headers)) {
      throw new Error(
        "fx rejects a literal Authorization header. Pass --bearer-token-env <name>, use a non-Authorization header, or set header_env or oauth in ~/.fx/mcp.json."
      );
    }
    const entry2 = {
      type: config.type === "sse" ? "sse" : "http",
      url: config.url,
      enabled: true
    };
    if (headers && Object.keys(headers).length > 0) {
      entry2.headers = headers;
    }
    if (bearerTokenEnv) {
      entry2.bearer_token_env = bearerTokenEnv;
    }
    return entry2;
  }
  const entry = {
    type: "local",
    command: [config.command, ...config.args || []],
    enabled: true
  };
  if (config.env && Object.keys(config.env).length > 0) {
    entry.environment = config.env;
  }
  return entry;
}
function transformCursorConfig(_serverName, config) {
  if (config.url) {
    const remoteConfig = {
      url: config.url
    };
    if (config.headers && Object.keys(config.headers).length > 0) {
      remoteConfig.headers = config.headers;
    }
    if (config.oauthScopes && config.oauthScopes.length > 0) {
      remoteConfig.auth = { scopes: config.oauthScopes };
    }
    return remoteConfig;
  }
  return buildStandardLocal(config);
}
function transformClineConfig(_serverName, config) {
  if (config.url) {
    const remoteConfig = {
      url: config.url,
      type: config.type === "sse" ? "sse" : "streamableHttp",
      disabled: false
    };
    if (config.headers && Object.keys(config.headers).length > 0) {
      remoteConfig.headers = config.headers;
    }
    return remoteConfig;
  }
  const localConfig = {
    command: config.command,
    args: config.args || [],
    disabled: false
  };
  if (config.env && Object.keys(config.env).length > 0) {
    localConfig.env = config.env;
  }
  return localConfig;
}
function transformAntigravityConfig(_serverName, config) {
  if (config.url) {
    const remoteConfig = {
      serverUrl: config.url
    };
    if (config.headers && Object.keys(config.headers).length > 0) {
      remoteConfig.headers = config.headers;
    }
    return remoteConfig;
  }
  const localConfig = {
    command: config.command,
    args: config.args || []
  };
  if (config.env && Object.keys(config.env).length > 0) {
    localConfig.env = config.env;
  }
  return localConfig;
}
function looksLikeServerEntry2(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const entry = value;
  return typeof entry.command === "string" || typeof entry.url === "string" || typeof entry.type === "string";
}
function isVscodeServersWrapper(obj) {
  if (obj.mcpServers !== void 0) return false;
  if (!obj.servers || typeof obj.servers !== "object" || Array.isArray(obj.servers)) {
    return false;
  }
  if (looksLikeServerEntry2(obj.servers)) return false;
  return Object.values(obj.servers).some(
    looksLikeServerEntry2
  );
}
function parseCopilotProjectFile(configPath) {
  const errors = [];
  const parsed = jsonc3.parse(
    readFileSync3(configPath, "utf-8"),
    errors
  );
  if (parsed === void 0 && errors.every((error) => error.error === jsonc3.ParseErrorCode.ValueExpected)) {
    return {};
  }
  if (errors.length > 0) {
    throw new Error(`Invalid JSON in ${configPath}`);
  }
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error(`${configPath} must be a JSON object`);
  }
  return parsed;
}
function githubCopilotCliProjectConfigKey(configPath) {
  if (!existsSync3(configPath)) {
    return "mcpServers";
  }
  const obj = parseCopilotProjectFile(configPath);
  if (isVscodeServersWrapper(obj)) {
    throw new Error(
      `${configPath} uses VS Code's "servers" key. Copilot CLI reads mcpServers in .mcp.json or .github/mcp.json; use --agent vscode for .vscode/mcp.json.`
    );
  }
  if (isBareServerMap(obj)) {
    return "";
  }
  return "mcpServers";
}
function transformGitHubCopilotCliConfig(_serverName, config, context) {
  const includeTools = !context?.local;
  if (config.url) {
    const remoteConfig = {
      type: config.type || "http",
      url: config.url
    };
    if (includeTools) {
      remoteConfig.tools = ["*"];
    }
    if (config.headers && Object.keys(config.headers).length > 0) {
      remoteConfig.headers = config.headers;
    }
    return remoteConfig;
  }
  const stdioConfig = {
    type: "stdio",
    command: config.command,
    args: config.args || []
  };
  if (includeTools) {
    stdioConfig.tools = ["*"];
  }
  if (config.env && Object.keys(config.env).length > 0) {
    stdioConfig.env = config.env;
  }
  return stdioConfig;
}
function resolveGitHubCopilotCliConfigPath(agent, options) {
  if (!options.local) {
    return agent.configPath;
  }
  const mcpJson = join2(options.cwd, ".mcp.json");
  const githubMcpJson = join2(options.cwd, ".github", "mcp.json");
  if (existsSync3(mcpJson)) return mcpJson;
  if (existsSync3(githubMcpJson)) return githubMcpJson;
  return mcpJson;
}
function resolveMcporterConfigPath(agent, options) {
  if (options.local && agent.localConfigPath) {
    return join2(options.cwd, agent.localConfigPath);
  }
  const globalJsonPath = agent.configPath;
  const globalJsoncPath = join2(dirname3(globalJsonPath), "mcporter.jsonc");
  if (existsSync3(globalJsonPath)) {
    return globalJsonPath;
  }
  if (existsSync3(globalJsoncPath)) {
    return globalJsoncPath;
  }
  return globalJsonPath;
}
function resolveOpenCodeConfigPath(agent, options) {
  if (options.local) {
    const candidates2 = [
      join2(options.cwd, "opencode.jsonc"),
      join2(options.cwd, "opencode.json"),
      join2(options.cwd, ".opencode", "opencode.jsonc"),
      join2(options.cwd, ".opencode", "opencode.json")
    ];
    for (const path of candidates2) {
      if (existsSync3(path)) return path;
    }
    return join2(options.cwd, "opencode.jsonc");
  }
  const candidates = [
    join2(home, ".config", "opencode", "opencode.jsonc"),
    join2(home, ".config", "opencode", "opencode.json")
  ];
  for (const path of candidates) {
    if (existsSync3(path)) return path;
  }
  return join2(home, ".config", "opencode", "opencode.jsonc");
}
function resolveGrokBuildConfigPath(agent, options) {
  if (options.local && agent.localConfigPath) {
    return join2(options.cwd, agent.localConfigPath);
  }
  return join2(getGrokHome(), "config.toml");
}
function resolveKimiCodeConfigPath(agent, options) {
  if (options.local && agent.localConfigPath) {
    return join2(options.cwd, agent.localConfigPath);
  }
  return join2(getKimiCodeHome(), "mcp.json");
}
function resolveJunieConfigPath(agent, options) {
  if (options.local && agent.localConfigPath) {
    return join2(options.cwd, agent.localConfigPath);
  }
  return join2(getJunieHome(), "mcp", "mcp.json");
}
function resolvePiConfigPath(agent, options) {
  if (options.local && agent.localConfigPath) {
    return join2(options.cwd, agent.localConfigPath);
  }
  return join2(getPiAgentDir(), "mcp.json");
}
function resolveKiloCodeConfigPath(_agent, options) {
  const baseDir = options.local ? options.cwd : getKiloConfigDir();
  const candidates = options.local ? [
    join2(".kilo", "kilo.jsonc"),
    join2(".kilo", "kilo.json"),
    join2(".kilocode", "kilo.jsonc"),
    join2(".kilocode", "kilo.json"),
    "kilo.jsonc"
  ] : ["kilo.jsonc"];
  for (const candidate of candidates) {
    const candidatePath = join2(baseDir, candidate);
    if (existsSync3(candidatePath)) {
      return candidatePath;
    }
  }
  return join2(baseDir, "kilo.json");
}
var agents = {
  antigravity: {
    name: "antigravity",
    displayName: "Antigravity",
    configPath: antigravityConfigPath,
    projectDetectPaths: [],
    // Global only - no project support
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".gemini", "config"));
    },
    transformConfig: transformAntigravityConfig
  },
  cline: {
    name: "cline",
    displayName: "Cline VSCode Extension",
    configPath: clineExtensionConfigPath,
    projectDetectPaths: [],
    // Global only - no project support
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(dirname3(clineExtensionConfigPath));
    },
    transformConfig: transformClineConfig
  },
  "cline-cli": {
    name: "cline-cli",
    displayName: "Cline CLI",
    configPath: clineCliConfigPath,
    projectDetectPaths: [],
    // Global only - no project support
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(dirname3(clineCliConfigPath));
    },
    transformConfig: transformClineConfig
  },
  "claude-code": {
    name: "claude-code",
    displayName: "Claude Code",
    configPath: join2(home, ".claude.json"),
    localConfigPath: ".mcp.json",
    projectDetectPaths: [".mcp.json", ".claude"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout", "autoApprove"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".claude"));
    },
    transformConfig: transformStandardConfig
  },
  "claude-desktop": {
    name: "claude-desktop",
    displayName: "Claude Desktop",
    configPath: join2(appSupport, "Claude", "claude_desktop_config.json"),
    projectDetectPaths: [],
    // Global only - no project support
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio"],
    supportedFields: [],
    unsupportedTransportMessage: "Claude Desktop only supports local (stdio) servers via its config file. Add remote servers through Settings \u2192 Connectors in the app instead.",
    detectGlobalInstall: async () => {
      return existsSync3(join2(appSupport, "Claude"));
    },
    transformConfig: transformStandardConfig
  },
  codex: {
    name: "codex",
    displayName: "Codex",
    configPath: join2(
      process.env.CODEX_HOME || join2(home, ".codex"),
      "config.toml"
    ),
    localConfigPath: ".codex/config.toml",
    projectDetectPaths: [".codex"],
    configKey: "mcp_servers",
    format: "toml",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["autoApprove"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".codex"));
    },
    transformConfig: transformCodexConfig
  },
  cursor: {
    name: "cursor",
    displayName: "Cursor",
    configPath: join2(home, ".cursor", "mcp.json"),
    localConfigPath: ".cursor/mcp.json",
    projectDetectPaths: [".cursor"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["scopes"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".cursor"));
    },
    transformConfig: transformCursorConfig
  },
  "factory-droid": {
    name: "factory-droid",
    displayName: "Factory Droid",
    configPath: join2(home, ".factory", "mcp.json"),
    localConfigPath: ".factory/mcp.json",
    projectDetectPaths: [".factory"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".factory"));
    },
    transformConfig: transformFactoryDroidConfig
  },
  fx: {
    name: "fx",
    displayName: "fx",
    // fx reads MCP config only from its trusted global profile:
    // https://fx.sh/docs/capabilities/mcp
    configPath: join2(home, ".fx", "mcp.json"),
    projectDetectPaths: [],
    configKey: "mcp",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["bearerTokenEnv"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".fx"));
    },
    transformConfig: transformFxConfig
  },
  "gemini-cli": {
    name: "gemini-cli",
    displayName: "Gemini CLI",
    configPath: join2(home, ".gemini", "settings.json"),
    localConfigPath: ".gemini/settings.json",
    projectDetectPaths: [".gemini"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout", "scopes"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".gemini"));
    },
    transformConfig: transformGeminiConfig
  },
  goose: {
    name: "goose",
    displayName: "Goose",
    configPath: gooseConfigPath,
    projectDetectPaths: [],
    // Global only - no project support
    configKey: "extensions",
    format: "yaml",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(gooseConfigPath);
    },
    transformConfig: transformGooseConfig
  },
  "github-copilot-cli": {
    name: "github-copilot-cli",
    displayName: "GitHub Copilot CLI",
    configPath: join2(copilotHome, "mcp-config.json"),
    localConfigPath: ".mcp.json",
    projectDetectPaths: [".mcp.json", ".github/mcp.json"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(copilotHome);
    },
    resolveConfigPath: resolveGitHubCopilotCliConfigPath,
    transformConfig: transformGitHubCopilotCliConfig
  },
  "grok-build": {
    name: "grok-build",
    displayName: "Grok Build",
    configPath: join2(getGrokHome(), "config.toml"),
    localConfigPath: ".grok/config.toml",
    projectDetectPaths: [".grok"],
    configKey: "mcp_servers",
    format: "toml",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(getGrokHome());
    },
    resolveConfigPath: resolveGrokBuildConfigPath,
    transformConfig: transformGrokBuildConfig
  },
  hermes: {
    name: "hermes",
    displayName: "Hermes Agent",
    configPath: join2(getHermesRoot(), "config.yaml"),
    projectDetectPaths: [],
    configKey: "mcp_servers",
    format: "yaml-1.1",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(getHermesRoot());
    },
    resolveConfigPath: () => join2(getHermesHome(), "config.yaml"),
    transformConfig: transformHermesConfig
  },
  junie: {
    name: "junie",
    displayName: "Junie",
    configPath: join2(getJunieHome(), "mcp", "mcp.json"),
    localConfigPath: ".junie/mcp/mcp.json",
    projectDetectPaths: [".junie"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(getJunieHome());
    },
    resolveConfigPath: resolveJunieConfigPath,
    transformConfig: transformJunieConfig
  },
  "kilo-code": {
    name: "kilo-code",
    displayName: "Kilo Code",
    configPath: join2(getKiloConfigDir(), "kilo.json"),
    localConfigPath: "kilo.json",
    projectDetectPaths: [".kilo", ".kilocode", "kilo.json", "kilo.jsonc"],
    configKey: "mcp",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(getKiloConfigDir()) || existsSync3(kiloVscodeStoragePath);
    },
    resolveConfigPath: resolveKiloCodeConfigPath,
    transformConfig: transformKiloCodeConfig
  },
  "kimi-code": {
    name: "kimi-code",
    displayName: "Kimi Code",
    configPath: join2(getKimiCodeHome(), "mcp.json"),
    localConfigPath: ".kimi-code/mcp.json",
    projectDetectPaths: [".kimi-code"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(getKimiCodeHome());
    },
    resolveConfigPath: resolveKimiCodeConfigPath,
    transformConfig: transformKimiCodeConfig
  },
  "kiro-cli": {
    name: "kiro-cli",
    displayName: "Kiro CLI",
    // Shared with the Kiro IDE, which reads the same two files.
    configPath: join2(home, ".kiro", "settings", "mcp.json"),
    localConfigPath: ".kiro/settings/mcp.json",
    projectDetectPaths: [".kiro"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".kiro"));
    },
    transformConfig: transformKiroCliConfig
  },
  mastracode: {
    name: "mastracode",
    displayName: "Mastra Code",
    configPath: join2(getMastraCodeDir(), "mcp.json"),
    localConfigPath: ".mastracode/mcp.json",
    projectDetectPaths: [".mastracode"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["scopes"],
    detectGlobalInstall: async () => {
      return existsSync3(getMastraCodeDir());
    },
    transformConfig: transformMastraCodeConfig
  },
  mcporter: {
    name: "mcporter",
    displayName: "MCPorter",
    configPath: join2(home, ".mcporter", "mcporter.json"),
    localConfigPath: "config/mcporter.json",
    projectDetectPaths: ["config/mcporter.json"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".mcporter"));
    },
    resolveConfigPath: resolveMcporterConfigPath,
    transformConfig: transformStandardConfig
  },
  opencode: {
    name: "opencode",
    displayName: "OpenCode",
    configPath: join2(home, ".config", "opencode", "opencode.jsonc"),
    localConfigPath: "opencode.jsonc",
    projectDetectPaths: ["opencode.jsonc", "opencode.json", ".opencode"],
    configKey: "mcp",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".config", "opencode"));
    },
    resolveConfigPath: resolveOpenCodeConfigPath,
    transformConfig: transformOpenCodeConfig
  },
  pi: {
    name: "pi",
    displayName: "Pi",
    configPath: join2(getPiAgentDir(), "mcp.json"),
    localConfigPath: ".pi/mcp.json",
    projectDetectPaths: [".pi"],
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: ["timeout"],
    detectGlobalInstall: async () => {
      return existsSync3(getPiAgentDir());
    },
    resolveConfigPath: resolvePiConfigPath,
    transformConfig: transformPiConfig
  },
  vscode: {
    name: "vscode",
    displayName: "VS Code",
    configPath: join2(vscodePath, "mcp.json"),
    localConfigPath: ".vscode/mcp.json",
    projectDetectPaths: [".vscode"],
    configKey: "servers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(vscodePath);
    },
    transformConfig: transformStandardConfig
  },
  windsurf: {
    name: "windsurf",
    displayName: "Windsurf",
    configPath: windsurfConfigPath,
    projectDetectPaths: [],
    // Global only - no project support
    configKey: "mcpServers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      return existsSync3(join2(home, ".codeium", "windsurf"));
    },
    transformConfig: transformAntigravityConfig
  },
  zed: {
    name: "zed",
    displayName: "Zed",
    configPath: process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed", "settings.json") : join2(appSupport, "zed", "settings.json"),
    localConfigPath: ".zed/settings.json",
    projectDetectPaths: [".zed"],
    configKey: "context_servers",
    format: "json",
    supportedTransports: ["stdio", "http", "sse"],
    supportedFields: [],
    detectGlobalInstall: async () => {
      const configDir = process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed") : join2(appSupport, "zed");
      return existsSync3(configDir);
    },
    transformConfig: transformZedConfig
  }
};
function getAgentTypes() {
  return Object.keys(agents);
}
function supportsProjectConfig(agentType) {
  return agents[agentType].localConfigPath !== void 0;
}
function getCommonInstallScopes(agentTypes) {
  if (agentTypes.length === 0) return [];
  if (agentTypes.some((agentType) => !supportsProjectConfig(agentType))) {
    return ["global"];
  }
  return ["local", "global"];
}
function getProjectCapableAgents() {
  return Object.keys(agents).filter(
    (type) => supportsProjectConfig(type)
  );
}
function detectProjectAgents(cwd) {
  const dir = cwd || process.cwd();
  const detected = [];
  for (const [type, config] of Object.entries(agents)) {
    if (!config.localConfigPath) continue;
    for (const detectPath of config.projectDetectPaths) {
      if (existsSync3(join2(dir, detectPath))) {
        detected.push(type);
        break;
      }
    }
  }
  return detected;
}
async function detectGlobalAgents() {
  const detected = [];
  for (const [type, config] of Object.entries(agents)) {
    if (await config.detectGlobalInstall()) {
      detected.push(type);
    }
  }
  return detected;
}
function isTransportSupported(agentType, transport) {
  return agents[agentType].supportedTransports.includes(transport);
}
function buildAgentSelectionChoices(options) {
  const { availableAgents, detectedAgents, agentRouting, lastSelected } = options;
  const detectedSet = new Set(detectedAgents);
  const validLastSelected = lastSelected?.filter(
    (agent) => availableAgents.includes(agent) && !detectedSet.has(agent)
  ) ?? [];
  const remainingAgents = availableAgents.filter(
    (agent) => !detectedSet.has(agent) && !validLastSelected.includes(agent)
  );
  const orderedAgents = [
    ...detectedAgents,
    ...validLastSelected,
    ...remainingAgents
  ];
  const choices = orderedAgents.map((agentType) => {
    const routing = agentRouting.get(agentType);
    const baseHint = routing === "local" ? "project" : routing === "global" ? "global" : shortenPath(agents[agentType].configPath);
    const lastSelectedHint = validLastSelected.includes(agentType) ? "selected last time" : "";
    const hint = lastSelectedHint ? `${baseHint} \xB7 ${lastSelectedHint}` : baseHint;
    return {
      value: agentType,
      label: agents[agentType].displayName,
      hint
    };
  });
  return { choices, initialValues: detectedAgents };
}
async function promptForAgents(message, choices, defaultToAll = false) {
  let lastSelected;
  try {
    lastSelected = await getLastSelectedAgents();
  } catch {
  }
  const validAgents = choices.map((c) => c.value);
  let initialValues;
  if (lastSelected && lastSelected.length > 0) {
    initialValues = lastSelected.filter(
      (a) => validAgents.includes(a)
    );
    if (initialValues.length === 0 && defaultToAll) {
      initialValues = validAgents;
    }
  } else {
    initialValues = defaultToAll ? validAgents : [];
  }
  const selected = await p.multiselect({
    message,
    options: choices,
    required: true,
    initialValues
  });
  if (!p.isCancel(selected)) {
    try {
      await saveSelectedAgents(selected);
    } catch {
    }
  }
  return selected;
}
async function selectAgentsInteractive(availableAgents, options) {
  let lastSelected;
  try {
    lastSelected = await getLastSelectedAgents();
  } catch {
  }
  const validLastSelected = lastSelected?.filter(
    (a) => availableAgents.includes(a)
  );
  const selectOptions = [];
  const hasPrevious = validLastSelected && validLastSelected.length > 0;
  if (hasPrevious) {
    const agentNames = validLastSelected.map((a) => agents[a].displayName).join(", ");
    selectOptions.push({
      value: "previous",
      label: "Same as last time",
      hint: agentNames
    });
  }
  selectOptions.push({
    value: "all",
    label: hasPrevious ? "All available agents" : "All available agents",
    hint: `Install to all ${availableAgents.length} available agents`
  });
  selectOptions.push({
    value: "select",
    label: "Select specific agents",
    hint: "Choose which agents to install to"
  });
  const installChoice = await p.select({
    message: "Install to",
    options: selectOptions
  });
  if (p.isCancel(installChoice)) {
    return installChoice;
  }
  if (installChoice === "all") {
    return availableAgents;
  }
  if (installChoice === "previous" && validLastSelected) {
    return validLastSelected;
  }
  const agentChoices = availableAgents.map((agentType) => {
    const localPath = agents[agentType].localConfigPath;
    const hint = options.global ? shortenPath(agents[agentType].configPath) : localPath ?? shortenPath(agents[agentType].configPath);
    return {
      value: agentType,
      label: agents[agentType].displayName,
      hint
    };
  });
  return promptForAgents("Select agents to install to", agentChoices, false);
}

// src/source-parser.ts
function isUrl(input) {
  return input.startsWith("http://") || input.startsWith("https://");
}
function looksLikePath(input) {
  if (input.startsWith("/")) return true;
  if (input.startsWith("~/") || input === "~") return true;
  if (input.startsWith("./") || input.startsWith("../")) return true;
  if (/^[A-Za-z]:[\\/]/.test(input)) return true;
  return false;
}
function isCommand(input) {
  if (looksLikePath(input)) {
    return true;
  }
  if (input.includes(" ")) {
    return true;
  }
  if (input.startsWith("npx ") || input.startsWith("node ") || input.startsWith("python ")) {
    return true;
  }
  return false;
}
function isPackageName(input) {
  if (input.startsWith("@") && input.includes("/")) {
    return true;
  }
  if (/^[a-z0-9][\w.-]*(@[\w.-]+)?$/i.test(input)) {
    return true;
  }
  return false;
}
var commonTlds = /* @__PURE__ */ new Set([
  "com",
  "org",
  "net",
  "io",
  "dev",
  "ai",
  "tech",
  "co",
  "app",
  "cloud",
  "sh",
  "run"
]);
function extractBrandFromHostname(hostname) {
  const parts = hostname.split(".");
  const meaningfulParts = parts.filter((part) => {
    const lower = part.toLowerCase();
    if (commonTlds.has(lower)) return false;
    if (lower === "mcp" || lower === "api" || lower === "www") return false;
    return true;
  });
  if (meaningfulParts.length > 0) {
    return meaningfulParts[0];
  }
  if (parts.length >= 2) {
    return parts[parts.length - 2];
  }
  return "mcp-server";
}
function inferName(input, type) {
  if (type === "remote") {
    try {
      const url = new URL(input);
      return extractBrandFromHostname(url.hostname);
    } catch {
      return "mcp-server";
    }
  }
  if (type === "command") {
    if (looksLikePath(input)) {
      const segments = input.split(/[\\/]/);
      const base = segments[segments.length - 1] || "mcp-server";
      const withoutExt = base.replace(/\.[^.]+$/, "");
      return extractPackageName(withoutExt || base);
    }
    const parts = input.split(" ");
    let startIndex = 0;
    if (parts[0] === "npx" || parts[0] === "node" || parts[0] === "python") {
      startIndex = 1;
    }
    for (let i = startIndex; i < parts.length; i++) {
      const part = parts[i];
      if (part && !part.startsWith("-")) {
        return extractPackageName(part);
      }
    }
    return "mcp-server";
  }
  return extractPackageName(input);
}
function extractPackageName(input) {
  let name = input;
  const atIndex = name.lastIndexOf("@");
  if (atIndex > 0 && !name.startsWith("@")) {
    name = name.slice(0, atIndex);
  } else if (name.startsWith("@") && name.indexOf("@", 1) > 0) {
    const secondAt = name.indexOf("@", 1);
    name = name.slice(0, secondAt);
  }
  if (name.startsWith("@") && name.includes("/")) {
    const parts = name.split("/");
    name = parts[1] || name;
  }
  name = name.replace(/^mcp-server-/, "");
  name = name.replace(/^server-/, "");
  name = name.replace(/-mcp$/, "");
  return name || "mcp-server";
}
function parseSource(input) {
  const trimmed = input.trim();
  if (isUrl(trimmed)) {
    return {
      type: "remote",
      value: trimmed,
      inferredName: inferName(trimmed, "remote")
    };
  }
  if (isCommand(trimmed)) {
    return {
      type: "command",
      value: trimmed,
      inferredName: inferName(trimmed, "command")
    };
  }
  if (isPackageName(trimmed)) {
    return {
      type: "package",
      value: trimmed,
      inferredName: inferName(trimmed, "package")
    };
  }
  return {
    type: "package",
    value: trimmed,
    inferredName: inferName(trimmed, "package")
  };
}
function isRemoteSource(parsed) {
  return parsed.type === "remote";
}

// src/formats/yaml.ts
import { isDeepStrictEqual } from "util";
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
import { dirname as dirname4 } from "path";
import {
  Document,
  isAlias,
  isMap,
  isScalar,
  LineCounter,
  parseDocument
} from "yaml";
var PYYAML_SEXAGESIMAL_INT = /^[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+$/;
function isSexagesimalIntTag(tag) {
  return typeof tag === "object" && !tag.collection && tag.tag === "tag:yaml.org,2002:int" && tag.format === "TIME";
}
function matchPyYamlTags(tags) {
  return tags.map(
    (tag) => isSexagesimalIntTag(tag) ? { ...tag, test: PYYAML_SEXAGESIMAL_INT } : tag
  );
}
function yamlOptions(version) {
  return version === "1.1" ? { version, merge: true, customTags: matchPyYamlTags } : { version, merge: true };
}
function parseYaml(source, filePath, version) {
  const lineCounter = new LineCounter();
  const doc = parseDocument(source, {
    ...yamlOptions(version),
    prettyErrors: false,
    logLevel: "error",
    lineCounter
  });
  const [error] = doc.errors;
  if (error) {
    const { line, col } = lineCounter.linePos(error.pos[0]);
    throw new Error(
      `Invalid YAML in ${filePath} at line ${line}, column ${col} (${error.code}).`
    );
  }
  return doc;
}
function toJSValue(filePath, convert) {
  try {
    return convert();
  } catch (error) {
    const reason = error instanceof ReferenceError ? "an alias could not be resolved" : "it could not be converted to a value";
    throw new Error(`Invalid YAML in ${filePath}: ${reason}.`);
  }
}
function parseYamlValue(source, filePath, version) {
  const doc = parseYaml(source, filePath, version);
  return toJSValue(filePath, () => doc.toJS());
}
function readYamlConfig(filePath, version) {
  if (!existsSync4(filePath)) {
    return {};
  }
  return parseYamlValue(readFileSync4(filePath, "utf-8"), filePath, version) || {};
}
function parseSource2(source, filePath, version) {
  const doc = parseYaml(source, filePath, version);
  if (doc.contents !== null && !isMap(doc.contents)) {
    throw new Error(`${filePath} is not a YAML map.`);
  }
  return doc;
}
function isAliasOrMerge(node) {
  return isAlias(node) || isMap(node) && node.items.some(
    (pair) => isScalar(pair.key) && typeof pair.key.value === "symbol" && pair.key.value.description === "<<"
  );
}
function getServersNode(doc, filePath, configKey) {
  const keyPath = configKey.split(".");
  const path = [
    doc.contents,
    ...keyPath.map((_, i) => doc.getIn(keyPath.slice(0, i + 1), true))
  ];
  const servers = path[path.length - 1];
  if (path.some(isAliasOrMerge)) {
    throw new Error(
      `${configKey} in ${filePath} uses a YAML alias or merge key, which add-mcp does not edit. Change it by hand.`
    );
  }
  if (servers === void 0 || isScalar(servers) && servers.value === null) {
    return doc.hasIn(keyPath) ? null : void 0;
  }
  if (!isMap(servers)) {
    throw new Error(`${configKey} in ${filePath} is not a YAML map.`);
  }
  if (servers.flow && servers.items.length > 0) {
    throw new Error(
      `${configKey} in ${filePath} is a flow-style map, which add-mcp does not edit. Change it by hand.`
    );
  }
  return servers;
}
function lineStart(source, offset) {
  return source.lastIndexOf("\n", offset - 1) + 1;
}
function lineEnd(source, offset) {
  if (offset > 0 && source[offset - 1] === "\n") {
    return offset;
  }
  const newline = source.indexOf("\n", offset);
  return newline === -1 ? source.length : newline + 1;
}
function pairSpan(source, pair) {
  const key = pair.key;
  const value = pair.value;
  const end = value?.range ? value.range[2] : key.range[2];
  return [lineStart(source, key.range[0]), lineEnd(source, end)];
}
function lineEnding(source) {
  return source.includes("\r\n") ? "\r\n" : "\n";
}
function render(value, version, eol, indent, unit = 2) {
  const text = new Document(value, yamlOptions(version)).toString({
    lineWidth: 0,
    indent: unit
  });
  const pad = " ".repeat(indent);
  return text.replace(/^(?=.)/gm, pad).replace(/\n/g, eol);
}
function withTrailingNewline(source, eol) {
  return source === "" || source.endsWith("\n") ? source : source + eol;
}
function serversKeyLine(doc, source, configKey) {
  const keyPath = configKey.split(".");
  const parent = keyPath.length > 1 ? doc.getIn(keyPath.slice(0, -1), true) : doc.contents;
  const pair = parent.items.find(
    (item) => isScalar(item.key) && item.key.value === keyPath[keyPath.length - 1]
  );
  const keyRange = pair.key.range;
  const start = lineStart(source, keyRange[0]);
  const end = lineEnd(source, keyRange[2]);
  const line = source.slice(start, end).replace(
    /:[ \t]*(?:\{[ \t]*\}|~|null|Null|NULL)?[ \t]*(#[^\r\n]*)?(?:\r?\n)?$/,
    (_match, comment) => comment ? `: ${comment}${lineEnding(source)}` : `:${lineEnding(source)}`
  );
  return { start, end, indent: keyRange[0] - start, line };
}
function upsertServer(source, filePath, configKey, name, entry, version) {
  const doc = parseSource2(source, filePath, version);
  const servers = getServersNode(doc, filePath, configKey);
  const eol = lineEnding(source);
  if (servers === void 0) {
    const nested = configKey.split(".").reduceRight((inner, key) => ({ [key]: inner }), {
      [name]: entry
    });
    return withTrailingNewline(source, eol) + render(nested, version, eol, 0);
  }
  if (servers === null || servers.items.length === 0) {
    const key = serversKeyLine(doc, source, configKey);
    return source.slice(0, key.start) + key.line + render({ [name]: entry }, version, eol, key.indent + 2) + source.slice(key.end);
  }
  const first = servers.items[0].key;
  const indent = first.range[0] - lineStart(source, first.range[0]);
  const unit = indent - serversKeyLine(doc, source, configKey).indent;
  const snippet = render({ [name]: entry }, version, eol, indent, unit);
  const existing = servers.items.find(
    (pair) => isScalar(pair.key) && pair.key.value === name
  );
  if (existing) {
    const [start, end2] = pairSpan(source, existing);
    return source.slice(0, start) + snippet + source.slice(end2);
  }
  const [, end] = pairSpan(source, servers.items[servers.items.length - 1]);
  return withTrailingNewline(source.slice(0, end), eol) + snippet + source.slice(end);
}
function verifyEdit(updated, filePath, keyPath, expected, version) {
  const after = parseYamlValue(updated, filePath, version);
  const servers = getNestedValue(after ?? {}, keyPath.join("."));
  if (!isDeepStrictEqual(servers, expected)) {
    throw new Error(
      `add-mcp could not edit ${filePath} safely. The file was not changed.`
    );
  }
}
function removeYamlConfigKey(filePath, configKey, serverName, version) {
  if (!existsSync4(filePath)) {
    return;
  }
  const source = readFileSync4(filePath, "utf-8");
  const doc = parseSource2(source, filePath, version);
  const servers = getServersNode(doc, filePath, configKey);
  const pair = servers?.items.find(
    (item) => isScalar(item.key) && item.key.value === serverName
  );
  if (!servers || !pair) {
    return;
  }
  const [start, end] = pairSpan(source, pair);
  let updated = source.slice(0, start) + source.slice(end);
  if (servers.items.length === 1) {
    const key = serversKeyLine(doc, source, configKey);
    updated = source.slice(0, key.start) + key.line.replace(
      /:( #[^\r\n]*)?\r?\n$/,
      (_match, comment = "") => `: {}${comment}${lineEnding(source)}`
    ) + source.slice(key.end, start) + source.slice(end);
  }
  const expected = {
    ...toJSValue(filePath, () => servers.toJS(doc))
  };
  delete expected[serverName];
  verifyEdit(updated, filePath, configKey.split("."), expected, version);
  writeFileSync2(filePath, updated);
}
function writeYamlConfig(filePath, config, configKey, version) {
  const dir = dirname4(filePath);
  if (!existsSync4(dir)) {
    mkdirSync2(dir, { recursive: true });
  }
  const original = existsSync4(filePath) ? readFileSync4(filePath, "utf-8") : "";
  const keyPath = configKey.split(".");
  const before = getNestedValue(
    parseYamlValue(original, filePath, version) ?? {},
    configKey
  );
  const expected = {
    ...before && typeof before === "object" ? before : {}
  };
  let updated = original;
  const incoming = getNestedValue(config, configKey);
  for (const [name, entry] of Object.entries(incoming)) {
    updated = upsertServer(updated, filePath, configKey, name, entry, version);
    expected[name] = entry;
  }
  verifyEdit(updated, filePath, keyPath, expected, version);
  writeFileSync2(filePath, updated);
}

// src/formats/toml.ts
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
import { dirname as dirname5 } from "path";
import * as TOML from "@iarna/toml";
function readTomlConfig(filePath) {
  if (!existsSync5(filePath)) {
    return {};
  }
  const content = readFileSync5(filePath, "utf-8");
  const parsed = TOML.parse(content);
  return parsed;
}
function removeTomlConfigKey(filePath, configKey, serverName) {
  if (!existsSync5(filePath)) {
    return;
  }
  const existing = readTomlConfig(filePath);
  const keys = configKey.split(".");
  let current = existing;
  for (const key of keys) {
    if (current && typeof current === "object" && key in current) {
      current = current[key];
    } else {
      return;
    }
  }
  if (current && typeof current === "object" && serverName in current) {
    delete current[serverName];
  }
  const content = TOML.stringify(existing);
  writeFileSync3(filePath, content);
}
function writeTomlConfig(filePath, config, configKey) {
  const dir = dirname5(filePath);
  if (!existsSync5(dir)) {
    mkdirSync3(dir, { recursive: true });
  }
  let existingConfig = {};
  if (existsSync5(filePath)) {
    existingConfig = readTomlConfig(filePath);
  }
  dropReplacedServers(existingConfig, config, configKey);
  const mergedConfig = deepMerge(existingConfig, config);
  const content = TOML.stringify(mergedConfig);
  writeFileSync3(filePath, content);
}

// src/formats/index.ts
function readConfig2(filePath, format) {
  switch (format) {
    case "json":
      return readJsonConfig(filePath);
    case "yaml":
      return readYamlConfig(filePath, "1.2");
    case "yaml-1.1":
      return readYamlConfig(filePath, "1.1");
    case "toml":
      return readTomlConfig(filePath);
    default:
      throw new Error(`Unsupported config format: ${format}`);
  }
}
function removeServerFromConfig(filePath, format, configKey, serverName) {
  switch (format) {
    case "json":
      removeJsonConfigKey(filePath, configKey, serverName);
      break;
    case "yaml":
      removeYamlConfigKey(filePath, configKey, serverName, "1.2");
      break;
    case "yaml-1.1":
      removeYamlConfigKey(filePath, configKey, serverName, "1.1");
      break;
    case "toml":
      removeTomlConfigKey(filePath, configKey, serverName);
      break;
    default:
      throw new Error(`Unsupported config format: ${format}`);
  }
}
function writeConfig2(filePath, config, format, configKey) {
  switch (format) {
    case "json":
      writeJsonConfig(filePath, config, configKey);
      break;
    case "yaml":
      writeYamlConfig(filePath, config, configKey, "1.2");
      break;
    case "yaml-1.1":
      writeYamlConfig(filePath, config, configKey, "1.1");
      break;
    case "toml":
      writeTomlConfig(filePath, config, configKey);
      break;
    default:
      throw new Error(`Unsupported config format: ${format}`);
  }
}
function buildConfigWithKey(configKey, serverName, serverConfig) {
  if (configKey === ROOT_CONFIG_KEY) {
    return { [serverName]: serverConfig };
  }
  const config = {};
  const servers = {};
  servers[serverName] = serverConfig;
  setNestedValue(config, configKey, servers);
  return config;
}

// src/installer.ts
import {
  chmodSync,
  existsSync as existsSync6,
  mkdirSync as mkdirSync4,
  readFileSync as readFileSync6,
  writeFileSync as writeFileSync4
} from "fs";
import { homedir as homedir3 } from "os";
import { join as join3, dirname as dirname6, isAbsolute, relative, sep } from "path";
var home2 = homedir3();
function buildServerConfig(parsed, options = {}) {
  if (parsed.type === "remote") {
    const config2 = {
      type: options.transport ?? "http",
      url: parsed.value
    };
    if (options.headers && Object.keys(options.headers).length > 0) {
      config2.headers = options.headers;
    }
    if (typeof options.timeout === "number") {
      config2.timeout = options.timeout;
    }
    if (options.oauthScopes && options.oauthScopes.length > 0) {
      config2.oauthScopes = options.oauthScopes;
    }
    if (options.autoApproveTools) {
      config2.autoApproveTools = options.autoApproveTools;
    }
    if (typeof options.bearerTokenEnv === "string") {
      const name = options.bearerTokenEnv.trim();
      if (name.length > 0) {
        config2.bearerTokenEnv = name;
      }
    }
    return config2;
  }
  if (parsed.type === "command") {
    let command;
    let parsedArgs;
    if (looksLikePath(parsed.value)) {
      command = parsed.value;
      parsedArgs = [];
    } else {
      const parts = parsed.value.split(" ");
      command = parts[0];
      parsedArgs = parts.slice(1);
    }
    const args = [...parsedArgs, ...options.args ?? []];
    const config2 = {
      command,
      args
    };
    if (options.env && Object.keys(options.env).length > 0) {
      config2.env = options.env;
    }
    if (options.autoApproveTools) {
      config2.autoApproveTools = options.autoApproveTools;
    }
    return config2;
  }
  const config = {
    command: "npx",
    args: ["-y", parsed.value, ...options.args ?? []]
  };
  if (options.env && Object.keys(options.env).length > 0) {
    config.env = options.env;
  }
  if (options.autoApproveTools) {
    config.autoApproveTools = options.autoApproveTools;
  }
  return config;
}
function readJsonObject(filePath) {
  if (!existsSync6(filePath)) {
    return {};
  }
  const parsed = JSON.parse(readFileSync6(filePath, "utf-8"));
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
}
function writeJsonObject(filePath, value) {
  const dir = dirname6(filePath);
  if (!existsSync6(dir)) {
    mkdirSync4(dir, { recursive: true });
  }
  writeFileSync4(filePath, `${JSON.stringify(value, null, 2)}
`, "utf-8");
}
function addUniqueStrings(existing, additions) {
  const values = Array.isArray(existing) ? existing.filter((value) => typeof value === "string") : [];
  for (const addition of additions) {
    if (!values.includes(addition)) {
      values.push(addition);
    }
  }
  return values;
}
function claudeAutoApproveRules(serverName, tools) {
  if (tools.length === 0) {
    return [`mcp__${serverName}`];
  }
  return tools.map((tool) => `mcp__${serverName}__${tool}`);
}
function getClaudeCodeSettingsPath(options = {}) {
  const local = Boolean(options.local);
  const cwd = options.cwd || process.cwd();
  return local ? join3(cwd, ".claude", "settings.local.json") : join3(home2, ".claude", "settings.json");
}
function installClaudeCodeAutoApproval(serverName, tools, options = {}) {
  const settingsPath = getClaudeCodeSettingsPath(options);
  try {
    const settings = readJsonObject(settingsPath);
    const permissions = settings.permissions && typeof settings.permissions === "object" && !Array.isArray(settings.permissions) ? settings.permissions : {};
    permissions.allow = addUniqueStrings(
      permissions.allow,
      claudeAutoApproveRules(serverName, tools)
    );
    settings.permissions = permissions;
    writeJsonObject(settingsPath, settings);
    return { success: true, path: settingsPath };
  } catch (error) {
    return {
      success: false,
      path: settingsPath,
      error: error instanceof Error ? error.message : "Unknown error"
    };
  }
}
function updateGitignoreWithPaths(paths, options = {}) {
  const cwd = options.cwd || process.cwd();
  const gitignorePath = join3(cwd, ".gitignore");
  const existingContent = existsSync6(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
  const existingEntries = new Set(
    existingContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0)
  );
  const entriesToAdd = [];
  for (const filePath of paths) {
    const relativePath = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
    if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
      continue;
    }
    const normalizedPath = relativePath.split(sep).join("/");
    const cleanPath = normalizedPath.startsWith("./") ? normalizedPath.slice(2) : normalizedPath;
    if (!cleanPath || cleanPath === ".gitignore" || existingEntries.has(cleanPath)) {
      continue;
    }
    existingEntries.add(cleanPath);
    entriesToAdd.push(cleanPath);
  }
  if (entriesToAdd.length > 0) {
    let nextContent = existingContent;
    if (nextContent.length > 0 && !nextContent.endsWith("\n")) {
      nextContent += "\n";
    }
    nextContent += `${entriesToAdd.join("\n")}
`;
    writeFileSync4(gitignorePath, nextContent, "utf-8");
  }
  return {
    path: gitignorePath,
    added: entriesToAdd
  };
}
function getConfigPath2(agent, options = {}) {
  const local = Boolean(options.local);
  const cwd = options.cwd || process.cwd();
  if (agent.resolveConfigPath) {
    return agent.resolveConfigPath(agent, { local, cwd });
  }
  if (local && agent.localConfigPath) {
    return join3(cwd, agent.localConfigPath);
  }
  return agent.configPath;
}
function getConfigKey(agent, options = {}) {
  if (options.local && agent.name === "github-copilot-cli") {
    return githubCopilotCliProjectConfigKey(getConfigPath2(agent, options));
  }
  if (options.local && agent.localConfigKey) {
    return agent.localConfigKey;
  }
  return agent.configKey;
}
function rewriteCopilotCliConfig(agentType, configPath) {
  if (agentType === "github-copilot-cli") {
    rewriteJsoncAsJson(configPath);
  }
}
function claudeCopilotGithubShadowError(cwd) {
  if (existsSync6(join3(cwd, ".mcp.json"))) {
    return null;
  }
  if (!existsSync6(join3(cwd, ".github", "mcp.json"))) {
    return null;
  }
  return "No changes made for Claude Code or GitHub Copilot CLI. Merge the servers from `.github/mcp.json` into `.mcp.json` under `mcpServers`, resolve duplicate names, then rerun this command.";
}
function copilotClaudeGithubLayoutError(agentTypes, options = {}) {
  const cwd = options.cwd || process.cwd();
  const copilotLocal = options.routing?.get("github-copilot-cli") === "local";
  const claudeLocal = options.routing?.get("claude-code") === "local";
  if (!agentTypes.includes("github-copilot-cli") || !agentTypes.includes("claude-code") || !copilotLocal || !claudeLocal) {
    return null;
  }
  return claudeCopilotGithubShadowError(cwd);
}
function installServerForAgent(serverName, serverConfig, agentType, options = {}) {
  const agent = agents[agentType];
  let configPath = "";
  try {
    configPath = getConfigPath2(agent, options);
    if (typeof serverConfig.bearerTokenEnv === "string") {
      const name = serverConfig.bearerTokenEnv.trim();
      if (!isBearerTokenEnvName(name)) {
        return {
          success: false,
          path: configPath,
          error: invalidBearerTokenEnvMessage(serverConfig.bearerTokenEnv)
        };
      }
    }
    const { config: gatedConfig, dropped } = applyFieldSupport(
      serverConfig,
      agent.supportedFields
    );
    const configKey = agentType === "opencode" ? resolveOpenCodeUpsertKey(configPath, serverName) : getConfigKey(agent, options);
    const transformedConfig = agent.transformConfig(serverName, gatedConfig, {
      local: Boolean(options.local),
      configKey
    });
    if (options.local && agentType === "claude-code") {
      const layoutError = claudeCopilotGithubShadowError(
        options.cwd || process.cwd()
      );
      if (layoutError) {
        return {
          success: false,
          path: configPath,
          error: layoutError
        };
      }
    }
    const dir = dirname6(configPath);
    if (!existsSync6(dir)) {
      mkdirSync4(dir, { recursive: true });
    }
    const config = buildConfigWithKey(configKey, serverName, transformedConfig);
    writeConfig2(configPath, config, agent.format, configKey);
    rewriteCopilotCliConfig(agentType, configPath);
    if (agentType === "fx") {
      chmodSync(dirname6(configPath), 448);
      chmodSync(configPath, 384);
    }
    const extraPaths = [];
    if (agentType === "claude-code" && gatedConfig.autoApproveTools) {
      const approval = installClaudeCodeAutoApproval(
        serverName,
        gatedConfig.autoApproveTools,
        options
      );
      if (!approval.success) {
        return {
          success: false,
          path: approval.path,
          error: approval.error
        };
      }
      extraPaths.push(approval.path);
    }
    return {
      success: true,
      path: configPath,
      ...dropped.length > 0 ? { droppedFields: dropped } : {},
      ...extraPaths.length > 0 ? { extraPaths } : {}
    };
  } catch (error) {
    return {
      success: false,
      path: configPath,
      error: error instanceof Error ? error.message : "Unknown error"
    };
  }
}
function installServer(serverName, serverConfig, agentTypes, options = {}) {
  const results = /* @__PURE__ */ new Map();
  const layoutError = copilotClaudeGithubLayoutError(agentTypes, options);
  for (const agentType of agentTypes) {
    if (layoutError && (agentType === "github-copilot-cli" || agentType === "claude-code")) {
      results.set(agentType, {
        success: false,
        path: "",
        error: layoutError
      });
      continue;
    }
    const routing = options.routing?.get(agentType);
    const installOptions = {
      local: routing === "local",
      cwd: options.cwd
    };
    const result = installServerForAgent(
      serverName,
      serverConfig,
      agentType,
      installOptions
    );
    results.set(agentType, result);
  }
  return results;
}

// src/reader.ts
function normalizeStoredCommand(serverConfig) {
  const commandParts = Array.isArray(serverConfig.command) ? serverConfig.command.filter((a) => typeof a === "string") : void 0;
  const command = typeof serverConfig.command === "string" ? serverConfig.command : typeof serverConfig.cmd === "string" ? serverConfig.cmd : commandParts?.[0];
  const args = Array.isArray(serverConfig.args) ? serverConfig.args.filter((a) => typeof a === "string") : commandParts?.slice(1) ?? [];
  return { command, args };
}
function extractServerIdentity(serverConfig) {
  for (const key of ["httpUrl", "url", "uri", "serverUrl"]) {
    const value = serverConfig[key];
    if (typeof value === "string" && value.length > 0) {
      return value;
    }
  }
  const { command, args: rawArgs } = normalizeStoredCommand(serverConfig);
  if (!command) {
    return "";
  }
  if (command === "npx" || command === "bunx") {
    const yIndex = rawArgs.indexOf("-y");
    const pkgIndex = yIndex >= 0 ? yIndex + 1 : 0;
    const pkg = rawArgs[pkgIndex];
    if (pkg && !pkg.startsWith("-")) {
      return pkg;
    }
  }
  if (rawArgs.length > 0) {
    return `${command} ${rawArgs.join(" ")}`;
  }
  return command;
}
function readServersForAgent(agentType, options) {
  const agent = agents[agentType];
  const installOptions = {
    local: options.scope === "local",
    cwd: options.cwd
  };
  const configPath = getConfigPath2(agent, installOptions);
  if (agentType === "opencode") {
    const servers2 = listOpenCodeServers(configPath).map(
      (entry) => ({
        serverName: entry.serverName,
        config: entry.config,
        identity: extractServerIdentity(entry.config),
        agentType,
        scope: options.scope,
        configPath,
        configKey: entry.configKey
      })
    );
    return {
      agentType,
      displayName: agent.displayName,
      detected: true,
      scope: options.scope,
      configPath,
      servers: servers2
    };
  }
  const configKey = getConfigKey(agent, installOptions);
  const fullConfig = readConfig2(configPath, agent.format);
  const serversObj = getNestedValue(fullConfig, configKey);
  const servers = [];
  if (serversObj && typeof serversObj === "object" && !Array.isArray(serversObj)) {
    for (const [serverName, serverConfig] of Object.entries(serversObj)) {
      if (serverConfig && typeof serverConfig === "object") {
        const config = serverConfig;
        servers.push({
          serverName,
          config,
          identity: extractServerIdentity(config),
          agentType,
          scope: options.scope,
          configPath,
          configKey
        });
      }
    }
  }
  return {
    agentType,
    displayName: agent.displayName,
    detected: true,
    scope: options.scope,
    configPath,
    servers
  };
}
function readServersForAgentSafe(agentType, options) {
  const agent = agents[agentType];
  let configPath = "";
  try {
    configPath = getConfigPath2(agent, {
      local: options.scope === "local",
      cwd: options.cwd
    });
    return readServersForAgent(agentType, options);
  } catch (error) {
    return {
      agentType,
      displayName: agent.displayName,
      detected: true,
      scope: options.scope,
      configPath,
      servers: [],
      error: error instanceof Error ? error.message : String(error)
    };
  }
}
async function listInstalledServers(options) {
  const scope = options.global ? "global" : "local";
  const results = [];
  if (options.agents && options.agents.length > 0) {
    const detectedSet = new Set(
      options.global ? await detectGlobalAgents() : detectProjectAgents(options.cwd)
    );
    for (const agentType of options.agents) {
      const detected = detectedSet.has(agentType);
      if (detected) {
        results.push(
          readServersForAgentSafe(agentType, { scope, cwd: options.cwd })
        );
      } else {
        const agent = agents[agentType];
        let configPath = "";
        let error;
        try {
          configPath = getConfigPath2(agent, {
            local: scope === "local",
            cwd: options.cwd
          });
        } catch (resolveError) {
          error = resolveError instanceof Error ? resolveError.message : String(resolveError);
        }
        results.push({
          agentType,
          displayName: agent.displayName,
          detected: false,
          scope,
          configPath,
          servers: [],
          ...error ? { error } : {}
        });
      }
    }
  } else {
    const detected = options.global ? await detectGlobalAgents() : detectProjectAgents(options.cwd);
    for (const agentType of detected) {
      results.push(
        readServersForAgentSafe(agentType, { scope, cwd: options.cwd })
      );
    }
  }
  return results;
}
function findMatchingServers(agentServersList, query) {
  const lowerQuery = query.toLowerCase();
  const matches = [];
  for (const agentServers of agentServersList) {
    for (const server of agentServers.servers) {
      const nameMatch = server.serverName.toLowerCase().includes(lowerQuery);
      const identityMatch = server.identity === query;
      if (nameMatch || identityMatch) {
        matches.push(server);
      }
    }
  }
  return matches;
}

export {
  DEFAULT_FIND_REGISTRY_URL,
  DEFAULT_FIND_REGISTRY_LABEL,
  getConfigPath,
  getLastSelectedAgents,
  getFindRegistries,
  saveFindRegistries,
  isBearerTokenEnvName,
  describeOptionalField,
  getNestedValue,
  listOpenCodeServers,
  relocateOpenCodeServer,
  removeOpenCodeServer,
  agents,
  getAgentTypes,
  supportsProjectConfig,
  getCommonInstallScopes,
  getProjectCapableAgents,
  detectProjectAgents,
  detectGlobalAgents,
  isTransportSupported,
  buildAgentSelectionChoices,
  selectAgentsInteractive,
  parseSource,
  isRemoteSource,
  readConfig2 as readConfig,
  removeServerFromConfig,
  buildServerConfig,
  updateGitignoreWithPaths,
  getConfigPath2,
  getConfigKey,
  rewriteCopilotCliConfig,
  claudeCopilotGithubShadowError,
  installServerForAgent,
  installServer,
  normalizeStoredCommand,
  extractServerIdentity,
  listInstalledServers,
  findMatchingServers
};