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 +38 -1
- package/dist/{chunk-ONLHW5AV.js → chunk-2OCTBM45.js} +117 -9
- package/dist/index.js +60 -7
- package/dist/lib.d.ts +44 -3
- package/dist/lib.js +1 -1
- package/package.json +1 -1
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
|
-
|
|
|
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
|
|
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
|
-
})
|
|
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-
|
|
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.
|
|
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(
|
|
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
|
-
/**
|
|
32
|
-
|
|
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