add-mcp 1.8.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 +1 -1
  2. package/dist/index.js +396 -21
  3. package/package.json +8 -6
package/README.md CHANGED
@@ -18,7 +18,7 @@ Example installing the Context7 remote MCP server:
18
18
  npx add-mcp https://mcp.context7.com/mcp
19
19
  ```
20
20
 
21
- You can add env variables and arguments (stdio) and headers (remote) to the server config using the `--env`, `--args` and `--header` options.
21
+ You can add env variables and arguments (stdio) and headers (remote) to the server config using the `--env`, `--args` and `--header` options. With `${VAR}` placeholders, interactive installs prompt for each variable (omit skipped optional keys so empty strings are not written to config).
22
22
 
23
23
  ## Find an MCP Servers
24
24
 
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(" ")
@@ -1562,7 +1850,7 @@ function buildServerConfig(parsed, options = {}) {
1562
1850
  if (parsed.type === "command") {
1563
1851
  const parts = parsed.value.split(" ");
1564
1852
  const command = parts[0];
1565
- const args = parts.slice(1);
1853
+ const args = [...parts.slice(1), ...options.args ?? []];
1566
1854
  const config2 = {
1567
1855
  command,
1568
1856
  args
@@ -1574,7 +1862,7 @@ function buildServerConfig(parsed, options = {}) {
1574
1862
  }
1575
1863
  const config = {
1576
1864
  command: "npx",
1577
- args: ["-y", parsed.value]
1865
+ args: ["-y", parsed.value, ...options.args ?? []]
1578
1866
  };
1579
1867
  if (options.env && Object.keys(options.env).length > 0) {
1580
1868
  config.env = options.env;
@@ -1797,7 +2085,7 @@ function findMatchingServers(agentServersList, query) {
1797
2085
  // package.json
1798
2086
  var package_default = {
1799
2087
  name: "add-mcp",
1800
- version: "1.8.0",
2088
+ version: "1.9.0",
1801
2089
  description: "Add MCP servers to your favorite coding agents with a single command.",
1802
2090
  author: "Andre Landgraf <andre@neon.tech>",
1803
2091
  license: "Apache-2.0",
@@ -1813,12 +2101,13 @@ var package_default = {
1813
2101
  fmt: "prettier --write .",
1814
2102
  build: "tsup src/index.ts --format esm --dts --clean",
1815
2103
  dev: "tsx src/index.ts",
1816
- 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/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
1817
- "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/reader.test.ts && tsx tests/formats-remove.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",
1818
2106
  "registry:sort": "tsx scripts/sort-registry.ts",
1819
2107
  "registry:verify": "tsx scripts/verify-registry.ts",
1820
2108
  "test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
1821
2109
  typecheck: "tsc --noEmit",
2110
+ fallow: "fallow",
1822
2111
  prepublishOnly: "npm run build"
1823
2112
  },
1824
2113
  keywords: [
@@ -1842,11 +2131,11 @@ var package_default = {
1842
2131
  ],
1843
2132
  repository: {
1844
2133
  type: "git",
1845
- url: "git+https://github.com/neondatabase/add-mcp.git"
2134
+ url: "git+https://github.com/neon-solutions/add-mcp.git"
1846
2135
  },
1847
- homepage: "https://github.com/neondatabase/add-mcp#readme",
2136
+ homepage: "https://github.com/neon-solutions/add-mcp#readme",
1848
2137
  bugs: {
1849
- url: "https://github.com/neondatabase/add-mcp/issues"
2138
+ url: "https://github.com/neon-solutions/add-mcp/issues"
1850
2139
  },
1851
2140
  dependencies: {
1852
2141
  "@clack/prompts": "^0.9.1",
@@ -1859,6 +2148,7 @@ var package_default = {
1859
2148
  devDependencies: {
1860
2149
  "@types/js-yaml": "^4.0.9",
1861
2150
  "@types/node": "^22.10.0",
2151
+ fallow: "^2.61.0",
1862
2152
  prettier: "^3.8.1",
1863
2153
  tsup: "^8.3.5",
1864
2154
  tsx: "^4.19.2",
@@ -1976,6 +2266,27 @@ function extractSubcommandOptionsFromArgv() {
1976
2266
  result.gitignore = true;
1977
2267
  continue;
1978
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
+ }
1979
2290
  if ((arg === "-n" || arg === "--name") && argv[i + 1]) {
1980
2291
  result.name = argv[i + 1];
1981
2292
  i += 1;
@@ -2058,6 +2369,13 @@ function parseEnv(values) {
2058
2369
  }
2059
2370
  return { env, invalid };
2060
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
+ }
2061
2379
  program.name("add-mcp").description(
2062
2380
  "Install MCP servers for coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex, and more \u2014 run list-agents for the full list)"
2063
2381
  ).version(version).argument("[target]", "MCP server URL (remote) or package name (local stdio)").option(
@@ -2071,12 +2389,17 @@ program.name("add-mcp").description(
2071
2389
  "Transport type for remote servers (http, sse)"
2072
2390
  ).option("--type <type>", "Alias for --transport").option(
2073
2391
  "--header <header>",
2074
- "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.",
2075
2393
  collect,
2076
2394
  []
2077
2395
  ).option(
2078
2396
  "--env <env>",
2079
- "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.",
2080
2403
  collect,
2081
2404
  []
2082
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) => {
@@ -2111,7 +2434,9 @@ async function runFindCommand(keyword, rawOptions) {
2111
2434
  transport: installPlan.transport,
2112
2435
  header: installPlan.headers ? Object.entries(installPlan.headers).map(
2113
2436
  ([key, value]) => `${key}: ${value}`
2114
- ) : 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
2115
2440
  };
2116
2441
  await main(installPlan.target, mergedOptions);
2117
2442
  }
@@ -2681,6 +3006,55 @@ async function main(target, options) {
2681
3006
  "--env is only used for local/package/command installs, ignoring"
2682
3007
  );
2683
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;
2684
3058
  const serverName = options.name || parsed.inferredName;
2685
3059
  p3.log.info(`Server name: ${chalk.cyan(serverName)}`);
2686
3060
  const transportValue = options.transport || options.type;
@@ -2700,8 +3074,9 @@ async function main(target, options) {
2700
3074
  }
2701
3075
  const serverConfig = buildServerConfig(parsed, {
2702
3076
  transport: resolvedTransport,
2703
- headers: isRemote && hasHeaderValues ? headerResult.headers : void 0,
2704
- 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
2705
3080
  });
2706
3081
  let targetAgents;
2707
3082
  const allAgentTypes = getAgentTypes();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-mcp",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Add MCP servers to your favorite coding agents with a single command.",
5
5
  "author": "Andre Landgraf <andre@neon.tech>",
6
6
  "license": "Apache-2.0",
@@ -16,12 +16,13 @@
16
16
  "fmt": "prettier --write .",
17
17
  "build": "tsup src/index.ts --format esm --dts --clean",
18
18
  "dev": "tsx src/index.ts",
19
- "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/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
20
- "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/reader.test.ts && tsx tests/formats-remove.test.ts",
19
+ "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",
20
+ "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",
21
21
  "registry:sort": "tsx scripts/sort-registry.ts",
22
22
  "registry:verify": "tsx scripts/verify-registry.ts",
23
23
  "test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
24
24
  "typecheck": "tsc --noEmit",
25
+ "fallow": "fallow",
25
26
  "prepublishOnly": "npm run build"
26
27
  },
27
28
  "keywords": [
@@ -45,11 +46,11 @@
45
46
  ],
46
47
  "repository": {
47
48
  "type": "git",
48
- "url": "git+https://github.com/neondatabase/add-mcp.git"
49
+ "url": "git+https://github.com/neon-solutions/add-mcp.git"
49
50
  },
50
- "homepage": "https://github.com/neondatabase/add-mcp#readme",
51
+ "homepage": "https://github.com/neon-solutions/add-mcp#readme",
51
52
  "bugs": {
52
- "url": "https://github.com/neondatabase/add-mcp/issues"
53
+ "url": "https://github.com/neon-solutions/add-mcp/issues"
53
54
  },
54
55
  "dependencies": {
55
56
  "@clack/prompts": "^0.9.1",
@@ -62,6 +63,7 @@
62
63
  "devDependencies": {
63
64
  "@types/js-yaml": "^4.0.9",
64
65
  "@types/node": "^22.10.0",
66
+ "fallow": "^2.61.0",
65
67
  "prettier": "^3.8.1",
66
68
  "tsup": "^8.3.5",
67
69
  "tsx": "^4.19.2",