add-mcp 1.10.4 → 1.12.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
@@ -50,7 +50,7 @@ MCP servers can be installed to any of these agents:
50
50
 
51
51
  | Agent | `--agent` | Project Path | Global Path |
52
52
  | ---------------------- | -------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------- |
53
- | Antigravity | `antigravity` | - | `~/.gemini/antigravity/mcp_config.json` |
53
+ | Antigravity | `antigravity` | - | `~/.gemini/config/mcp_config.json` (shared by Antigravity, Antigravity IDE, and Antigravity CLI) |
54
54
  | Cline VSCode Extension | `cline` | - | `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` |
55
55
  | Cline CLI | `cline-cli` | - | `~/.cline/data/settings/cline_mcp_settings.json` |
56
56
  | Claude Code | `claude-code` | `.mcp.json` | `~/.claude.json` |
@@ -124,6 +124,16 @@ npx add-mcp https://mcp.example.com/sse --transport sse
124
124
  # Remote MCP server with auth header
125
125
  npx add-mcp https://mcp.example.com/mcp --header "Authorization: Bearer $TOKEN"
126
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
+
131
+ # Auto-approve all tools for agents that support it (Codex, Claude Code)
132
+ npx add-mcp "executor mcp" --name executor -a codex -a claude-code --auto-approve
133
+
134
+ # Auto-approve only selected tools
135
+ npx add-mcp "executor mcp" --name executor -a codex --auto-approve --approve-tool execute
136
+
127
137
  # npm package (runs via npx)
128
138
  npx add-mcp @modelcontextprotocol/server-postgres
129
139
 
@@ -160,18 +170,60 @@ npx add-mcp https://mcp.example.com/mcp -a cursor -y --gitignore
160
170
 
161
171
  ### Options
162
172
 
163
- | Option | Description |
164
- | ------------------------ | ------------------------------------------------------------------------ |
165
- | `-g, --global` | Install to user directory instead of project |
166
- | `-a, --agent <agent>` | Target specific agents (e.g., `cursor`, `claude-code`). Can be repeated. |
167
- | `-t, --transport <type>` | Transport type for remote servers: `http` (default), `sse` |
168
- | `--type <type>` | Alias for `--transport` |
169
- | `-h, --header <header>` | HTTP header for remote servers (repeatable, `Key: Value`) |
170
- | `--env <env>` | Env var for local stdio servers (repeatable, `KEY=VALUE`) |
171
- | `-n, --name <name>` | Server name (auto-inferred if not provided) |
172
- | `-y, --yes` | Skip all confirmation prompts |
173
- | `--all` | Install to all agents |
174
- | `--gitignore` | Add generated config files to `.gitignore` |
173
+ | Option | Description |
174
+ | ------------------------- | ------------------------------------------------------------------------ |
175
+ | `-g, --global` | Install to user directory instead of project |
176
+ | `-a, --agent <agent>` | Target specific agents (e.g., `cursor`, `claude-code`). Can be repeated. |
177
+ | `-t, --transport <type>` | Transport type for remote servers: `http` (default), `sse` |
178
+ | `--type <type>` | Alias for `--transport` |
179
+ | `-h, --header <header>` | HTTP header for remote servers (repeatable, `Key: Value`) |
180
+ | `--env <env>` | Env var for local stdio servers (repeatable, `KEY=VALUE`) |
181
+ | `--timeout <ms>` | Request timeout (ms) for remote servers (capability-gated, see below) |
182
+ | `--scopes <scopes>` | OAuth scopes for remote servers, comma-separated (capability-gated) |
183
+ | `--oauth-scopes <scopes>` | Alias for `--scopes` |
184
+ | `--auto-approve` | Auto-approve MCP tool calls for supported agents (Codex, Claude Code) |
185
+ | `--approve-tool <tool>` | Tool to auto-approve with `--auto-approve` (repeatable; defaults to all) |
186
+ | `-n, --name <name>` | Server name (auto-inferred if not provided) |
187
+ | `-y, --yes` | Skip all confirmation prompts |
188
+ | `--all` | Install to all agents |
189
+ | `--gitignore` | Add generated config files to `.gitignore` |
190
+
191
+ #### Capability-gated fields (`--timeout`, `--scopes`)
192
+
193
+ Not every MCP client understands every field. `add-mcp` keeps one canonical
194
+ server config and each agent declares which optional fields it supports, mapping
195
+ them into that client's native shape:
196
+
197
+ | Field | Flag | Supported by | Mapped to |
198
+ | ------------------ | ----------------------------------- | ----------------------- | -------------------------------------------------------- |
199
+ | Timeout | `--timeout` | Claude Code, Gemini CLI | `timeout` (milliseconds) |
200
+ | OAuth scopes | `--scopes` | Cursor, Gemini CLI | Cursor `auth.scopes`, Gemini `oauth.scopes` |
201
+ | Tool auto-approval | `--auto-approve` / `--approve-tool` | Codex, Claude Code | Codex approval modes; Claude Code permission allow rules |
202
+
203
+ When you target an agent that does not support a field, `add-mcp` drops it from
204
+ that agent's config and prints a warning (e.g. _"request timeout is not
205
+ supported by VS Code; dropped from that config."_). Other agents still receive
206
+ it. `--timeout` and `--scopes` apply to remote servers only; `--auto-approve`
207
+ applies to both remote and local servers.
208
+
209
+ #### Auto-approving tool calls (`--auto-approve`)
210
+
211
+ `--auto-approve` preconfigures agent-level approval so the agent doesn't prompt
212
+ before each MCP tool call — useful for servers that already gate actions
213
+ internally. Use `--approve-tool <name>` (repeatable) to approve only specific
214
+ tools; without it, all tools are approved.
215
+
216
+ - **Codex** — writes approval modes into `config.toml`: per-tool
217
+ `tools.<name>.approval_mode = "approve"`, or `default_tools_approval_mode = "approve"` for all tools.
218
+ - **Claude Code** — writes permission allow rules to a separate settings file
219
+ (`.claude/settings.local.json` for project installs, `~/.claude/settings.json`
220
+ for global), e.g. `mcp__<server>__<tool>`, or `mcp__<server>` for all tools.
221
+ The MCP server entry itself stays clean.
222
+
223
+ > Note: Claude Code's all-tools rule (`mcp__<server>`) follows the documented
224
+ > format, but a known Claude Code bug ([#34739](https://github.com/anthropics/claude-code/issues/34739))
225
+ > can still prompt for non-fully-qualified MCP rules. Listing tools explicitly
226
+ > with `--approve-tool` is the most reliable path there.
175
227
 
176
228
  ### Transport Types
177
229
 
@@ -209,14 +261,16 @@ npx add-mcp find github --all --gitignore
209
261
 
210
262
  ### Options
211
263
 
212
- | Option | Description |
213
- | --------------------- | ------------------------------------------------------------------------ |
214
- | `-g, --global` | Install to user directory instead of project |
215
- | `-a, --agent <agent>` | Target specific agents (e.g., `cursor`, `claude-code`). Can be repeated. |
216
- | `-n, --name <name>` | Server name override (defaults to the selected catalog entry name) |
217
- | `-y, --yes` | Skip confirmation prompts |
218
- | `--all` | Install to all agents |
219
- | `--gitignore` | Add generated config files to `.gitignore` |
264
+ | Option | Description |
265
+ | ----------------------- | ------------------------------------------------------------------------ |
266
+ | `-g, --global` | Install to user directory instead of project |
267
+ | `-a, --agent <agent>` | Target specific agents (e.g., `cursor`, `claude-code`). Can be repeated. |
268
+ | `-n, --name <name>` | Server name override (defaults to the selected catalog entry name) |
269
+ | `-y, --yes` | Skip confirmation prompts |
270
+ | `--auto-approve` | Auto-approve MCP tool calls for supported agents (Codex, Claude Code) |
271
+ | `--approve-tool <tool>` | Tool to auto-approve with `--auto-approve` (repeatable; defaults to all) |
272
+ | `--all` | Install to all agents |
273
+ | `--gitignore` | Add generated config files to `.gitignore` |
220
274
 
221
275
  Transport for `find`/`search` is inferred from registry metadata. The CLI prefers HTTP remotes when available and only falls back to SSE when HTTP is not available for the selected install context.
222
276
 
@@ -133,7 +133,7 @@ var { appSupport, vscodePath, gooseConfigPath } = getPlatformPaths();
133
133
  var antigravityConfigPath = join2(
134
134
  home,
135
135
  ".gemini",
136
- "antigravity",
136
+ "config",
137
137
  "mcp_config.json"
138
138
  );
139
139
  var clineCliConfigPath = join2(
@@ -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";
@@ -215,6 +250,17 @@ function transformOpenCodeConfig(_serverName, config) {
215
250
  environment: config.env || {}
216
251
  };
217
252
  }
253
+ function applyCodexApproval(target, autoApproveTools) {
254
+ if (!autoApproveTools) return target;
255
+ if (autoApproveTools.length === 0) {
256
+ target.default_tools_approval_mode = "approve";
257
+ return target;
258
+ }
259
+ target.tools = Object.fromEntries(
260
+ autoApproveTools.map((tool) => [tool, { approval_mode: "approve" }])
261
+ );
262
+ return target;
263
+ }
218
264
  function transformCodexConfig(_serverName, config) {
219
265
  if (config.url) {
220
266
  const remoteConfig = {
@@ -224,13 +270,16 @@ function transformCodexConfig(_serverName, config) {
224
270
  if (config.headers && Object.keys(config.headers).length > 0) {
225
271
  remoteConfig.http_headers = config.headers;
226
272
  }
227
- return remoteConfig;
273
+ return applyCodexApproval(remoteConfig, config.autoApproveTools);
228
274
  }
229
- return {
230
- command: config.command,
231
- args: config.args || [],
232
- env: config.env
233
- };
275
+ return applyCodexApproval(
276
+ {
277
+ command: config.command,
278
+ args: config.args || [],
279
+ env: config.env
280
+ },
281
+ config.autoApproveTools
282
+ );
234
283
  }
235
284
  function transformCursorConfig(_serverName, config) {
236
285
  if (config.url) {
@@ -240,9 +289,12 @@ function transformCursorConfig(_serverName, config) {
240
289
  if (config.headers && Object.keys(config.headers).length > 0) {
241
290
  remoteConfig.headers = config.headers;
242
291
  }
292
+ if (config.oauthScopes && config.oauthScopes.length > 0) {
293
+ remoteConfig.auth = { scopes: config.oauthScopes };
294
+ }
243
295
  return remoteConfig;
244
296
  }
245
- return config;
297
+ return buildStandardLocal(config);
246
298
  }
247
299
  function transformClineConfig(_serverName, config) {
248
300
  if (config.url) {
@@ -335,8 +387,9 @@ var agents = {
335
387
  configKey: "mcpServers",
336
388
  format: "json",
337
389
  supportedTransports: ["stdio", "http", "sse"],
390
+ supportedFields: [],
338
391
  detectGlobalInstall: async () => {
339
- return existsSync(join2(home, ".gemini"));
392
+ return existsSync(join2(home, ".gemini", "config"));
340
393
  },
341
394
  transformConfig: transformAntigravityConfig
342
395
  },
@@ -349,6 +402,7 @@ var agents = {
349
402
  configKey: "mcpServers",
350
403
  format: "json",
351
404
  supportedTransports: ["stdio", "http", "sse"],
405
+ supportedFields: [],
352
406
  detectGlobalInstall: async () => {
353
407
  return existsSync(dirname2(clineExtensionConfigPath));
354
408
  },
@@ -363,6 +417,7 @@ var agents = {
363
417
  configKey: "mcpServers",
364
418
  format: "json",
365
419
  supportedTransports: ["stdio", "http", "sse"],
420
+ supportedFields: [],
366
421
  detectGlobalInstall: async () => {
367
422
  return existsSync(dirname2(clineCliConfigPath));
368
423
  },
@@ -377,9 +432,11 @@ var agents = {
377
432
  configKey: "mcpServers",
378
433
  format: "json",
379
434
  supportedTransports: ["stdio", "http", "sse"],
435
+ supportedFields: ["timeout", "autoApprove"],
380
436
  detectGlobalInstall: async () => {
381
437
  return existsSync(join2(home, ".claude"));
382
- }
438
+ },
439
+ transformConfig: transformStandardConfig
383
440
  },
384
441
  "claude-desktop": {
385
442
  name: "claude-desktop",
@@ -390,10 +447,12 @@ var agents = {
390
447
  configKey: "mcpServers",
391
448
  format: "json",
392
449
  supportedTransports: ["stdio"],
450
+ supportedFields: [],
393
451
  unsupportedTransportMessage: "Claude Desktop only supports local (stdio) servers via its config file. Add remote servers through Settings \u2192 Connectors in the app instead.",
394
452
  detectGlobalInstall: async () => {
395
453
  return existsSync(join2(appSupport, "Claude"));
396
- }
454
+ },
455
+ transformConfig: transformStandardConfig
397
456
  },
398
457
  codex: {
399
458
  name: "codex",
@@ -407,6 +466,7 @@ var agents = {
407
466
  configKey: "mcp_servers",
408
467
  format: "toml",
409
468
  supportedTransports: ["stdio", "http", "sse"],
469
+ supportedFields: ["autoApprove"],
410
470
  detectGlobalInstall: async () => {
411
471
  return existsSync(join2(home, ".codex"));
412
472
  },
@@ -421,6 +481,7 @@ var agents = {
421
481
  configKey: "mcpServers",
422
482
  format: "json",
423
483
  supportedTransports: ["stdio", "http", "sse"],
484
+ supportedFields: ["scopes"],
424
485
  detectGlobalInstall: async () => {
425
486
  return existsSync(join2(home, ".cursor"));
426
487
  },
@@ -435,9 +496,11 @@ var agents = {
435
496
  configKey: "mcpServers",
436
497
  format: "json",
437
498
  supportedTransports: ["stdio", "http", "sse"],
499
+ supportedFields: ["timeout", "scopes"],
438
500
  detectGlobalInstall: async () => {
439
501
  return existsSync(join2(home, ".gemini"));
440
- }
502
+ },
503
+ transformConfig: transformGeminiConfig
441
504
  },
442
505
  goose: {
443
506
  name: "goose",
@@ -448,6 +511,7 @@ var agents = {
448
511
  configKey: "extensions",
449
512
  format: "yaml",
450
513
  supportedTransports: ["stdio", "http", "sse"],
514
+ supportedFields: [],
451
515
  detectGlobalInstall: async () => {
452
516
  return existsSync(gooseConfigPath);
453
517
  },
@@ -463,6 +527,7 @@ var agents = {
463
527
  localConfigKey: "servers",
464
528
  format: "json",
465
529
  supportedTransports: ["stdio", "http", "sse"],
530
+ supportedFields: [],
466
531
  detectGlobalInstall: async () => {
467
532
  return existsSync(dirname2(copilotConfigPath));
468
533
  },
@@ -477,10 +542,12 @@ var agents = {
477
542
  configKey: "mcpServers",
478
543
  format: "json",
479
544
  supportedTransports: ["stdio", "http", "sse"],
545
+ supportedFields: [],
480
546
  detectGlobalInstall: async () => {
481
547
  return existsSync(join2(home, ".mcporter"));
482
548
  },
483
- resolveConfigPath: resolveMcporterConfigPath
549
+ resolveConfigPath: resolveMcporterConfigPath,
550
+ transformConfig: transformStandardConfig
484
551
  },
485
552
  opencode: {
486
553
  name: "opencode",
@@ -491,6 +558,7 @@ var agents = {
491
558
  configKey: "mcp",
492
559
  format: "json",
493
560
  supportedTransports: ["stdio", "http", "sse"],
561
+ supportedFields: [],
494
562
  detectGlobalInstall: async () => {
495
563
  return existsSync(join2(home, ".config", "opencode"));
496
564
  },
@@ -505,9 +573,11 @@ var agents = {
505
573
  configKey: "servers",
506
574
  format: "json",
507
575
  supportedTransports: ["stdio", "http", "sse"],
576
+ supportedFields: [],
508
577
  detectGlobalInstall: async () => {
509
578
  return existsSync(vscodePath);
510
- }
579
+ },
580
+ transformConfig: transformStandardConfig
511
581
  },
512
582
  windsurf: {
513
583
  name: "windsurf",
@@ -518,6 +588,7 @@ var agents = {
518
588
  configKey: "mcpServers",
519
589
  format: "json",
520
590
  supportedTransports: ["stdio", "http", "sse"],
591
+ supportedFields: [],
521
592
  detectGlobalInstall: async () => {
522
593
  return existsSync(join2(home, ".codeium", "windsurf"));
523
594
  },
@@ -532,6 +603,7 @@ var agents = {
532
603
  configKey: "context_servers",
533
604
  format: "json",
534
605
  supportedTransports: ["stdio", "http", "sse"],
606
+ supportedFields: [],
535
607
  detectGlobalInstall: async () => {
536
608
  const configDir = process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed") : join2(appSupport, "zed");
537
609
  return existsSync(configDir);
@@ -1108,9 +1180,59 @@ function buildConfigWithKey(configKey, serverName, serverConfig) {
1108
1180
  return config;
1109
1181
  }
1110
1182
 
1183
+ // src/schema.ts
1184
+ var OPTIONAL_FIELD_SPECS = {
1185
+ timeout: {
1186
+ label: "request timeout",
1187
+ isSet: (config) => typeof config.timeout === "number",
1188
+ clear: (config) => {
1189
+ delete config.timeout;
1190
+ }
1191
+ },
1192
+ scopes: {
1193
+ label: "OAuth scopes",
1194
+ isSet: (config) => Array.isArray(config.oauthScopes) && config.oauthScopes.length > 0,
1195
+ clear: (config) => {
1196
+ delete config.oauthScopes;
1197
+ }
1198
+ },
1199
+ autoApprove: {
1200
+ label: "tool auto-approval",
1201
+ // An empty array is meaningful ("approve all tools"), so presence alone
1202
+ // counts as set — unlike scopes where an empty list means "nothing".
1203
+ isSet: (config) => Array.isArray(config.autoApproveTools),
1204
+ clear: (config) => {
1205
+ delete config.autoApproveTools;
1206
+ }
1207
+ }
1208
+ };
1209
+ var ALL_OPTIONAL_FIELDS = Object.keys(
1210
+ OPTIONAL_FIELD_SPECS
1211
+ );
1212
+ function describeOptionalField(field) {
1213
+ return OPTIONAL_FIELD_SPECS[field].label;
1214
+ }
1215
+ function applyFieldSupport(config, supportedFields) {
1216
+ const supported = new Set(supportedFields);
1217
+ const copy = { ...config };
1218
+ if (copy.oauthScopes) copy.oauthScopes = [...copy.oauthScopes];
1219
+ if (copy.autoApproveTools) copy.autoApproveTools = [...copy.autoApproveTools];
1220
+ const dropped = [];
1221
+ for (const field of ALL_OPTIONAL_FIELDS) {
1222
+ const spec = OPTIONAL_FIELD_SPECS[field];
1223
+ if (spec.isSet(copy) && !supported.has(field)) {
1224
+ spec.clear(copy);
1225
+ dropped.push(field);
1226
+ }
1227
+ }
1228
+ return { config: copy, dropped };
1229
+ }
1230
+
1111
1231
  // src/installer.ts
1112
1232
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
1233
+ import { homedir as homedir3 } from "os";
1113
1234
  import { join as join3, dirname as dirname6, isAbsolute, relative, sep } from "path";
1235
+ var home2 = homedir3();
1114
1236
  function buildServerConfig(parsed, options = {}) {
1115
1237
  if (parsed.type === "remote") {
1116
1238
  const config2 = {
@@ -1120,6 +1242,15 @@ function buildServerConfig(parsed, options = {}) {
1120
1242
  if (options.headers && Object.keys(options.headers).length > 0) {
1121
1243
  config2.headers = options.headers;
1122
1244
  }
1245
+ if (typeof options.timeout === "number") {
1246
+ config2.timeout = options.timeout;
1247
+ }
1248
+ if (options.oauthScopes && options.oauthScopes.length > 0) {
1249
+ config2.oauthScopes = options.oauthScopes;
1250
+ }
1251
+ if (options.autoApproveTools) {
1252
+ config2.autoApproveTools = options.autoApproveTools;
1253
+ }
1123
1254
  return config2;
1124
1255
  }
1125
1256
  if (parsed.type === "command") {
@@ -1141,6 +1272,9 @@ function buildServerConfig(parsed, options = {}) {
1141
1272
  if (options.env && Object.keys(options.env).length > 0) {
1142
1273
  config2.env = options.env;
1143
1274
  }
1275
+ if (options.autoApproveTools) {
1276
+ config2.autoApproveTools = options.autoApproveTools;
1277
+ }
1144
1278
  return config2;
1145
1279
  }
1146
1280
  const config = {
@@ -1150,8 +1284,66 @@ function buildServerConfig(parsed, options = {}) {
1150
1284
  if (options.env && Object.keys(options.env).length > 0) {
1151
1285
  config.env = options.env;
1152
1286
  }
1287
+ if (options.autoApproveTools) {
1288
+ config.autoApproveTools = options.autoApproveTools;
1289
+ }
1153
1290
  return config;
1154
1291
  }
1292
+ function readJsonObject(filePath) {
1293
+ if (!existsSync5(filePath)) {
1294
+ return {};
1295
+ }
1296
+ const parsed = JSON.parse(readFileSync4(filePath, "utf-8"));
1297
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1298
+ }
1299
+ function writeJsonObject(filePath, value) {
1300
+ const dir = dirname6(filePath);
1301
+ if (!existsSync5(dir)) {
1302
+ mkdirSync4(dir, { recursive: true });
1303
+ }
1304
+ writeFileSync4(filePath, `${JSON.stringify(value, null, 2)}
1305
+ `, "utf-8");
1306
+ }
1307
+ function addUniqueStrings(existing, additions) {
1308
+ const values = Array.isArray(existing) ? existing.filter((value) => typeof value === "string") : [];
1309
+ for (const addition of additions) {
1310
+ if (!values.includes(addition)) {
1311
+ values.push(addition);
1312
+ }
1313
+ }
1314
+ return values;
1315
+ }
1316
+ function claudeAutoApproveRules(serverName, tools) {
1317
+ if (tools.length === 0) {
1318
+ return [`mcp__${serverName}`];
1319
+ }
1320
+ return tools.map((tool) => `mcp__${serverName}__${tool}`);
1321
+ }
1322
+ function getClaudeCodeSettingsPath(options = {}) {
1323
+ const local = Boolean(options.local);
1324
+ const cwd = options.cwd || process.cwd();
1325
+ return local ? join3(cwd, ".claude", "settings.local.json") : join3(home2, ".claude", "settings.json");
1326
+ }
1327
+ function installClaudeCodeAutoApproval(serverName, tools, options = {}) {
1328
+ const settingsPath = getClaudeCodeSettingsPath(options);
1329
+ try {
1330
+ const settings = readJsonObject(settingsPath);
1331
+ const permissions = settings.permissions && typeof settings.permissions === "object" && !Array.isArray(settings.permissions) ? settings.permissions : {};
1332
+ permissions.allow = addUniqueStrings(
1333
+ permissions.allow,
1334
+ claudeAutoApproveRules(serverName, tools)
1335
+ );
1336
+ settings.permissions = permissions;
1337
+ writeJsonObject(settingsPath, settings);
1338
+ return { success: true, path: settingsPath };
1339
+ } catch (error) {
1340
+ return {
1341
+ success: false,
1342
+ path: settingsPath,
1343
+ error: error instanceof Error ? error.message : "Unknown error"
1344
+ };
1345
+ }
1346
+ }
1155
1347
  function updateGitignoreWithPaths(paths, options = {}) {
1156
1348
  const cwd = options.cwd || process.cwd();
1157
1349
  const gitignorePath = join3(cwd, ".gitignore");
@@ -1212,15 +1404,37 @@ function installServerForAgent(serverName, serverConfig, agentType, options = {}
1212
1404
  if (!existsSync5(dir)) {
1213
1405
  mkdirSync4(dir, { recursive: true });
1214
1406
  }
1215
- const transformedConfig = agent.transformConfig ? agent.transformConfig(serverName, serverConfig, {
1407
+ const { config: gatedConfig, dropped } = applyFieldSupport(
1408
+ serverConfig,
1409
+ agent.supportedFields
1410
+ );
1411
+ const transformedConfig = agent.transformConfig(serverName, gatedConfig, {
1216
1412
  local: Boolean(options.local)
1217
- }) : serverConfig;
1413
+ });
1218
1414
  const configKey = getConfigKey(agent, options);
1219
1415
  const config = buildConfigWithKey(configKey, serverName, transformedConfig);
1220
1416
  writeConfig2(configPath, config, agent.format, configKey);
1417
+ const extraPaths = [];
1418
+ if (agentType === "claude-code" && gatedConfig.autoApproveTools) {
1419
+ const approval = installClaudeCodeAutoApproval(
1420
+ serverName,
1421
+ gatedConfig.autoApproveTools,
1422
+ options
1423
+ );
1424
+ if (!approval.success) {
1425
+ return {
1426
+ success: false,
1427
+ path: approval.path,
1428
+ error: approval.error
1429
+ };
1430
+ }
1431
+ extraPaths.push(approval.path);
1432
+ }
1221
1433
  return {
1222
1434
  success: true,
1223
- path: configPath
1435
+ path: configPath,
1436
+ ...dropped.length > 0 ? { droppedFields: dropped } : {},
1437
+ ...extraPaths.length > 0 ? { extraPaths } : {}
1224
1438
  };
1225
1439
  } catch (error) {
1226
1440
  return {
@@ -1384,6 +1598,7 @@ export {
1384
1598
  getNestedValue,
1385
1599
  readConfig2 as readConfig,
1386
1600
  removeServerFromConfig,
1601
+ describeOptionalField,
1387
1602
  buildServerConfig,
1388
1603
  updateGitignoreWithPaths,
1389
1604
  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-IPCKQUBH.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.4",
786
+ version: "1.12.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,6 +974,25 @@ function extractSubcommandOptionsFromArgv() {
973
974
  result.gitignore = true;
974
975
  continue;
975
976
  }
977
+ if (arg === "--auto-approve") {
978
+ result.autoApprove = true;
979
+ continue;
980
+ }
981
+ if (arg === "--approve-tool") {
982
+ const tools = result.approveTool ? [...result.approveTool] : [];
983
+ let j = i + 1;
984
+ while (j < argv.length) {
985
+ const value = argv[j];
986
+ if (!value || value.startsWith("-")) break;
987
+ tools.push(value);
988
+ j += 1;
989
+ }
990
+ if (tools.length > 0) {
991
+ result.approveTool = tools;
992
+ }
993
+ i = j - 1;
994
+ continue;
995
+ }
976
996
  if ((arg === "-h" || arg === "--header") && argv[i + 1]) {
977
997
  const headers = result.header ? [...result.header] : [];
978
998
  headers.push(argv[i + 1]);
@@ -1119,6 +1139,20 @@ program.name("add-mcp").description(
1119
1139
  "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
1140
  collect,
1121
1141
  []
1142
+ ).option(
1143
+ "--timeout <ms>",
1144
+ "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."
1145
+ ).option(
1146
+ "--scopes <scopes>",
1147
+ "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."
1148
+ ).option("--oauth-scopes <scopes>", "Alias for --scopes").option(
1149
+ "--auto-approve",
1150
+ "Auto-approve MCP tool calls for agents that support it (Codex, Claude Code). Dropped with a warning for other agents."
1151
+ ).option(
1152
+ "--approve-tool <tool>",
1153
+ "Tool name to auto-approve when --auto-approve is set (repeatable; defaults to all tools)",
1154
+ collect,
1155
+ []
1122
1156
  ).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
1157
  await main(target, options);
1124
1158
  });
@@ -1165,7 +1199,15 @@ program.command("find [keyword]").description(
1165
1199
  ).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
1166
1200
  "-n, --name <name>",
1167
1201
  "Server name override (defaults to catalog entry name)"
1168
- ).option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(
1202
+ ).option("-y, --yes", "Skip confirmation prompts").option(
1203
+ "--auto-approve",
1204
+ "Auto-approve MCP tool calls for agents that support it (Codex, Claude Code)"
1205
+ ).option(
1206
+ "--approve-tool <tool>",
1207
+ "Tool name to auto-approve when --auto-approve is set (repeatable; defaults to all tools)",
1208
+ collect,
1209
+ []
1210
+ ).option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(
1169
1211
  async (keyword, options) => {
1170
1212
  await runFindCommand(keyword, options);
1171
1213
  }
@@ -1176,7 +1218,15 @@ program.command("search [keyword]").description("Alias for find").option(
1176
1218
  ).option("-a, --agent <agent>", "Specify agents to install to", collect, []).option(
1177
1219
  "-n, --name <name>",
1178
1220
  "Server name override (defaults to catalog entry name)"
1179
- ).option("-y, --yes", "Skip confirmation prompts").option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(
1221
+ ).option("-y, --yes", "Skip confirmation prompts").option(
1222
+ "--auto-approve",
1223
+ "Auto-approve MCP tool calls for agents that support it (Codex, Claude Code)"
1224
+ ).option(
1225
+ "--approve-tool <tool>",
1226
+ "Tool name to auto-approve when --auto-approve is set (repeatable; defaults to all tools)",
1227
+ collect,
1228
+ []
1229
+ ).option("--all", "Install to all agents").option("--gitignore", "Add generated project config files to .gitignore").action(
1180
1230
  async (keyword, options) => {
1181
1231
  await runFindCommand(keyword, options);
1182
1232
  }
@@ -1791,11 +1841,47 @@ async function main(target, options) {
1791
1841
  p2.log.warn("--transport is only used for remote URLs, ignoring");
1792
1842
  }
1793
1843
  }
1844
+ let resolvedTimeout;
1845
+ if (options.timeout !== void 0) {
1846
+ const parsedTimeout = Number(options.timeout);
1847
+ if (!Number.isInteger(parsedTimeout) || parsedTimeout <= 0) {
1848
+ p2.log.error(
1849
+ `Invalid --timeout value: ${options.timeout}. Provide a positive integer (milliseconds).`
1850
+ );
1851
+ process.exit(1);
1852
+ }
1853
+ if (isRemote) {
1854
+ resolvedTimeout = parsedTimeout;
1855
+ } else {
1856
+ p2.log.warn("--timeout is only used for remote URLs, ignoring");
1857
+ }
1858
+ }
1859
+ const scopesValue = options.scopes ?? options.oauthScopes;
1860
+ let resolvedScopes;
1861
+ if (scopesValue !== void 0) {
1862
+ const parsedScopes = scopesValue.split(",").map((scope) => scope.trim()).filter((scope) => scope.length > 0);
1863
+ if (parsedScopes.length === 0) {
1864
+ p2.log.error(
1865
+ `Invalid --scopes value: ${scopesValue}. Provide one or more comma-separated scopes.`
1866
+ );
1867
+ process.exit(1);
1868
+ }
1869
+ if (isRemote) {
1870
+ resolvedScopes = parsedScopes;
1871
+ } else {
1872
+ p2.log.warn("--scopes is only used for remote URLs, ignoring");
1873
+ }
1874
+ }
1875
+ const approveTools = [...new Set(options.approveTool ?? [])];
1876
+ const autoApproveTools = options.autoApprove || approveTools.length > 0 ? approveTools : void 0;
1794
1877
  const serverConfig = buildServerConfig(parsed, {
1795
1878
  transport: resolvedTransport,
1796
1879
  headers: headersForConfig && Object.keys(headersForConfig).length > 0 ? headersForConfig : void 0,
1797
1880
  env: envForConfig && Object.keys(envForConfig).length > 0 ? envForConfig : void 0,
1798
- args: argsForConfig && argsForConfig.length > 0 ? argsForConfig : void 0
1881
+ args: argsForConfig && argsForConfig.length > 0 ? argsForConfig : void 0,
1882
+ timeout: resolvedTimeout,
1883
+ oauthScopes: resolvedScopes,
1884
+ autoApproveTools
1799
1885
  });
1800
1886
  let targetAgents;
1801
1887
  const allAgentTypes = getAgentTypes();
@@ -1991,6 +2077,11 @@ async function main(target, options) {
1991
2077
  const summaryLines = [];
1992
2078
  summaryLines.push(`${chalk.cyan("Server:")} ${serverName}`);
1993
2079
  summaryLines.push(`${chalk.cyan("Type:")} ${sourceType}`);
2080
+ if (autoApproveTools) {
2081
+ summaryLines.push(
2082
+ `${chalk.cyan("Auto-approve:")} ${autoApproveTools.length === 0 ? "All tools" : autoApproveTools.join(", ")}`
2083
+ );
2084
+ }
1994
2085
  const localAgents = targetAgents.filter(
1995
2086
  (a) => agentRouting.get(a) === "local"
1996
2087
  );
@@ -2043,6 +2134,11 @@ async function main(target, options) {
2043
2134
  resultLines.push(
2044
2135
  `${chalk.green("\u2713")} ${agent.displayName}: ${chalk.dim(shortPath)}`
2045
2136
  );
2137
+ for (const extraPath of result.extraPaths ?? []) {
2138
+ resultLines.push(
2139
+ ` ${chalk.dim("\u21B3 permissions:")} ${chalk.dim(shortenPath(extraPath))}`
2140
+ );
2141
+ }
2046
2142
  }
2047
2143
  p2.note(
2048
2144
  resultLines.join("\n"),
@@ -2065,12 +2161,28 @@ async function main(target, options) {
2065
2161
  );
2066
2162
  }
2067
2163
  }
2164
+ const droppedByField = /* @__PURE__ */ new Map();
2165
+ for (const [agentType, result] of results) {
2166
+ for (const field of result.droppedFields ?? []) {
2167
+ const agentNames = droppedByField.get(field) ?? [];
2168
+ agentNames.push(agents[agentType].displayName);
2169
+ droppedByField.set(field, agentNames);
2170
+ }
2171
+ }
2172
+ for (const [field, agentNames] of droppedByField) {
2173
+ p2.log.warn(
2174
+ `${describeOptionalField(field)} is not supported by ${agentNames.join(", ")}; dropped from ${agentNames.length === 1 ? "that config" : "those configs"}.`
2175
+ );
2176
+ }
2068
2177
  if (options.gitignore && options.global) {
2069
2178
  p2.log.warn(
2070
2179
  "--gitignore is only supported for project-scoped installations; ignoring."
2071
2180
  );
2072
2181
  } else if (options.gitignore) {
2073
- const successfulPaths = successful.map(([_, result]) => result.path);
2182
+ const successfulPaths = successful.flatMap(([_, result]) => [
2183
+ result.path,
2184
+ ...result.extraPaths ?? []
2185
+ ]);
2074
2186
  const gitignoreUpdate = updateGitignoreWithPaths(successfulPaths);
2075
2187
  if (gitignoreUpdate.added.length > 0) {
2076
2188
  p2.log.info(
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" | "autoApprove";
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,26 @@ 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[];
82
+ /**
83
+ * Tools to auto-approve (skip the agent's per-call approval prompt). An empty
84
+ * array means "all tools". This is an install directive rather than a literal
85
+ * server-config key: capability-gated via `"autoApprove"` in `supportedFields`
86
+ * and each supporting agent applies it in its own way (e.g. Codex emits
87
+ * approval modes in its config; Claude Code writes permission allow rules to a
88
+ * separate settings file). It is never written into a server entry verbatim.
89
+ */
90
+ autoApproveTools?: string[];
46
91
  }
47
92
 
48
93
  interface InstallOptions {
@@ -55,6 +100,17 @@ interface InstallResult {
55
100
  success: boolean;
56
101
  path: string;
57
102
  error?: string;
103
+ /**
104
+ * Optional fields that were requested but dropped because the target agent
105
+ * does not support them. Empty/undefined when nothing was dropped.
106
+ */
107
+ droppedFields?: OptionalField[];
108
+ /**
109
+ * Additional files written beyond the main config (e.g. a Claude Code
110
+ * permissions settings file for auto-approval). Surfaced so the CLI can
111
+ * report them and include them in `--gitignore`.
112
+ */
113
+ extraPaths?: string[];
58
114
  }
59
115
 
60
116
  declare const agents: Record<AgentType, AgentConfig>;
@@ -99,4 +155,4 @@ interface RemoveServerResult {
99
155
  }
100
156
  declare function removeServer(agentType: AgentInput, serverName: string, options?: InstallOptions): RemoveServerResult;
101
157
 
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 };
158
+ 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-IPCKQUBH.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.4",
3
+ "version": "1.12.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",