add-mcp 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +72 -2
  2. package/dist/index.js +1048 -23
  3. package/package.json +8 -6
package/dist/index.js CHANGED
@@ -816,6 +816,234 @@ function isRemoteSource(parsed) {
816
816
 
817
817
  // src/find.ts
818
818
  import * as p2 from "@clack/prompts";
819
+
820
+ // src/template.ts
821
+ var TEMPLATE_PATTERN = /\$\{([^}]+)\}/g;
822
+ function findTemplateVars(value) {
823
+ const vars = [];
824
+ let match;
825
+ while ((match = TEMPLATE_PATTERN.exec(value)) !== null) {
826
+ vars.push(match[1]);
827
+ }
828
+ TEMPLATE_PATTERN.lastIndex = 0;
829
+ return vars;
830
+ }
831
+ function substituteTemplatePlaceholders(value, replacement) {
832
+ return value.replace(/\$\{([^}]+)\}/g, () => replacement);
833
+ }
834
+ async function resolveTemplates(value, ask) {
835
+ const vars = findTemplateVars(value);
836
+ if (vars.length === 0) return { resolved: value, cancelled: false };
837
+ let result = value;
838
+ for (const varName of vars) {
839
+ const answer = await ask(varName);
840
+ if (typeof answer === "symbol") return { resolved: value, cancelled: true };
841
+ const entered = typeof answer === "string" ? answer : "";
842
+ result = result.replace(`\${${varName}}`, entered);
843
+ }
844
+ return { resolved: result, cancelled: false };
845
+ }
846
+ async function resolveRecordTemplates(record, ask) {
847
+ const resolved = {};
848
+ for (const [key, value] of Object.entries(record)) {
849
+ const result = await resolveTemplates(value, ask);
850
+ if (result.cancelled) return { resolved: record, cancelled: true };
851
+ resolved[key] = result.resolved;
852
+ }
853
+ return { resolved, cancelled: false };
854
+ }
855
+ async function resolveArrayTemplates(values, ask) {
856
+ const resolved = [];
857
+ for (const value of values) {
858
+ const result = await resolveTemplates(value, ask);
859
+ if (result.cancelled) return { resolved: values, cancelled: true };
860
+ resolved.push(result.resolved);
861
+ }
862
+ return { resolved, cancelled: false };
863
+ }
864
+ function hasTemplateVars(values) {
865
+ const strings = Array.isArray(values) ? values : Object.values(values);
866
+ return strings.some((v) => findTemplateVars(v).length > 0);
867
+ }
868
+
869
+ // src/package-arguments.ts
870
+ var ARG_PLACEHOLDER = "<your-variable-value-here>";
871
+ function normalizeNamedFlag(name) {
872
+ const t = name.trim();
873
+ if (t.startsWith("-")) return t;
874
+ return `--${t}`;
875
+ }
876
+ function definitionsFromPackage(pkg) {
877
+ return pkg.packageArguments ?? [];
878
+ }
879
+ function isNamedArg(d) {
880
+ if (d.type === "positional") return false;
881
+ if (d.type === "named") return true;
882
+ return Boolean(d.name?.trim());
883
+ }
884
+ function labelForArg(d, index) {
885
+ if (isNamedArg(d)) {
886
+ return `Flag ${normalizeNamedFlag(d.name)}`;
887
+ }
888
+ return d.valueHint?.trim() || d.description?.trim() || `Positional argument #${index + 1}`;
889
+ }
890
+ function hasTemplates(s) {
891
+ return Boolean(s && findTemplateVars(s).length > 0);
892
+ }
893
+ function buildPackageArgumentsArgvNonInteractive(definitions, placeholder = ARG_PLACEHOLDER) {
894
+ const out = [];
895
+ for (let i = 0; i < definitions.length; i++) {
896
+ const d = definitions[i];
897
+ if (isNamedArg(d)) {
898
+ const flag = normalizeNamedFlag(d.name);
899
+ let val = d.value?.trim() ?? "";
900
+ if (hasTemplates(d.value)) {
901
+ val = substituteTemplatePlaceholders(d.value, placeholder);
902
+ } else if (!val && d.default?.trim()) {
903
+ val = d.default.trim();
904
+ }
905
+ if (!val) {
906
+ if (d.isRequired === true) {
907
+ out.push(flag, placeholder);
908
+ }
909
+ continue;
910
+ }
911
+ out.push(flag, val);
912
+ } else {
913
+ let val = d.value?.trim() ?? "";
914
+ if (hasTemplates(d.value)) {
915
+ val = substituteTemplatePlaceholders(d.value, placeholder);
916
+ pushPositionalSegments(out, d, val);
917
+ continue;
918
+ }
919
+ if (val) {
920
+ pushPositionalSegments(out, d, val);
921
+ continue;
922
+ }
923
+ if (d.default?.trim()) {
924
+ pushPositionalSegments(out, d, d.default.trim());
925
+ continue;
926
+ }
927
+ pushPositionalSegments(out, d, placeholder);
928
+ }
929
+ }
930
+ return out;
931
+ }
932
+ function pushPositionalSegments(out, d, raw) {
933
+ if (d.isRepeated === true) {
934
+ const parts = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
935
+ if (parts.length === 0) {
936
+ out.push(raw);
937
+ return;
938
+ }
939
+ for (const p4 of parts) {
940
+ out.push(p4);
941
+ }
942
+ return;
943
+ }
944
+ out.push(raw);
945
+ }
946
+ async function buildPackageArgumentsArgvInteractive(definitions, prompt) {
947
+ const out = [];
948
+ for (let i = 0; i < definitions.length; i++) {
949
+ const d = definitions[i];
950
+ const label = labelForArg(d, i);
951
+ if (isNamedArg(d)) {
952
+ const flag = normalizeNamedFlag(d.name);
953
+ let val2 = d.value ?? "";
954
+ if (hasTemplates(d.value)) {
955
+ const res = await resolveTemplates(d.value, async (varName) => {
956
+ const answer = await prompt({
957
+ label: `${label} (${varName})`,
958
+ isRequired: true,
959
+ placeholder: `<${varName}>`
960
+ });
961
+ return answer;
962
+ });
963
+ if (res.cancelled) return { cancelled: true };
964
+ val2 = res.resolved;
965
+ out.push(flag, val2);
966
+ continue;
967
+ }
968
+ if (val2.trim()) {
969
+ out.push(flag, val2.trim());
970
+ continue;
971
+ }
972
+ const defVal2 = d.default?.trim() ?? "";
973
+ const raw2 = await prompt({
974
+ label,
975
+ isRequired: d.isRequired === true,
976
+ placeholder: defVal2 || ARG_PLACEHOLDER
977
+ });
978
+ if (typeof raw2 === "symbol") return { cancelled: true };
979
+ const trimmed2 = typeof raw2 === "string" ? raw2.trim() : "";
980
+ if (!trimmed2) {
981
+ if (d.isRequired === true) {
982
+ out.push(flag, defVal2 || ARG_PLACEHOLDER);
983
+ } else if (defVal2) {
984
+ out.push(flag, defVal2);
985
+ }
986
+ continue;
987
+ }
988
+ out.push(flag, trimmed2);
989
+ continue;
990
+ }
991
+ let val = d.value ?? "";
992
+ if (hasTemplates(d.value)) {
993
+ const res = await resolveTemplates(d.value, async (varName) => {
994
+ const answer = await prompt({
995
+ label: `${label} (${varName})`,
996
+ isRequired: true,
997
+ placeholder: `<${varName}>`
998
+ });
999
+ return answer;
1000
+ });
1001
+ if (res.cancelled) return { cancelled: true };
1002
+ val = res.resolved;
1003
+ pushPositionalSegments(out, d, val);
1004
+ continue;
1005
+ }
1006
+ if (val.trim()) {
1007
+ pushPositionalSegments(out, d, val.trim());
1008
+ continue;
1009
+ }
1010
+ const defVal = d.default?.trim() ?? "";
1011
+ if (defVal) {
1012
+ pushPositionalSegments(out, d, defVal);
1013
+ continue;
1014
+ }
1015
+ if (d.isRequired !== true) {
1016
+ const raw2 = await prompt({
1017
+ label,
1018
+ isRequired: false,
1019
+ placeholder: ARG_PLACEHOLDER
1020
+ });
1021
+ if (typeof raw2 === "symbol") return { cancelled: true };
1022
+ const trimmed2 = typeof raw2 === "string" ? raw2.trim() : "";
1023
+ if (!trimmed2) {
1024
+ pushPositionalSegments(out, d, ARG_PLACEHOLDER);
1025
+ } else {
1026
+ pushPositionalSegments(out, d, trimmed2);
1027
+ }
1028
+ continue;
1029
+ }
1030
+ const raw = await prompt({
1031
+ label,
1032
+ isRequired: true,
1033
+ placeholder: ARG_PLACEHOLDER
1034
+ });
1035
+ if (typeof raw === "symbol") return { cancelled: true };
1036
+ const trimmed = typeof raw === "string" ? raw.trim() : "";
1037
+ pushPositionalSegments(
1038
+ out,
1039
+ d,
1040
+ trimmed.length > 0 ? trimmed : ARG_PLACEHOLDER
1041
+ );
1042
+ }
1043
+ return { argv: out, cancelled: false };
1044
+ }
1045
+
1046
+ // src/find.ts
819
1047
  function buildPlaceholderValue(kind) {
820
1048
  return kind === "header" ? "<your-header-value-here>" : "<your-variable-value-here>";
821
1049
  }
@@ -1009,9 +1237,6 @@ function pickRemote(entry, preferredTransport) {
1009
1237
  return preferred ?? remotes[0] ?? null;
1010
1238
  }
1011
1239
  function formatPackageTarget(pkg) {
1012
- if (pkg.version) {
1013
- return `${pkg.identifier}@${pkg.version}`;
1014
- }
1015
1240
  return pkg.identifier;
1016
1241
  }
1017
1242
  function transportLabel(entry) {
@@ -1064,6 +1289,15 @@ function headerFields(headers) {
1064
1289
  placeholder: buildPlaceholderValue("header")
1065
1290
  }));
1066
1291
  }
1292
+ function packageVariableFields(variables) {
1293
+ if (!variables) return [];
1294
+ return variables.filter((variable) => variable.name && variable.name.trim().length > 0).map((variable) => ({
1295
+ key: variable.name,
1296
+ label: `Environment variable ${variable.name}`,
1297
+ isRequired: variable.isRequired === true,
1298
+ placeholder: buildPlaceholderValue("variable")
1299
+ }));
1300
+ }
1067
1301
  function resolveNonInteractiveRemote(remote) {
1068
1302
  const variableValues = {};
1069
1303
  for (const [key, def] of Object.entries(remote.variables ?? {})) {
@@ -1098,6 +1332,55 @@ async function resolveInteractiveRemote(remote) {
1098
1332
  headers: Object.keys(headerResult.values).length > 0 ? headerResult.values : void 0
1099
1333
  };
1100
1334
  }
1335
+ function resolveNonInteractivePackage(pkg) {
1336
+ const env = {};
1337
+ for (const field of packageVariableFields(pkg.environmentVariables)) {
1338
+ if (field.isRequired) {
1339
+ env[field.key] = field.placeholder;
1340
+ }
1341
+ }
1342
+ const headers = {};
1343
+ for (const field of headerFields(pkg.headers)) {
1344
+ if (field.isRequired) {
1345
+ headers[field.key] = field.placeholder;
1346
+ }
1347
+ }
1348
+ const argv = buildPackageArgumentsArgvNonInteractive(
1349
+ definitionsFromPackage(pkg),
1350
+ buildPlaceholderValue("variable")
1351
+ );
1352
+ return {
1353
+ env: Object.keys(env).length > 0 ? env : void 0,
1354
+ headers: Object.keys(headers).length > 0 ? headers : void 0,
1355
+ args: argv.length > 0 ? argv : void 0
1356
+ };
1357
+ }
1358
+ async function resolveInteractivePackage(pkg) {
1359
+ const promptPackageArg = async (info) => p2.text({
1360
+ message: `${info.label} ${info.isRequired ? "(required)" : "(optional)"}`,
1361
+ placeholder: info.placeholder
1362
+ });
1363
+ const envResult = await collectPromptValues(
1364
+ packageVariableFields(pkg.environmentVariables),
1365
+ promptValue
1366
+ );
1367
+ if (envResult.cancelled) return null;
1368
+ const headerResult = await collectPromptValues(
1369
+ headerFields(pkg.headers),
1370
+ promptValue
1371
+ );
1372
+ if (headerResult.cancelled) return null;
1373
+ const argvResult = await buildPackageArgumentsArgvInteractive(
1374
+ definitionsFromPackage(pkg),
1375
+ promptPackageArg
1376
+ );
1377
+ if (argvResult.cancelled) return null;
1378
+ return {
1379
+ env: Object.keys(envResult.values).length > 0 ? envResult.values : void 0,
1380
+ headers: Object.keys(headerResult.values).length > 0 ? headerResult.values : void 0,
1381
+ args: argvResult.argv.length > 0 ? argvResult.argv : void 0
1382
+ };
1383
+ }
1101
1384
  async function buildInstallPlanForEntry(entry, options) {
1102
1385
  const remote = pickRemote(entry, options.preferredTransport);
1103
1386
  const pkg = entry.package ?? null;
@@ -1130,9 +1413,14 @@ async function buildInstallPlanForEntry(entry, options) {
1130
1413
  mode = hasRemote ? "remote" : "package";
1131
1414
  }
1132
1415
  if (mode === "package" && pkg) {
1416
+ const resolved2 = options.yes ? resolveNonInteractivePackage(pkg) : await resolveInteractivePackage(pkg);
1417
+ if (!resolved2) return null;
1133
1418
  return {
1134
1419
  target: formatPackageTarget(pkg),
1135
- serverName: resolveServerName(entry)
1420
+ serverName: resolveServerName(entry),
1421
+ env: resolved2.env,
1422
+ headers: resolved2.headers,
1423
+ args: resolved2.args
1136
1424
  };
1137
1425
  }
1138
1426
  if (!remote) return null;
@@ -1306,13 +1594,13 @@ function getNestedValue(obj, path) {
1306
1594
  }
1307
1595
 
1308
1596
  // src/formats/json.ts
1309
- function detectIndent(text2) {
1597
+ function detectIndent(text3) {
1310
1598
  let result = null;
1311
- jsonc.visit(text2, {
1599
+ jsonc.visit(text3, {
1312
1600
  onObjectProperty: (_property, offset, _length, startLine, startCharacter) => {
1313
1601
  if (result === null && startLine > 0 && startCharacter > 0) {
1314
- const lineStart = text2.lastIndexOf("\n", offset - 1) + 1;
1315
- const whitespace = text2.slice(lineStart, offset);
1602
+ const lineStart = text3.lastIndexOf("\n", offset - 1) + 1;
1603
+ const whitespace = text3.slice(lineStart, offset);
1316
1604
  result = {
1317
1605
  tabSize: startCharacter,
1318
1606
  insertSpaces: !whitespace.includes(" ")
@@ -1322,6 +1610,14 @@ function detectIndent(text2) {
1322
1610
  });
1323
1611
  return result || { tabSize: 2, insertSpaces: true };
1324
1612
  }
1613
+ function readJsonConfig(filePath) {
1614
+ if (!existsSync2(filePath)) {
1615
+ return {};
1616
+ }
1617
+ const content = readFileSync(filePath, "utf-8");
1618
+ const parsed = jsonc.parse(content);
1619
+ return parsed;
1620
+ }
1325
1621
  function writeJsonConfig(filePath, config, configKey) {
1326
1622
  const dir = dirname3(filePath);
1327
1623
  if (!existsSync2(dir)) {
@@ -1349,6 +1645,31 @@ function writeJsonConfig(filePath, config, configKey) {
1349
1645
  }
1350
1646
  writeFileSync(filePath, JSON.stringify(mergedConfig, null, 2));
1351
1647
  }
1648
+ function removeJsonConfigKey(filePath, configKey, serverName) {
1649
+ if (!existsSync2(filePath)) {
1650
+ return;
1651
+ }
1652
+ const originalContent = readFileSync(filePath, "utf-8");
1653
+ const configKeyPath = configKey.split(".");
1654
+ try {
1655
+ const edits = jsonc.modify(
1656
+ originalContent,
1657
+ [...configKeyPath, serverName],
1658
+ void 0,
1659
+ { formattingOptions: detectIndent(originalContent) }
1660
+ );
1661
+ const updatedContent = jsonc.applyEdits(originalContent, edits);
1662
+ writeFileSync(filePath, updatedContent);
1663
+ return;
1664
+ } catch {
1665
+ }
1666
+ const parsed = jsonc.parse(originalContent);
1667
+ const servers = getNestedValue(parsed, configKey);
1668
+ if (servers && typeof servers === "object" && serverName in servers) {
1669
+ delete servers[serverName];
1670
+ }
1671
+ writeFileSync(filePath, JSON.stringify(parsed, null, 2));
1672
+ }
1352
1673
  function setNestedValue(obj, path, value) {
1353
1674
  const keys = path.split(".");
1354
1675
  const lastKey = keys.pop();
@@ -1375,6 +1696,30 @@ function readYamlConfig(filePath) {
1375
1696
  const parsed = yaml.load(content);
1376
1697
  return parsed || {};
1377
1698
  }
1699
+ function removeYamlConfigKey(filePath, configKey, serverName) {
1700
+ if (!existsSync3(filePath)) {
1701
+ return;
1702
+ }
1703
+ const existing = readYamlConfig(filePath);
1704
+ const keys = configKey.split(".");
1705
+ let current = existing;
1706
+ for (const key of keys) {
1707
+ if (current && typeof current === "object" && key in current) {
1708
+ current = current[key];
1709
+ } else {
1710
+ return;
1711
+ }
1712
+ }
1713
+ if (current && typeof current === "object" && serverName in current) {
1714
+ delete current[serverName];
1715
+ }
1716
+ const content = yaml.dump(existing, {
1717
+ indent: 2,
1718
+ lineWidth: -1,
1719
+ noRefs: true
1720
+ });
1721
+ writeFileSync2(filePath, content);
1722
+ }
1378
1723
  function writeYamlConfig(filePath, config) {
1379
1724
  const dir = dirname4(filePath);
1380
1725
  if (!existsSync3(dir)) {
@@ -1405,6 +1750,26 @@ function readTomlConfig(filePath) {
1405
1750
  const parsed = TOML.parse(content);
1406
1751
  return parsed;
1407
1752
  }
1753
+ function removeTomlConfigKey(filePath, configKey, serverName) {
1754
+ if (!existsSync4(filePath)) {
1755
+ return;
1756
+ }
1757
+ const existing = readTomlConfig(filePath);
1758
+ const keys = configKey.split(".");
1759
+ let current = existing;
1760
+ for (const key of keys) {
1761
+ if (current && typeof current === "object" && key in current) {
1762
+ current = current[key];
1763
+ } else {
1764
+ return;
1765
+ }
1766
+ }
1767
+ if (current && typeof current === "object" && serverName in current) {
1768
+ delete current[serverName];
1769
+ }
1770
+ const content = TOML.stringify(existing);
1771
+ writeFileSync3(filePath, content);
1772
+ }
1408
1773
  function writeTomlConfig(filePath, config) {
1409
1774
  const dir = dirname5(filePath);
1410
1775
  if (!existsSync4(dir)) {
@@ -1420,6 +1785,33 @@ function writeTomlConfig(filePath, config) {
1420
1785
  }
1421
1786
 
1422
1787
  // src/formats/index.ts
1788
+ function readConfig2(filePath, format) {
1789
+ switch (format) {
1790
+ case "json":
1791
+ return readJsonConfig(filePath);
1792
+ case "yaml":
1793
+ return readYamlConfig(filePath);
1794
+ case "toml":
1795
+ return readTomlConfig(filePath);
1796
+ default:
1797
+ throw new Error(`Unsupported config format: ${format}`);
1798
+ }
1799
+ }
1800
+ function removeServerFromConfig(filePath, format, configKey, serverName) {
1801
+ switch (format) {
1802
+ case "json":
1803
+ removeJsonConfigKey(filePath, configKey, serverName);
1804
+ break;
1805
+ case "yaml":
1806
+ removeYamlConfigKey(filePath, configKey, serverName);
1807
+ break;
1808
+ case "toml":
1809
+ removeTomlConfigKey(filePath, configKey, serverName);
1810
+ break;
1811
+ default:
1812
+ throw new Error(`Unsupported config format: ${format}`);
1813
+ }
1814
+ }
1423
1815
  function writeConfig2(filePath, config, format, configKey) {
1424
1816
  switch (format) {
1425
1817
  case "json":
@@ -1458,7 +1850,7 @@ function buildServerConfig(parsed, options = {}) {
1458
1850
  if (parsed.type === "command") {
1459
1851
  const parts = parsed.value.split(" ");
1460
1852
  const command = parts[0];
1461
- const args = parts.slice(1);
1853
+ const args = [...parts.slice(1), ...options.args ?? []];
1462
1854
  const config2 = {
1463
1855
  command,
1464
1856
  args
@@ -1470,7 +1862,7 @@ function buildServerConfig(parsed, options = {}) {
1470
1862
  }
1471
1863
  const config = {
1472
1864
  command: "npx",
1473
- args: ["-y", parsed.value]
1865
+ args: ["-y", parsed.value, ...options.args ?? []]
1474
1866
  };
1475
1867
  if (options.env && Object.keys(options.env).length > 0) {
1476
1868
  config.env = options.env;
@@ -1574,10 +1966,126 @@ function installServer(serverName, serverConfig, agentTypes, options = {}) {
1574
1966
  return results;
1575
1967
  }
1576
1968
 
1969
+ // src/reader.ts
1970
+ function extractServerIdentity(serverConfig) {
1971
+ for (const key of ["url", "uri", "serverUrl"]) {
1972
+ const value = serverConfig[key];
1973
+ if (typeof value === "string" && value.length > 0) {
1974
+ return value;
1975
+ }
1976
+ }
1977
+ const command = typeof serverConfig.command === "string" ? serverConfig.command : typeof serverConfig.cmd === "string" ? serverConfig.cmd : void 0;
1978
+ if (!command) {
1979
+ return "";
1980
+ }
1981
+ const rawArgs = Array.isArray(serverConfig.args) ? serverConfig.args.filter((a) => typeof a === "string") : Array.isArray(serverConfig.command) ? serverConfig.command.slice(1).filter((a) => typeof a === "string") : [];
1982
+ if (command === "npx" || command === "bunx") {
1983
+ const yIndex = rawArgs.indexOf("-y");
1984
+ const pkgIndex = yIndex >= 0 ? yIndex + 1 : 0;
1985
+ const pkg = rawArgs[pkgIndex];
1986
+ if (pkg && !pkg.startsWith("-")) {
1987
+ return pkg;
1988
+ }
1989
+ }
1990
+ if (Array.isArray(serverConfig.command)) {
1991
+ return serverConfig.command.join(" ");
1992
+ }
1993
+ if (rawArgs.length > 0) {
1994
+ return `${command} ${rawArgs.join(" ")}`;
1995
+ }
1996
+ return command;
1997
+ }
1998
+ function readServersForAgent(agentType, options) {
1999
+ const agent = agents[agentType];
2000
+ const installOptions = {
2001
+ local: options.scope === "local",
2002
+ cwd: options.cwd
2003
+ };
2004
+ const configPath = getConfigPath2(agent, installOptions);
2005
+ const configKey = getConfigKey(agent, installOptions);
2006
+ const fullConfig = readConfig2(configPath, agent.format);
2007
+ const serversObj = getNestedValue(fullConfig, configKey);
2008
+ const servers = [];
2009
+ if (serversObj && typeof serversObj === "object" && !Array.isArray(serversObj)) {
2010
+ for (const [serverName, serverConfig] of Object.entries(serversObj)) {
2011
+ if (serverConfig && typeof serverConfig === "object") {
2012
+ const config = serverConfig;
2013
+ servers.push({
2014
+ serverName,
2015
+ config,
2016
+ identity: extractServerIdentity(config),
2017
+ agentType,
2018
+ scope: options.scope,
2019
+ configPath
2020
+ });
2021
+ }
2022
+ }
2023
+ }
2024
+ return {
2025
+ agentType,
2026
+ displayName: agent.displayName,
2027
+ detected: true,
2028
+ scope: options.scope,
2029
+ configPath,
2030
+ servers
2031
+ };
2032
+ }
2033
+ async function gatherInstalledServers(options) {
2034
+ const scope = options.global ? "global" : "local";
2035
+ const results = [];
2036
+ if (options.agents && options.agents.length > 0) {
2037
+ const detectedSet = new Set(
2038
+ options.global ? await detectAllGlobalAgents() : detectProjectAgents(options.cwd)
2039
+ );
2040
+ for (const agentType of options.agents) {
2041
+ const detected = detectedSet.has(agentType);
2042
+ if (detected) {
2043
+ results.push(
2044
+ readServersForAgent(agentType, { scope, cwd: options.cwd })
2045
+ );
2046
+ } else {
2047
+ const agent = agents[agentType];
2048
+ const installOptions = {
2049
+ local: scope === "local",
2050
+ cwd: options.cwd
2051
+ };
2052
+ results.push({
2053
+ agentType,
2054
+ displayName: agent.displayName,
2055
+ detected: false,
2056
+ scope,
2057
+ configPath: getConfigPath2(agent, installOptions),
2058
+ servers: []
2059
+ });
2060
+ }
2061
+ }
2062
+ } else {
2063
+ const detected = options.global ? await detectAllGlobalAgents() : detectProjectAgents(options.cwd);
2064
+ for (const agentType of detected) {
2065
+ results.push(readServersForAgent(agentType, { scope, cwd: options.cwd }));
2066
+ }
2067
+ }
2068
+ return results;
2069
+ }
2070
+ function findMatchingServers(agentServersList, query) {
2071
+ const lowerQuery = query.toLowerCase();
2072
+ const matches = [];
2073
+ for (const agentServers of agentServersList) {
2074
+ for (const server of agentServers.servers) {
2075
+ const nameMatch = server.serverName.toLowerCase().includes(lowerQuery);
2076
+ const identityMatch = server.identity === query;
2077
+ if (nameMatch || identityMatch) {
2078
+ matches.push(server);
2079
+ }
2080
+ }
2081
+ }
2082
+ return matches;
2083
+ }
2084
+
1577
2085
  // package.json
1578
2086
  var package_default = {
1579
2087
  name: "add-mcp",
1580
- version: "1.7.0",
2088
+ version: "1.9.0",
1581
2089
  description: "Add MCP servers to your favorite coding agents with a single command.",
1582
2090
  author: "Andre Landgraf <andre@neon.tech>",
1583
2091
  license: "Apache-2.0",
@@ -1593,12 +2101,13 @@ var package_default = {
1593
2101
  fmt: "prettier --write .",
1594
2102
  build: "tsup src/index.ts --format esm --dts --clean",
1595
2103
  dev: "tsx src/index.ts",
1596
- test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
1597
- "test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts",
2104
+ test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
2105
+ "test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts",
1598
2106
  "registry:sort": "tsx scripts/sort-registry.ts",
1599
2107
  "registry:verify": "tsx scripts/verify-registry.ts",
1600
2108
  "test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
1601
2109
  typecheck: "tsc --noEmit",
2110
+ fallow: "fallow",
1602
2111
  prepublishOnly: "npm run build"
1603
2112
  },
1604
2113
  keywords: [
@@ -1622,11 +2131,11 @@ var package_default = {
1622
2131
  ],
1623
2132
  repository: {
1624
2133
  type: "git",
1625
- url: "git+https://github.com/neondatabase/add-mcp.git"
2134
+ url: "git+https://github.com/neon-solutions/add-mcp.git"
1626
2135
  },
1627
- homepage: "https://github.com/neondatabase/add-mcp#readme",
2136
+ homepage: "https://github.com/neon-solutions/add-mcp#readme",
1628
2137
  bugs: {
1629
- url: "https://github.com/neondatabase/add-mcp/issues"
2138
+ url: "https://github.com/neon-solutions/add-mcp/issues"
1630
2139
  },
1631
2140
  dependencies: {
1632
2141
  "@clack/prompts": "^0.9.1",
@@ -1639,6 +2148,7 @@ var package_default = {
1639
2148
  devDependencies: {
1640
2149
  "@types/js-yaml": "^4.0.9",
1641
2150
  "@types/node": "^22.10.0",
2151
+ fallow: "^2.61.0",
1642
2152
  prettier: "^3.8.1",
1643
2153
  tsup: "^8.3.5",
1644
2154
  tsx: "^4.19.2",
@@ -1734,7 +2244,7 @@ function extractOptions(raw) {
1734
2244
  }
1735
2245
  return raw;
1736
2246
  }
1737
- function extractFindOptionsFromArgv() {
2247
+ function extractSubcommandOptionsFromArgv() {
1738
2248
  const argv = process.argv.slice(2);
1739
2249
  const result = {};
1740
2250
  for (let i = 0; i < argv.length; i++) {
@@ -1756,6 +2266,27 @@ function extractFindOptionsFromArgv() {
1756
2266
  result.gitignore = true;
1757
2267
  continue;
1758
2268
  }
2269
+ if (arg === "--header" && argv[i + 1]) {
2270
+ const headers = result.header ? [...result.header] : [];
2271
+ headers.push(argv[i + 1]);
2272
+ result.header = headers;
2273
+ i += 1;
2274
+ continue;
2275
+ }
2276
+ if (arg === "--env" && argv[i + 1]) {
2277
+ const env = result.env ? [...result.env] : [];
2278
+ env.push(argv[i + 1]);
2279
+ result.env = env;
2280
+ i += 1;
2281
+ continue;
2282
+ }
2283
+ if (arg === "--args" && argv[i + 1]) {
2284
+ const args = result.args ? [...result.args] : [];
2285
+ args.push(argv[i + 1]);
2286
+ result.args = args;
2287
+ i += 1;
2288
+ continue;
2289
+ }
1759
2290
  if ((arg === "-n" || arg === "--name") && argv[i + 1]) {
1760
2291
  result.name = argv[i + 1];
1761
2292
  i += 1;
@@ -1838,6 +2369,13 @@ function parseEnv(values) {
1838
2369
  }
1839
2370
  return { env, invalid };
1840
2371
  }
2372
+ function omitEmptyStringValues(record) {
2373
+ return Object.fromEntries(
2374
+ Object.entries(record).filter(
2375
+ ([, v]) => typeof v === "string" && v.trim().length > 0
2376
+ )
2377
+ );
2378
+ }
1841
2379
  program.name("add-mcp").description(
1842
2380
  "Install MCP servers for coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex, and more \u2014 run list-agents for the full list)"
1843
2381
  ).version(version).argument("[target]", "MCP server URL (remote) or package name (local stdio)").option(
@@ -1851,12 +2389,17 @@ program.name("add-mcp").description(
1851
2389
  "Transport type for remote servers (http, sse)"
1852
2390
  ).option("--type <type>", "Alias for --transport").option(
1853
2391
  "--header <header>",
1854
- "HTTP header for remote servers (repeatable, 'Key: Value')",
2392
+ "HTTP header for remote servers (repeatable, 'Key: Value'). Placeholders ${VAR} prompt interactively when not using --yes.",
1855
2393
  collect,
1856
2394
  []
1857
2395
  ).option(
1858
2396
  "--env <env>",
1859
- "Environment variable for local stdio servers (repeatable, 'KEY=VALUE')",
2397
+ "Environment variable for local stdio servers (repeatable, 'KEY=VALUE'). Placeholders ${VAR} prompt interactively when not using --yes.",
2398
+ collect,
2399
+ []
2400
+ ).option(
2401
+ "--args <arg>",
2402
+ "Argument for local stdio servers (repeatable). Placeholders ${VAR} prompt interactively when not using --yes.",
1860
2403
  collect,
1861
2404
  []
1862
2405
  ).option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(async (target, options) => {
@@ -1868,7 +2411,7 @@ program.command("list-agents").description("List all supported coding agents").a
1868
2411
  async function runFindCommand(keyword, rawOptions) {
1869
2412
  const options = {
1870
2413
  ...extractOptions(rawOptions),
1871
- ...extractFindOptionsFromArgv()
2414
+ ...extractSubcommandOptionsFromArgv()
1872
2415
  };
1873
2416
  const query = (keyword ?? "").trim();
1874
2417
  const registries = await ensureFindRegistriesConfigured(options.yes);
@@ -1891,7 +2434,9 @@ async function runFindCommand(keyword, rawOptions) {
1891
2434
  transport: installPlan.transport,
1892
2435
  header: installPlan.headers ? Object.entries(installPlan.headers).map(
1893
2436
  ([key, value]) => `${key}: ${value}`
1894
- ) : options.header
2437
+ ) : options.header,
2438
+ env: installPlan.env ? Object.entries(installPlan.env).map(([key, value]) => `${key}=${value}`) : options.env,
2439
+ args: installPlan.args ?? options.args
1895
2440
  };
1896
2441
  await main(installPlan.target, mergedOptions);
1897
2442
  }
@@ -1919,7 +2464,437 @@ program.command("search [keyword]").description("Alias for find").option(
1919
2464
  await runFindCommand(keyword, options);
1920
2465
  }
1921
2466
  );
2467
+ program.command("list").description("List installed MCP servers across detected agents").option("-g, --global", "List global configs instead of project-level").option("-a, --agent <agent>", "Filter to specific agent(s)", collect, []).action(async (rawOptions) => {
2468
+ const options = {
2469
+ ...extractOptions(rawOptions),
2470
+ ...extractSubcommandOptionsFromArgv()
2471
+ };
2472
+ await runListCommand(options);
2473
+ });
2474
+ program.command("remove <query>").description("Remove an MCP server from agent configurations").option("-g, --global", "Remove from global configs instead of project-level").option("-a, --agent <agent>", "Filter to specific agent(s)", collect, []).option("-y, --yes", "Remove all matches without prompting").action(
2475
+ async (query, rawOptions) => {
2476
+ const options = {
2477
+ ...extractOptions(rawOptions),
2478
+ ...extractSubcommandOptionsFromArgv()
2479
+ };
2480
+ await runRemoveCommand(query, options);
2481
+ }
2482
+ );
2483
+ program.command("sync").description(
2484
+ "Synchronize server names and installations across all detected agents"
2485
+ ).option("-g, --global", "Sync global configs instead of project-level").option("-y, --yes", "Skip confirmation prompts").action(async (rawOptions) => {
2486
+ const options = {
2487
+ ...extractOptions(rawOptions),
2488
+ ...extractSubcommandOptionsFromArgv()
2489
+ };
2490
+ await runSyncCommand(options);
2491
+ });
2492
+ program.command("unify").description("Alias for sync").option("-g, --global", "Sync global configs instead of project-level").option("-y, --yes", "Skip confirmation prompts").action(async (rawOptions) => {
2493
+ const options = {
2494
+ ...extractOptions(rawOptions),
2495
+ ...extractSubcommandOptionsFromArgv()
2496
+ };
2497
+ await runSyncCommand(options);
2498
+ });
1922
2499
  program.parse();
2500
+ async function runListCommand(options) {
2501
+ showLogo();
2502
+ console.log();
2503
+ const explicitAgents = resolveAgentFlags(options.agent);
2504
+ const agentServersList = await gatherInstalledServers({
2505
+ global: options.global,
2506
+ agents: explicitAgents.length > 0 ? explicitAgents : void 0
2507
+ });
2508
+ if (agentServersList.length === 0) {
2509
+ const hint = options.global ? "No agents detected globally. Use -a to target a specific agent." : "No agents detected in this project. Use -g for global or -a to target a specific agent.";
2510
+ p3.log.info(hint);
2511
+ console.log();
2512
+ return;
2513
+ }
2514
+ for (const agentServers of agentServersList) {
2515
+ if (!agentServers.detected) {
2516
+ console.log(
2517
+ `${TEXT}${agentServers.displayName}:${RESET} ${DIM}not detected${RESET}`
2518
+ );
2519
+ continue;
2520
+ }
2521
+ if (agentServers.servers.length === 0) {
2522
+ console.log(
2523
+ `${TEXT}${agentServers.displayName}:${RESET} ${DIM}no servers configured${RESET}`
2524
+ );
2525
+ continue;
2526
+ }
2527
+ console.log(`${TEXT}${agentServers.displayName}:${RESET}`);
2528
+ for (const server of agentServers.servers) {
2529
+ const identityHint = server.identity ? ` ${DIM}(${server.identity})${RESET}` : "";
2530
+ console.log(
2531
+ ` ${DIM}-${RESET} ${TEXT}${server.serverName}${RESET}${identityHint}`
2532
+ );
2533
+ }
2534
+ }
2535
+ console.log();
2536
+ }
2537
+ async function runRemoveCommand(query, options) {
2538
+ showLogo();
2539
+ console.log();
2540
+ const explicitAgents = resolveAgentFlags(options.agent);
2541
+ const agentServersList = await gatherInstalledServers({
2542
+ global: options.global,
2543
+ agents: explicitAgents.length > 0 ? explicitAgents : void 0
2544
+ });
2545
+ const matches = findMatchingServers(agentServersList, query);
2546
+ if (matches.length === 0) {
2547
+ p3.log.info(`No matching servers found for '${query}'`);
2548
+ console.log();
2549
+ return;
2550
+ }
2551
+ const matchOptions = matches.map((m, i) => ({
2552
+ value: i,
2553
+ label: `${m.serverName} (${agents[m.agentType].displayName})`,
2554
+ hint: m.identity || m.configPath
2555
+ }));
2556
+ let selectedIndices;
2557
+ if (options.yes) {
2558
+ selectedIndices = matches.map((_, i) => i);
2559
+ p3.log.info(
2560
+ `Removing ${matches.length} server${matches.length !== 1 ? "s" : ""} matching '${query}'`
2561
+ );
2562
+ } else {
2563
+ const selected = await p3.multiselect({
2564
+ message: `Select servers to remove (${matches.length} match${matches.length !== 1 ? "es" : ""} found)`,
2565
+ options: matchOptions,
2566
+ required: false,
2567
+ initialValues: matches.map((_, i) => i)
2568
+ });
2569
+ if (p3.isCancel(selected)) {
2570
+ p3.log.info("No changes made");
2571
+ console.log();
2572
+ return;
2573
+ }
2574
+ selectedIndices = selected;
2575
+ if (selectedIndices.length === 0) {
2576
+ p3.log.info("No changes made");
2577
+ console.log();
2578
+ return;
2579
+ }
2580
+ }
2581
+ let removedCount = 0;
2582
+ const affectedAgents = /* @__PURE__ */ new Set();
2583
+ for (const idx of selectedIndices) {
2584
+ const server = matches[idx];
2585
+ const agent = agents[server.agentType];
2586
+ try {
2587
+ removeServerFromConfig(
2588
+ server.configPath,
2589
+ agent.format,
2590
+ getConfigKeyForServer(server),
2591
+ server.serverName
2592
+ );
2593
+ removedCount++;
2594
+ affectedAgents.add(agent.displayName);
2595
+ } catch (error) {
2596
+ p3.log.error(
2597
+ `Failed to remove ${server.serverName} from ${agent.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
2598
+ );
2599
+ }
2600
+ }
2601
+ if (removedCount > 0) {
2602
+ p3.log.success(
2603
+ `Removed ${removedCount} server${removedCount !== 1 ? "s" : ""} from ${affectedAgents.size} agent${affectedAgents.size !== 1 ? "s" : ""}`
2604
+ );
2605
+ }
2606
+ console.log();
2607
+ }
2608
+ function getConfigKeyForServer(server) {
2609
+ const agent = agents[server.agentType];
2610
+ if (server.scope === "local" && agent.localConfigKey) {
2611
+ return agent.localConfigKey;
2612
+ }
2613
+ return agent.configKey;
2614
+ }
2615
+ function deepEqual(a, b) {
2616
+ if (a === b) return true;
2617
+ if (a === null || b === null) return false;
2618
+ if (a === void 0 || b === void 0) return a === b;
2619
+ if (typeof a !== typeof b) return false;
2620
+ if (Array.isArray(a) && Array.isArray(b)) {
2621
+ if (a.length !== b.length) return false;
2622
+ return a.every((val, i) => deepEqual(val, b[i]));
2623
+ }
2624
+ if (typeof a === "object" && typeof b === "object") {
2625
+ const aObj = a;
2626
+ const bObj = b;
2627
+ const aKeys = Object.keys(aObj).sort();
2628
+ const bKeys = Object.keys(bObj).sort();
2629
+ if (!deepEqual(aKeys, bKeys)) return false;
2630
+ return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
2631
+ }
2632
+ return false;
2633
+ }
2634
+ function pickCanonicalName(entries) {
2635
+ const nameFreq = /* @__PURE__ */ new Map();
2636
+ for (const entry of entries) {
2637
+ nameFreq.set(entry.serverName, (nameFreq.get(entry.serverName) ?? 0) + 1);
2638
+ }
2639
+ const names = [...nameFreq.entries()];
2640
+ names.sort(([nameA, freqA], [nameB, freqB]) => {
2641
+ if (nameA.length !== nameB.length) return nameA.length - nameB.length;
2642
+ if (freqA !== freqB) return freqB - freqA;
2643
+ return nameA.localeCompare(nameB);
2644
+ });
2645
+ return names[0][0];
2646
+ }
2647
+ function extractConflictFields(config) {
2648
+ return {
2649
+ headers: config.headers ?? config.http_headers ?? null,
2650
+ env: config.env ?? config.envs ?? config.environment ?? null,
2651
+ args: config.args ?? null
2652
+ };
2653
+ }
2654
+ function buildSyncGroups(agentServersList) {
2655
+ const byIdentity = /* @__PURE__ */ new Map();
2656
+ for (const agentServers of agentServersList) {
2657
+ for (const server of agentServers.servers) {
2658
+ if (!server.identity) continue;
2659
+ const existing = byIdentity.get(server.identity) ?? [];
2660
+ existing.push(server);
2661
+ byIdentity.set(server.identity, existing);
2662
+ }
2663
+ }
2664
+ const groups = [];
2665
+ for (const [identity, entries] of byIdentity) {
2666
+ const fieldSets = entries.map((e) => extractConflictFields(e.config));
2667
+ const reference = fieldSets[0];
2668
+ let hasConflict = false;
2669
+ let conflictReason;
2670
+ for (let i = 1; i < fieldSets.length; i++) {
2671
+ const other = fieldSets[i];
2672
+ if (!deepEqual(reference.headers, other.headers)) {
2673
+ hasConflict = true;
2674
+ conflictReason = `headers differ between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
2675
+ break;
2676
+ }
2677
+ if (!deepEqual(reference.env, other.env)) {
2678
+ hasConflict = true;
2679
+ conflictReason = `env differs between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
2680
+ break;
2681
+ }
2682
+ if (!deepEqual(reference.args, other.args)) {
2683
+ hasConflict = true;
2684
+ conflictReason = `args differ between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
2685
+ break;
2686
+ }
2687
+ }
2688
+ groups.push({
2689
+ identity,
2690
+ entries,
2691
+ canonicalName: pickCanonicalName(entries),
2692
+ canonicalConfig: entries[0].config,
2693
+ hasConflict,
2694
+ conflictReason
2695
+ });
2696
+ }
2697
+ return groups;
2698
+ }
2699
+ async function runSyncCommand(options) {
2700
+ showLogo();
2701
+ console.log();
2702
+ const agentServersList = await gatherInstalledServers({
2703
+ global: options.global
2704
+ });
2705
+ const agentsWithServers = agentServersList.filter(
2706
+ (a) => a.servers.length > 0
2707
+ );
2708
+ if (agentServersList.length < 2) {
2709
+ p3.log.info("Need at least 2 detected agents to sync");
2710
+ console.log();
2711
+ return;
2712
+ }
2713
+ const groups = buildSyncGroups(agentServersList);
2714
+ const detectedAgentTypes = new Set(agentServersList.map((a) => a.agentType));
2715
+ const renames = [];
2716
+ const additions = [];
2717
+ const skipped = [];
2718
+ for (const group of groups) {
2719
+ if (group.hasConflict) {
2720
+ skipped.push(group);
2721
+ continue;
2722
+ }
2723
+ const presentAgents = new Set(group.entries.map((e) => e.agentType));
2724
+ for (const entry of group.entries) {
2725
+ if (entry.serverName !== group.canonicalName) {
2726
+ renames.push({
2727
+ group,
2728
+ agentType: entry.agentType,
2729
+ oldName: entry.serverName
2730
+ });
2731
+ }
2732
+ }
2733
+ for (const agentType of detectedAgentTypes) {
2734
+ if (!presentAgents.has(agentType)) {
2735
+ additions.push({ group, agentType });
2736
+ }
2737
+ }
2738
+ }
2739
+ if (renames.length === 0 && additions.length === 0 && skipped.length === 0) {
2740
+ p3.log.info("All servers are already in sync");
2741
+ console.log();
2742
+ return;
2743
+ }
2744
+ const planLines = [];
2745
+ if (renames.length > 0) {
2746
+ planLines.push(chalk.cyan("Renames:"));
2747
+ for (const r of renames) {
2748
+ planLines.push(
2749
+ ` ${agents[r.agentType].displayName}: ${r.oldName} \u2192 ${r.group.canonicalName}`
2750
+ );
2751
+ }
2752
+ }
2753
+ if (additions.length > 0) {
2754
+ planLines.push(chalk.cyan("Additions:"));
2755
+ for (const a of additions) {
2756
+ planLines.push(
2757
+ ` ${agents[a.agentType].displayName}: + ${a.group.canonicalName} (${a.group.identity})`
2758
+ );
2759
+ }
2760
+ }
2761
+ if (skipped.length > 0) {
2762
+ planLines.push(chalk.yellow("Skipped (conflicts):"));
2763
+ for (const s of skipped) {
2764
+ planLines.push(` ${s.identity}: ${s.conflictReason}`);
2765
+ }
2766
+ }
2767
+ if (renames.length === 0 && additions.length === 0) {
2768
+ p3.note(planLines.join("\n"), "Sync Plan");
2769
+ p3.log.info(
2770
+ "All servers are already in sync (some skipped due to conflicts)"
2771
+ );
2772
+ console.log();
2773
+ return;
2774
+ }
2775
+ p3.note(planLines.join("\n"), "Sync Plan");
2776
+ if (!options.yes) {
2777
+ const confirmed = await p3.confirm({
2778
+ message: "Proceed with sync?"
2779
+ });
2780
+ if (p3.isCancel(confirmed) || !confirmed) {
2781
+ p3.log.info("No changes made");
2782
+ console.log();
2783
+ return;
2784
+ }
2785
+ }
2786
+ const scope = options.global ? "global" : "local";
2787
+ let changeCount = 0;
2788
+ for (const rename of renames) {
2789
+ const { group, agentType } = rename;
2790
+ const result = installServerForAgent(
2791
+ group.canonicalName,
2792
+ buildServerConfigFromStored(group.canonicalConfig),
2793
+ agentType,
2794
+ { local: scope === "local" }
2795
+ );
2796
+ if (result.success) {
2797
+ changeCount++;
2798
+ } else {
2799
+ p3.log.error(
2800
+ `Failed to write ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
2801
+ );
2802
+ }
2803
+ }
2804
+ for (const addition of additions) {
2805
+ const { group, agentType } = addition;
2806
+ const result = installServerForAgent(
2807
+ group.canonicalName,
2808
+ buildServerConfigFromStored(group.canonicalConfig),
2809
+ agentType,
2810
+ { local: scope === "local" }
2811
+ );
2812
+ if (result.success) {
2813
+ changeCount++;
2814
+ } else {
2815
+ p3.log.error(
2816
+ `Failed to add ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
2817
+ );
2818
+ }
2819
+ }
2820
+ for (const rename of renames) {
2821
+ const { group, agentType, oldName } = rename;
2822
+ const agentConfig = agents[agentType];
2823
+ const entry = group.entries.find((e) => e.agentType === agentType);
2824
+ if (!entry) continue;
2825
+ try {
2826
+ removeServerFromConfig(
2827
+ entry.configPath,
2828
+ agentConfig.format,
2829
+ getConfigKeyForServer(entry),
2830
+ oldName
2831
+ );
2832
+ } catch (error) {
2833
+ p3.log.error(
2834
+ `Failed to remove old alias ${oldName} from ${agentConfig.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
2835
+ );
2836
+ }
2837
+ }
2838
+ p3.log.success(
2839
+ `Synced ${changeCount} server${changeCount !== 1 ? "s" : ""} across ${detectedAgentTypes.size} agent${detectedAgentTypes.size !== 1 ? "s" : ""}`
2840
+ );
2841
+ console.log();
2842
+ }
2843
+ var TRANSPORT_ALIASES = {
2844
+ http: "http",
2845
+ sse: "sse",
2846
+ streamable_http: "http",
2847
+ streamableHttp: "http",
2848
+ "streamable-http": "http",
2849
+ remote: "http"
2850
+ };
2851
+ function normalizeTransportType(raw) {
2852
+ if (typeof raw === "string" && raw in TRANSPORT_ALIASES) {
2853
+ return TRANSPORT_ALIASES[raw];
2854
+ }
2855
+ return "http";
2856
+ }
2857
+ function buildServerConfigFromStored(config) {
2858
+ const url = typeof config.url === "string" ? config.url : typeof config.uri === "string" ? config.uri : typeof config.serverUrl === "string" ? config.serverUrl : void 0;
2859
+ if (url) {
2860
+ const result2 = {
2861
+ type: normalizeTransportType(config.type),
2862
+ url
2863
+ };
2864
+ const headers = config.headers && typeof config.headers === "object" ? config.headers : config.http_headers && typeof config.http_headers === "object" ? config.http_headers : void 0;
2865
+ if (headers && Object.keys(headers).length > 0) {
2866
+ result2.headers = headers;
2867
+ }
2868
+ return result2;
2869
+ }
2870
+ const command = typeof config.command === "string" ? config.command : typeof config.cmd === "string" ? config.cmd : void 0;
2871
+ const args = Array.isArray(config.args) ? config.args.filter((a) => typeof a === "string") : [];
2872
+ const env = config.env && typeof config.env === "object" ? config.env : config.envs && typeof config.envs === "object" ? config.envs : config.environment && typeof config.environment === "object" ? config.environment : void 0;
2873
+ const result = {};
2874
+ if (command) result.command = command;
2875
+ if (args.length > 0) result.args = args;
2876
+ if (env && Object.keys(env).length > 0) result.env = env;
2877
+ return result;
2878
+ }
2879
+ function resolveAgentFlags(agentFlags) {
2880
+ if (!agentFlags || agentFlags.length === 0) return [];
2881
+ const resolved = [];
2882
+ const invalid = [];
2883
+ for (const input of agentFlags) {
2884
+ const agentType = resolveAgentType(input);
2885
+ if (agentType) {
2886
+ resolved.push(agentType);
2887
+ } else {
2888
+ invalid.push(input);
2889
+ }
2890
+ }
2891
+ if (invalid.length > 0) {
2892
+ p3.log.error(`Invalid agents: ${invalid.join(", ")}`);
2893
+ p3.log.info(`Valid agents: ${getAgentTypes().join(", ")}`);
2894
+ process.exit(1);
2895
+ }
2896
+ return resolved;
2897
+ }
1923
2898
  function listAgents() {
1924
2899
  showLogo();
1925
2900
  console.log();
@@ -2031,6 +3006,55 @@ async function main(target, options) {
2031
3006
  "--env is only used for local/package/command installs, ignoring"
2032
3007
  );
2033
3008
  }
3009
+ const argsValues = options.args ?? [];
3010
+ const hasArgsValues = argsValues.length > 0;
3011
+ if (hasArgsValues && isRemote) {
3012
+ p3.log.warn(
3013
+ "--args is only used for local/package/command installs, ignoring"
3014
+ );
3015
+ }
3016
+ const promptTemplateVar = (varName) => p3.text({
3017
+ message: `Enter value for ${varName}`,
3018
+ placeholder: `<${varName}>`
3019
+ });
3020
+ let resolvedArgs = [...argsValues];
3021
+ if (!options.yes && hasHeaderValues && hasTemplateVars(headerResult.headers)) {
3022
+ const result = await resolveRecordTemplates(
3023
+ headerResult.headers,
3024
+ promptTemplateVar
3025
+ );
3026
+ if (result.cancelled) {
3027
+ p3.cancel("Cancelled");
3028
+ process.exit(0);
3029
+ }
3030
+ for (const [key, value] of Object.entries(result.resolved)) {
3031
+ headerResult.headers[key] = value;
3032
+ }
3033
+ }
3034
+ if (!options.yes && hasEnvValues && hasTemplateVars(envResult.env)) {
3035
+ const result = await resolveRecordTemplates(
3036
+ envResult.env,
3037
+ promptTemplateVar
3038
+ );
3039
+ if (result.cancelled) {
3040
+ p3.cancel("Cancelled");
3041
+ process.exit(0);
3042
+ }
3043
+ for (const [key, value] of Object.entries(result.resolved)) {
3044
+ envResult.env[key] = value;
3045
+ }
3046
+ }
3047
+ if (!options.yes && hasArgsValues && hasTemplateVars(resolvedArgs)) {
3048
+ const result = await resolveArrayTemplates(resolvedArgs, promptTemplateVar);
3049
+ if (result.cancelled) {
3050
+ p3.cancel("Cancelled");
3051
+ process.exit(0);
3052
+ }
3053
+ resolvedArgs = result.resolved;
3054
+ }
3055
+ const headersForConfig = isRemote && hasHeaderValues ? omitEmptyStringValues(headerResult.headers) : void 0;
3056
+ const envForConfig = !isRemote && hasEnvValues ? omitEmptyStringValues(envResult.env) : void 0;
3057
+ const argsForConfig = !isRemote && hasArgsValues ? resolvedArgs.filter((a) => a.trim().length > 0) : void 0;
2034
3058
  const serverName = options.name || parsed.inferredName;
2035
3059
  p3.log.info(`Server name: ${chalk.cyan(serverName)}`);
2036
3060
  const transportValue = options.transport || options.type;
@@ -2050,8 +3074,9 @@ async function main(target, options) {
2050
3074
  }
2051
3075
  const serverConfig = buildServerConfig(parsed, {
2052
3076
  transport: resolvedTransport,
2053
- headers: isRemote && hasHeaderValues ? headerResult.headers : void 0,
2054
- env: !isRemote && hasEnvValues ? envResult.env : void 0
3077
+ headers: headersForConfig && Object.keys(headersForConfig).length > 0 ? headersForConfig : void 0,
3078
+ env: envForConfig && Object.keys(envForConfig).length > 0 ? envForConfig : void 0,
3079
+ args: argsForConfig && argsForConfig.length > 0 ? argsForConfig : void 0
2055
3080
  });
2056
3081
  let targetAgents;
2057
3082
  const allAgentTypes = getAgentTypes();