add-mcp 1.10.3 → 1.11.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.
package/README.md CHANGED
@@ -18,6 +18,20 @@ Example installing the Context7 remote MCP server:
18
18
  npx add-mcp https://mcp.context7.com/mcp
19
19
  ```
20
20
 
21
+ Example installing the Context7 remote MCP server via the programmatic API — great for integrating add-mcp in your own CLI tool:
22
+
23
+ ```ts
24
+ import { upsertServer } from "add-mcp";
25
+
26
+ const result = upsertServer(
27
+ "cursor",
28
+ "context7",
29
+ { type: "http", url: "https://mcp.context7.com/mcp" },
30
+ { local: true },
31
+ );
32
+ console.log(result);
33
+ ```
34
+
21
35
  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
36
 
23
37
  ## Find an MCP Servers
@@ -110,6 +124,10 @@ npx add-mcp https://mcp.example.com/sse --transport sse
110
124
  # Remote MCP server with auth header
111
125
  npx add-mcp https://mcp.example.com/mcp --header "Authorization: Bearer $TOKEN"
112
126
 
127
+ # Remote server with a request timeout and OAuth scopes
128
+ # (each agent only keeps the fields it supports; others are dropped with a warning)
129
+ npx add-mcp https://mcp.example.com/mcp --timeout 30000 --scopes "read,write"
130
+
113
131
  # npm package (runs via npx)
114
132
  npx add-mcp @modelcontextprotocol/server-postgres
115
133
 
@@ -152,13 +170,32 @@ npx add-mcp https://mcp.example.com/mcp -a cursor -y --gitignore
152
170
  | `-a, --agent <agent>` | Target specific agents (e.g., `cursor`, `claude-code`). Can be repeated. |
153
171
  | `-t, --transport <type>` | Transport type for remote servers: `http` (default), `sse` |
154
172
  | `--type <type>` | Alias for `--transport` |
155
- | `--header <header>` | HTTP header for remote servers (repeatable, `Key: Value`) |
173
+ | `-h, --header <header>` | HTTP header for remote servers (repeatable, `Key: Value`) |
156
174
  | `--env <env>` | Env var for local stdio servers (repeatable, `KEY=VALUE`) |
175
+ | `--timeout <ms>` | Request timeout (ms) for remote servers (capability-gated, see below) |
176
+ | `--scopes <scopes>` | OAuth scopes for remote servers, comma-separated (capability-gated) |
177
+ | `--oauth-scopes <scopes>`| Alias for `--scopes` |
157
178
  | `-n, --name <name>` | Server name (auto-inferred if not provided) |
158
179
  | `-y, --yes` | Skip all confirmation prompts |
159
180
  | `--all` | Install to all agents |
160
181
  | `--gitignore` | Add generated config files to `.gitignore` |
161
182
 
183
+ #### Capability-gated fields (`--timeout`, `--scopes`)
184
+
185
+ Not every MCP client understands every field. `add-mcp` keeps one canonical
186
+ server config and each agent declares which optional fields it supports, mapping
187
+ them into that client's native shape:
188
+
189
+ | Field | Flag | Supported by | Mapped to |
190
+ | ---------- | ---------------- | --------------------------- | ---------------------------------- |
191
+ | Timeout | `--timeout` | Claude Code, Gemini CLI | `timeout` (milliseconds) |
192
+ | OAuth scopes | `--scopes` | Cursor, Gemini CLI | Cursor `auth.scopes`, Gemini `oauth.scopes` |
193
+
194
+ When you target an agent that does not support a field, `add-mcp` drops it from
195
+ that agent's config and prints a warning (e.g. _"request timeout is not
196
+ supported by VS Code; dropped from that config."_). Other agents still receive
197
+ it. Both flags apply to remote servers only.
198
+
162
199
  ### Transport Types
163
200
 
164
201
  `add-mcp` supports all three transport types: HTTP, SSE, and stdio. Some agents require `type` option to be set to specify the transport type. You can use the `--type` or `--transport` option to specify the transport type:
@@ -159,6 +159,41 @@ var windsurfConfigPath = join2(
159
159
  "windsurf",
160
160
  "mcp_config.json"
161
161
  );
162
+ function buildStandardRemote(config) {
163
+ const remote = {};
164
+ if (config.type) remote.type = config.type;
165
+ if (config.url) remote.url = config.url;
166
+ if (config.headers && Object.keys(config.headers).length > 0) {
167
+ remote.headers = config.headers;
168
+ }
169
+ if (typeof config.timeout === "number") {
170
+ remote.timeout = config.timeout;
171
+ }
172
+ return remote;
173
+ }
174
+ function buildStandardLocal(config) {
175
+ const local = {
176
+ command: config.command,
177
+ args: config.args || []
178
+ };
179
+ if (config.env && Object.keys(config.env).length > 0) {
180
+ local.env = config.env;
181
+ }
182
+ return local;
183
+ }
184
+ function transformStandardConfig(_serverName, config) {
185
+ return config.url ? buildStandardRemote(config) : buildStandardLocal(config);
186
+ }
187
+ function transformGeminiConfig(_serverName, config) {
188
+ if (!config.url) {
189
+ return buildStandardLocal(config);
190
+ }
191
+ const remote = buildStandardRemote(config);
192
+ if (config.oauthScopes && config.oauthScopes.length > 0) {
193
+ remote.oauth = { scopes: config.oauthScopes };
194
+ }
195
+ return remote;
196
+ }
162
197
  function transformGooseConfig(serverName, config) {
163
198
  if (config.url) {
164
199
  const gooseType = config.type === "sse" ? "sse" : "streamable_http";
@@ -240,9 +275,12 @@ function transformCursorConfig(_serverName, config) {
240
275
  if (config.headers && Object.keys(config.headers).length > 0) {
241
276
  remoteConfig.headers = config.headers;
242
277
  }
278
+ if (config.oauthScopes && config.oauthScopes.length > 0) {
279
+ remoteConfig.auth = { scopes: config.oauthScopes };
280
+ }
243
281
  return remoteConfig;
244
282
  }
245
- return config;
283
+ return buildStandardLocal(config);
246
284
  }
247
285
  function transformClineConfig(_serverName, config) {
248
286
  if (config.url) {
@@ -335,6 +373,7 @@ var agents = {
335
373
  configKey: "mcpServers",
336
374
  format: "json",
337
375
  supportedTransports: ["stdio", "http", "sse"],
376
+ supportedFields: [],
338
377
  detectGlobalInstall: async () => {
339
378
  return existsSync(join2(home, ".gemini"));
340
379
  },
@@ -349,6 +388,7 @@ var agents = {
349
388
  configKey: "mcpServers",
350
389
  format: "json",
351
390
  supportedTransports: ["stdio", "http", "sse"],
391
+ supportedFields: [],
352
392
  detectGlobalInstall: async () => {
353
393
  return existsSync(dirname2(clineExtensionConfigPath));
354
394
  },
@@ -363,6 +403,7 @@ var agents = {
363
403
  configKey: "mcpServers",
364
404
  format: "json",
365
405
  supportedTransports: ["stdio", "http", "sse"],
406
+ supportedFields: [],
366
407
  detectGlobalInstall: async () => {
367
408
  return existsSync(dirname2(clineCliConfigPath));
368
409
  },
@@ -377,9 +418,11 @@ var agents = {
377
418
  configKey: "mcpServers",
378
419
  format: "json",
379
420
  supportedTransports: ["stdio", "http", "sse"],
421
+ supportedFields: ["timeout"],
380
422
  detectGlobalInstall: async () => {
381
423
  return existsSync(join2(home, ".claude"));
382
- }
424
+ },
425
+ transformConfig: transformStandardConfig
383
426
  },
384
427
  "claude-desktop": {
385
428
  name: "claude-desktop",
@@ -390,10 +433,12 @@ var agents = {
390
433
  configKey: "mcpServers",
391
434
  format: "json",
392
435
  supportedTransports: ["stdio"],
436
+ supportedFields: [],
393
437
  unsupportedTransportMessage: "Claude Desktop only supports local (stdio) servers via its config file. Add remote servers through Settings \u2192 Connectors in the app instead.",
394
438
  detectGlobalInstall: async () => {
395
439
  return existsSync(join2(appSupport, "Claude"));
396
- }
440
+ },
441
+ transformConfig: transformStandardConfig
397
442
  },
398
443
  codex: {
399
444
  name: "codex",
@@ -407,6 +452,7 @@ var agents = {
407
452
  configKey: "mcp_servers",
408
453
  format: "toml",
409
454
  supportedTransports: ["stdio", "http", "sse"],
455
+ supportedFields: [],
410
456
  detectGlobalInstall: async () => {
411
457
  return existsSync(join2(home, ".codex"));
412
458
  },
@@ -421,6 +467,7 @@ var agents = {
421
467
  configKey: "mcpServers",
422
468
  format: "json",
423
469
  supportedTransports: ["stdio", "http", "sse"],
470
+ supportedFields: ["scopes"],
424
471
  detectGlobalInstall: async () => {
425
472
  return existsSync(join2(home, ".cursor"));
426
473
  },
@@ -435,9 +482,11 @@ var agents = {
435
482
  configKey: "mcpServers",
436
483
  format: "json",
437
484
  supportedTransports: ["stdio", "http", "sse"],
485
+ supportedFields: ["timeout", "scopes"],
438
486
  detectGlobalInstall: async () => {
439
487
  return existsSync(join2(home, ".gemini"));
440
- }
488
+ },
489
+ transformConfig: transformGeminiConfig
441
490
  },
442
491
  goose: {
443
492
  name: "goose",
@@ -448,6 +497,7 @@ var agents = {
448
497
  configKey: "extensions",
449
498
  format: "yaml",
450
499
  supportedTransports: ["stdio", "http", "sse"],
500
+ supportedFields: [],
451
501
  detectGlobalInstall: async () => {
452
502
  return existsSync(gooseConfigPath);
453
503
  },
@@ -463,6 +513,7 @@ var agents = {
463
513
  localConfigKey: "servers",
464
514
  format: "json",
465
515
  supportedTransports: ["stdio", "http", "sse"],
516
+ supportedFields: [],
466
517
  detectGlobalInstall: async () => {
467
518
  return existsSync(dirname2(copilotConfigPath));
468
519
  },
@@ -477,10 +528,12 @@ var agents = {
477
528
  configKey: "mcpServers",
478
529
  format: "json",
479
530
  supportedTransports: ["stdio", "http", "sse"],
531
+ supportedFields: [],
480
532
  detectGlobalInstall: async () => {
481
533
  return existsSync(join2(home, ".mcporter"));
482
534
  },
483
- resolveConfigPath: resolveMcporterConfigPath
535
+ resolveConfigPath: resolveMcporterConfigPath,
536
+ transformConfig: transformStandardConfig
484
537
  },
485
538
  opencode: {
486
539
  name: "opencode",
@@ -491,6 +544,7 @@ var agents = {
491
544
  configKey: "mcp",
492
545
  format: "json",
493
546
  supportedTransports: ["stdio", "http", "sse"],
547
+ supportedFields: [],
494
548
  detectGlobalInstall: async () => {
495
549
  return existsSync(join2(home, ".config", "opencode"));
496
550
  },
@@ -505,9 +559,11 @@ var agents = {
505
559
  configKey: "servers",
506
560
  format: "json",
507
561
  supportedTransports: ["stdio", "http", "sse"],
562
+ supportedFields: [],
508
563
  detectGlobalInstall: async () => {
509
564
  return existsSync(vscodePath);
510
- }
565
+ },
566
+ transformConfig: transformStandardConfig
511
567
  },
512
568
  windsurf: {
513
569
  name: "windsurf",
@@ -518,6 +574,7 @@ var agents = {
518
574
  configKey: "mcpServers",
519
575
  format: "json",
520
576
  supportedTransports: ["stdio", "http", "sse"],
577
+ supportedFields: [],
521
578
  detectGlobalInstall: async () => {
522
579
  return existsSync(join2(home, ".codeium", "windsurf"));
523
580
  },
@@ -532,6 +589,7 @@ var agents = {
532
589
  configKey: "context_servers",
533
590
  format: "json",
534
591
  supportedTransports: ["stdio", "http", "sse"],
592
+ supportedFields: [],
535
593
  detectGlobalInstall: async () => {
536
594
  const configDir = process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed") : join2(appSupport, "zed");
537
595
  return existsSync(configDir);
@@ -1108,6 +1166,44 @@ function buildConfigWithKey(configKey, serverName, serverConfig) {
1108
1166
  return config;
1109
1167
  }
1110
1168
 
1169
+ // src/schema.ts
1170
+ var OPTIONAL_FIELD_SPECS = {
1171
+ timeout: {
1172
+ label: "request timeout",
1173
+ isSet: (config) => typeof config.timeout === "number",
1174
+ clear: (config) => {
1175
+ delete config.timeout;
1176
+ }
1177
+ },
1178
+ scopes: {
1179
+ label: "OAuth scopes",
1180
+ isSet: (config) => Array.isArray(config.oauthScopes) && config.oauthScopes.length > 0,
1181
+ clear: (config) => {
1182
+ delete config.oauthScopes;
1183
+ }
1184
+ }
1185
+ };
1186
+ var ALL_OPTIONAL_FIELDS = Object.keys(
1187
+ OPTIONAL_FIELD_SPECS
1188
+ );
1189
+ function describeOptionalField(field) {
1190
+ return OPTIONAL_FIELD_SPECS[field].label;
1191
+ }
1192
+ function applyFieldSupport(config, supportedFields) {
1193
+ const supported = new Set(supportedFields);
1194
+ const copy = { ...config };
1195
+ if (copy.oauthScopes) copy.oauthScopes = [...copy.oauthScopes];
1196
+ const dropped = [];
1197
+ for (const field of ALL_OPTIONAL_FIELDS) {
1198
+ const spec = OPTIONAL_FIELD_SPECS[field];
1199
+ if (spec.isSet(copy) && !supported.has(field)) {
1200
+ spec.clear(copy);
1201
+ dropped.push(field);
1202
+ }
1203
+ }
1204
+ return { config: copy, dropped };
1205
+ }
1206
+
1111
1207
  // src/installer.ts
1112
1208
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
1113
1209
  import { join as join3, dirname as dirname6, isAbsolute, relative, sep } from "path";
@@ -1120,6 +1216,12 @@ function buildServerConfig(parsed, options = {}) {
1120
1216
  if (options.headers && Object.keys(options.headers).length > 0) {
1121
1217
  config2.headers = options.headers;
1122
1218
  }
1219
+ if (typeof options.timeout === "number") {
1220
+ config2.timeout = options.timeout;
1221
+ }
1222
+ if (options.oauthScopes && options.oauthScopes.length > 0) {
1223
+ config2.oauthScopes = options.oauthScopes;
1224
+ }
1123
1225
  return config2;
1124
1226
  }
1125
1227
  if (parsed.type === "command") {
@@ -1212,15 +1314,20 @@ function installServerForAgent(serverName, serverConfig, agentType, options = {}
1212
1314
  if (!existsSync5(dir)) {
1213
1315
  mkdirSync4(dir, { recursive: true });
1214
1316
  }
1215
- const transformedConfig = agent.transformConfig ? agent.transformConfig(serverName, serverConfig, {
1317
+ const { config: gatedConfig, dropped } = applyFieldSupport(
1318
+ serverConfig,
1319
+ agent.supportedFields
1320
+ );
1321
+ const transformedConfig = agent.transformConfig(serverName, gatedConfig, {
1216
1322
  local: Boolean(options.local)
1217
- }) : serverConfig;
1323
+ });
1218
1324
  const configKey = getConfigKey(agent, options);
1219
1325
  const config = buildConfigWithKey(configKey, serverName, transformedConfig);
1220
1326
  writeConfig2(configPath, config, agent.format, configKey);
1221
1327
  return {
1222
1328
  success: true,
1223
- path: configPath
1329
+ path: configPath,
1330
+ ...dropped.length > 0 ? { droppedFields: dropped } : {}
1224
1331
  };
1225
1332
  } catch (error) {
1226
1333
  return {
@@ -1384,6 +1491,7 @@ export {
1384
1491
  getNestedValue,
1385
1492
  readConfig2 as readConfig,
1386
1493
  removeServerFromConfig,
1494
+ describeOptionalField,
1387
1495
  buildServerConfig,
1388
1496
  updateGitignoreWithPaths,
1389
1497
  getConfigPath2,
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  agents,
4
4
  buildAgentSelectionChoices,
5
5
  buildServerConfig,
6
+ describeOptionalField,
6
7
  detectGlobalAgents,
7
8
  detectProjectAgents,
8
9
  findMatchingServers,
@@ -22,7 +23,7 @@ import {
22
23
  selectAgentsInteractive,
23
24
  supportsProjectConfig,
24
25
  updateGitignoreWithPaths
25
- } from "./chunk-ONLHW5AV.js";
26
+ } from "./chunk-2OCTBM45.js";
26
27
 
27
28
  // src/index.ts
28
29
  import { program } from "commander";
@@ -782,7 +783,7 @@ async function runFind(query, options) {
782
783
  // package.json
783
784
  var package_default = {
784
785
  name: "add-mcp",
785
- version: "1.10.3",
786
+ version: "1.11.0",
786
787
  description: "Add MCP servers to your favorite coding agents with a single command.",
787
788
  author: "Andre Landgraf <andre@neon.tech>",
788
789
  license: "Apache-2.0",
@@ -973,7 +974,7 @@ function extractSubcommandOptionsFromArgv() {
973
974
  result.gitignore = true;
974
975
  continue;
975
976
  }
976
- if (arg === "--header" && argv[i + 1]) {
977
+ if ((arg === "-h" || arg === "--header") && argv[i + 1]) {
977
978
  const headers = result.header ? [...result.header] : [];
978
979
  headers.push(argv[i + 1]);
979
980
  result.header = headers;
@@ -1095,7 +1096,7 @@ function omitEmptyStringValues(record) {
1095
1096
  }
1096
1097
  program.name("add-mcp").description(
1097
1098
  "Install MCP servers for coding agents (Claude Code, Cursor, VS Code, OpenCode, Codex, and more \u2014 run list-agents for the full list)"
1098
- ).version(version).argument("[target]", "MCP server URL (remote) or package name (local stdio)").option(
1099
+ ).version(version).helpOption("--help", "display help for command").argument("[target]", "MCP server URL (remote) or package name (local stdio)").option(
1099
1100
  "-g, --global",
1100
1101
  "Install globally (user-level) instead of project-level"
1101
1102
  ).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
@@ -1105,7 +1106,7 @@ program.name("add-mcp").description(
1105
1106
  "-t, --transport <type>",
1106
1107
  "Transport type for remote servers (http, sse)"
1107
1108
  ).option("--type <type>", "Alias for --transport").option(
1108
- "--header <header>",
1109
+ "-h, --header <header>",
1109
1110
  "HTTP header for remote servers (repeatable, 'Key: Value'). Placeholders ${VAR} prompt interactively when not using --yes. Use single quotes so your shell does not expand the ${VAR}.",
1110
1111
  collect,
1111
1112
  []
@@ -1119,7 +1120,13 @@ program.name("add-mcp").description(
1119
1120
  "Argument for local stdio servers (repeatable). Placeholders ${VAR} prompt interactively when not using --yes. Use single quotes so your shell does not expand the ${VAR}.",
1120
1121
  collect,
1121
1122
  []
1122
- ).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) => {
1123
+ ).option(
1124
+ "--timeout <ms>",
1125
+ "Request timeout in milliseconds for remote servers. Only applied to agents that support it (e.g. Claude Code, Gemini CLI); dropped with a warning elsewhere."
1126
+ ).option(
1127
+ "--scopes <scopes>",
1128
+ "OAuth scopes to request for remote servers (comma-separated). Only applied to agents that support it (e.g. Cursor, Gemini CLI); dropped with a warning elsewhere."
1129
+ ).option("--oauth-scopes <scopes>", "Alias for --scopes").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) => {
1123
1130
  await main(target, options);
1124
1131
  });
1125
1132
  program.command("list-agents").description("List all supported coding agents").action(() => {
@@ -1791,11 +1798,44 @@ async function main(target, options) {
1791
1798
  p2.log.warn("--transport is only used for remote URLs, ignoring");
1792
1799
  }
1793
1800
  }
1801
+ let resolvedTimeout;
1802
+ if (options.timeout !== void 0) {
1803
+ const parsedTimeout = Number(options.timeout);
1804
+ if (!Number.isInteger(parsedTimeout) || parsedTimeout <= 0) {
1805
+ p2.log.error(
1806
+ `Invalid --timeout value: ${options.timeout}. Provide a positive integer (milliseconds).`
1807
+ );
1808
+ process.exit(1);
1809
+ }
1810
+ if (isRemote) {
1811
+ resolvedTimeout = parsedTimeout;
1812
+ } else {
1813
+ p2.log.warn("--timeout is only used for remote URLs, ignoring");
1814
+ }
1815
+ }
1816
+ const scopesValue = options.scopes ?? options.oauthScopes;
1817
+ let resolvedScopes;
1818
+ if (scopesValue !== void 0) {
1819
+ const parsedScopes = scopesValue.split(",").map((scope) => scope.trim()).filter((scope) => scope.length > 0);
1820
+ if (parsedScopes.length === 0) {
1821
+ p2.log.error(
1822
+ `Invalid --scopes value: ${scopesValue}. Provide one or more comma-separated scopes.`
1823
+ );
1824
+ process.exit(1);
1825
+ }
1826
+ if (isRemote) {
1827
+ resolvedScopes = parsedScopes;
1828
+ } else {
1829
+ p2.log.warn("--scopes is only used for remote URLs, ignoring");
1830
+ }
1831
+ }
1794
1832
  const serverConfig = buildServerConfig(parsed, {
1795
1833
  transport: resolvedTransport,
1796
1834
  headers: headersForConfig && Object.keys(headersForConfig).length > 0 ? headersForConfig : void 0,
1797
1835
  env: envForConfig && Object.keys(envForConfig).length > 0 ? envForConfig : void 0,
1798
- args: argsForConfig && argsForConfig.length > 0 ? argsForConfig : void 0
1836
+ args: argsForConfig && argsForConfig.length > 0 ? argsForConfig : void 0,
1837
+ timeout: resolvedTimeout,
1838
+ oauthScopes: resolvedScopes
1799
1839
  });
1800
1840
  let targetAgents;
1801
1841
  const allAgentTypes = getAgentTypes();
@@ -2065,6 +2105,19 @@ async function main(target, options) {
2065
2105
  );
2066
2106
  }
2067
2107
  }
2108
+ const droppedByField = /* @__PURE__ */ new Map();
2109
+ for (const [agentType, result] of results) {
2110
+ for (const field of result.droppedFields ?? []) {
2111
+ const agentNames = droppedByField.get(field) ?? [];
2112
+ agentNames.push(agents[agentType].displayName);
2113
+ droppedByField.set(field, agentNames);
2114
+ }
2115
+ }
2116
+ for (const [field, agentNames] of droppedByField) {
2117
+ p2.log.warn(
2118
+ `${describeOptionalField(field)} is not supported by ${agentNames.join(", ")}; dropped from ${agentNames.length === 1 ? "that config" : "those configs"}.`
2119
+ );
2120
+ }
2068
2121
  if (options.gitignore && options.global) {
2069
2122
  p2.log.warn(
2070
2123
  "--gitignore is only supported for project-scoped installations; ignoring."
package/dist/lib.d.ts CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Optional, capability-gated fields on {@link McpServerConfig}.
3
+ *
4
+ * The canonical {@link McpServerConfig} is the single, agent-agnostic schema
5
+ * that the CLI and library populate. Not every MCP client understands every
6
+ * optional field, so each agent declares which of these it supports via
7
+ * `AgentConfig.supportedFields`. Anything an agent does not support is dropped
8
+ * before its transform runs, guaranteeing that only known fields are ever
9
+ * written to a client config.
10
+ */
11
+ type OptionalField = "timeout" | "scopes";
12
+
1
13
  type AgentType = "antigravity" | "cline" | "cline-cli" | "claude-code" | "claude-desktop" | "codex" | "cursor" | "gemini-cli" | "goose" | "github-copilot-cli" | "mcporter" | "opencode" | "vscode" | "windsurf" | "zed";
2
14
  type ConfigFormat = "json" | "yaml" | "toml";
3
15
  interface AgentConfig {
@@ -19,6 +31,14 @@ interface AgentConfig {
19
31
  format: ConfigFormat;
20
32
  /** Supported transport types for this agent */
21
33
  supportedTransports: ("stdio" | "sse" | "http")[];
34
+ /**
35
+ * Optional, capability-gated server fields this agent understands (e.g.
36
+ * `timeout`, `scopes`). Any optional field not listed here is stripped from
37
+ * the canonical config before {@link transformConfig} runs, so a client never
38
+ * receives a field it cannot interpret. Use an empty array for agents that
39
+ * only support the core fields.
40
+ */
41
+ supportedFields: OptionalField[];
22
42
  /** Shown when a user tries to use an unsupported transport */
23
43
  unsupportedTransportMessage?: string;
24
44
  /** Function to detect if agent is installed globally */
@@ -28,8 +48,13 @@ interface AgentConfig {
28
48
  local: boolean;
29
49
  cwd: string;
30
50
  }) => string;
31
- /** Optional function to transform server config to agent-specific format */
32
- transformConfig?: (serverName: string, config: McpServerConfig, context?: {
51
+ /**
52
+ * Transform the canonical, field-gated server config into this agent's
53
+ * concrete on-disk schema. Required for every agent: transforms must build a
54
+ * fresh object with only the keys the client understands, which structurally
55
+ * guarantees no unknown fields leak into a written config.
56
+ */
57
+ transformConfig: (serverName: string, config: McpServerConfig, context?: {
33
58
  local: boolean;
34
59
  }) => unknown;
35
60
  }
@@ -43,6 +68,17 @@ interface McpServerConfig {
43
68
  command?: string;
44
69
  args?: string[];
45
70
  env?: Record<string, string>;
71
+ /**
72
+ * Request timeout in milliseconds for remote servers. Capability-gated:
73
+ * only emitted for agents that list `"timeout"` in `supportedFields`.
74
+ */
75
+ timeout?: number;
76
+ /**
77
+ * OAuth scopes to request for remote servers. Capability-gated: only emitted
78
+ * for agents that list `"scopes"` in `supportedFields`, each mapping it into
79
+ * their own native shape (e.g. Cursor `auth.scopes`, Gemini `oauth.scopes`).
80
+ */
81
+ oauthScopes?: string[];
46
82
  }
47
83
 
48
84
  interface InstallOptions {
@@ -55,6 +91,11 @@ interface InstallResult {
55
91
  success: boolean;
56
92
  path: string;
57
93
  error?: string;
94
+ /**
95
+ * Optional fields that were requested but dropped because the target agent
96
+ * does not support them. Empty/undefined when nothing was dropped.
97
+ */
98
+ droppedFields?: OptionalField[];
58
99
  }
59
100
 
60
101
  declare const agents: Record<AgentType, AgentConfig>;
@@ -99,4 +140,4 @@ interface RemoveServerResult {
99
140
  }
100
141
  declare function removeServer(agentType: AgentInput, serverName: string, options?: InstallOptions): RemoveServerResult;
101
142
 
102
- export { type AgentConfig, type AgentInput, type AgentServers, type AgentType, type InstallOptions, type InstallResult, type InstalledServer, type McpServerConfig, type RemoveServerResult, agents, detectGlobalAgents, detectProjectAgents, getAgentTypes, listInstalledServers, removeServer, upsertServer };
143
+ export { type AgentConfig, type AgentInput, type AgentServers, type AgentType, type InstallOptions, type InstallResult, type InstalledServer, type McpServerConfig, type OptionalField, type RemoveServerResult, agents, detectGlobalAgents, detectProjectAgents, getAgentTypes, listInstalledServers, removeServer, upsertServer };
package/dist/lib.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  listInstalledServers,
11
11
  readConfig,
12
12
  removeServerFromConfig
13
- } from "./chunk-ONLHW5AV.js";
13
+ } from "./chunk-2OCTBM45.js";
14
14
 
15
15
  // src/lib.ts
16
16
  import { existsSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-mcp",
3
- "version": "1.10.3",
3
+ "version": "1.11.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",