add-mcp 0.5.0 → 0.6.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 +3 -3
- package/dist/index.js +255 -106
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [5 more](#sup
|
|
|
7
7
|
## Install an MCP Server
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx add-mcp https://mcp.example.com/
|
|
10
|
+
npx add-mcp https://mcp.example.com/mcp
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
### Usage Examples
|
|
@@ -100,8 +100,8 @@ The CLI automatically detects agents based on your environment:
|
|
|
100
100
|
|
|
101
101
|
**No agents detected:**
|
|
102
102
|
|
|
103
|
-
- Interactive mode: Shows
|
|
104
|
-
- With `--yes`: Installs to all project-capable agents
|
|
103
|
+
- Interactive mode: Shows an agent selector (all available, last selection, or pick specific)
|
|
104
|
+
- With `--yes`: Installs to all project-capable agents (project mode) or all global-capable agents (global mode)
|
|
105
105
|
|
|
106
106
|
## Transport Types
|
|
107
107
|
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
|
-
import * as
|
|
5
|
+
import * as p2 from "@clack/prompts";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import { homedir as
|
|
7
|
+
import { homedir as homedir3 } from "os";
|
|
8
8
|
|
|
9
9
|
// src/types.ts
|
|
10
10
|
var agentAliases = {
|
|
@@ -12,45 +12,107 @@ var agentAliases = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
// src/agents.ts
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
15
|
+
import * as p from "@clack/prompts";
|
|
16
|
+
import { homedir as homedir2 } from "os";
|
|
17
|
+
import { join as join2 } from "path";
|
|
17
18
|
import { existsSync } from "fs";
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
// src/mcp-lock.ts
|
|
21
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
22
|
+
import { dirname, join } from "path";
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
var AGENTS_DIR = ".agents";
|
|
25
|
+
var LOCK_FILE = ".mcp-lock.json";
|
|
26
|
+
var CURRENT_VERSION = 1;
|
|
27
|
+
function getMcpLockPath() {
|
|
28
|
+
return join(homedir(), AGENTS_DIR, LOCK_FILE);
|
|
29
|
+
}
|
|
30
|
+
async function readMcpLock() {
|
|
31
|
+
const lockPath = getMcpLockPath();
|
|
32
|
+
try {
|
|
33
|
+
const content = await readFile(lockPath, "utf-8");
|
|
34
|
+
const parsed = JSON.parse(content);
|
|
35
|
+
if (typeof parsed.version !== "number") {
|
|
36
|
+
return createEmptyLockFile();
|
|
37
|
+
}
|
|
38
|
+
if (parsed.version < CURRENT_VERSION) {
|
|
39
|
+
return createEmptyLockFile();
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
} catch {
|
|
43
|
+
return createEmptyLockFile();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function writeMcpLock(lock) {
|
|
47
|
+
const lockPath = getMcpLockPath();
|
|
48
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
49
|
+
const content = JSON.stringify(lock, null, 2);
|
|
50
|
+
await writeFile(lockPath, content, "utf-8");
|
|
51
|
+
}
|
|
52
|
+
async function getLastSelectedAgents() {
|
|
53
|
+
const lock = await readMcpLock();
|
|
54
|
+
return lock.lastSelectedAgents;
|
|
55
|
+
}
|
|
56
|
+
async function saveSelectedAgents(agents2) {
|
|
57
|
+
const lock = await readMcpLock();
|
|
58
|
+
lock.lastSelectedAgents = agents2;
|
|
59
|
+
await writeMcpLock(lock);
|
|
60
|
+
}
|
|
61
|
+
function createEmptyLockFile() {
|
|
62
|
+
return {
|
|
63
|
+
version: CURRENT_VERSION
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/agents.ts
|
|
68
|
+
var home = homedir2();
|
|
69
|
+
function shortenPath(fullPath) {
|
|
70
|
+
if (fullPath.startsWith(home)) {
|
|
71
|
+
return fullPath.replace(home, "~");
|
|
72
|
+
}
|
|
73
|
+
return fullPath;
|
|
74
|
+
}
|
|
19
75
|
function getPlatformPaths() {
|
|
20
76
|
const platform = process.platform;
|
|
21
77
|
if (platform === "win32") {
|
|
22
|
-
const appData = process.env.APPDATA ||
|
|
78
|
+
const appData = process.env.APPDATA || join2(home, "AppData", "Roaming");
|
|
23
79
|
return {
|
|
24
80
|
appSupport: appData,
|
|
25
|
-
vscodePath:
|
|
81
|
+
vscodePath: join2(appData, "Code", "User"),
|
|
82
|
+
gooseConfigPath: join2(appData, "Block", "goose", "config", "config.yaml")
|
|
26
83
|
};
|
|
27
84
|
} else if (platform === "darwin") {
|
|
28
85
|
return {
|
|
29
|
-
appSupport:
|
|
30
|
-
vscodePath:
|
|
86
|
+
appSupport: join2(home, "Library", "Application Support"),
|
|
87
|
+
vscodePath: join2(home, "Library", "Application Support", "Code", "User"),
|
|
88
|
+
gooseConfigPath: join2(home, ".config", "goose", "config.yaml")
|
|
31
89
|
};
|
|
32
90
|
} else {
|
|
33
|
-
const configDir = process.env.XDG_CONFIG_HOME ||
|
|
91
|
+
const configDir = process.env.XDG_CONFIG_HOME || join2(home, ".config");
|
|
34
92
|
return {
|
|
35
93
|
appSupport: configDir,
|
|
36
|
-
vscodePath:
|
|
94
|
+
vscodePath: join2(configDir, "Code", "User"),
|
|
95
|
+
gooseConfigPath: join2(configDir, "goose", "config.yaml")
|
|
37
96
|
};
|
|
38
97
|
}
|
|
39
98
|
}
|
|
40
|
-
var { appSupport, vscodePath } = getPlatformPaths();
|
|
99
|
+
var { appSupport, vscodePath, gooseConfigPath } = getPlatformPaths();
|
|
41
100
|
function transformGooseConfig(serverName, config) {
|
|
42
101
|
if (config.url) {
|
|
43
102
|
const gooseType = config.type === "sse" ? "sse" : "streamable_http";
|
|
44
103
|
return {
|
|
45
104
|
name: serverName,
|
|
105
|
+
description: "",
|
|
46
106
|
type: gooseType,
|
|
47
|
-
|
|
107
|
+
uri: config.url,
|
|
108
|
+
headers: config.headers || {},
|
|
48
109
|
enabled: true,
|
|
49
110
|
timeout: 300
|
|
50
111
|
};
|
|
51
112
|
}
|
|
52
113
|
return {
|
|
53
114
|
name: serverName,
|
|
115
|
+
description: "",
|
|
54
116
|
cmd: config.command,
|
|
55
117
|
args: config.args || [],
|
|
56
118
|
enabled: true,
|
|
@@ -110,7 +172,7 @@ var agents = {
|
|
|
110
172
|
"claude-code": {
|
|
111
173
|
name: "claude-code",
|
|
112
174
|
displayName: "Claude Code",
|
|
113
|
-
configPath:
|
|
175
|
+
configPath: join2(home, ".claude.json"),
|
|
114
176
|
localConfigPath: ".mcp.json",
|
|
115
177
|
projectDetectPaths: [".mcp.json", ".claude"],
|
|
116
178
|
configKey: "mcpServers",
|
|
@@ -118,13 +180,13 @@ var agents = {
|
|
|
118
180
|
supportedTransports: ["stdio", "http", "sse"],
|
|
119
181
|
supportsHeaders: true,
|
|
120
182
|
detectGlobalInstall: async () => {
|
|
121
|
-
return existsSync(
|
|
183
|
+
return existsSync(join2(home, ".claude"));
|
|
122
184
|
}
|
|
123
185
|
},
|
|
124
186
|
"claude-desktop": {
|
|
125
187
|
name: "claude-desktop",
|
|
126
188
|
displayName: "Claude Desktop",
|
|
127
|
-
configPath:
|
|
189
|
+
configPath: join2(appSupport, "Claude", "claude_desktop_config.json"),
|
|
128
190
|
projectDetectPaths: [],
|
|
129
191
|
// Global only - no project support
|
|
130
192
|
configKey: "mcpServers",
|
|
@@ -132,14 +194,14 @@ var agents = {
|
|
|
132
194
|
supportedTransports: ["stdio", "http", "sse"],
|
|
133
195
|
supportsHeaders: true,
|
|
134
196
|
detectGlobalInstall: async () => {
|
|
135
|
-
return existsSync(
|
|
197
|
+
return existsSync(join2(appSupport, "Claude"));
|
|
136
198
|
}
|
|
137
199
|
},
|
|
138
200
|
codex: {
|
|
139
201
|
name: "codex",
|
|
140
202
|
displayName: "Codex",
|
|
141
|
-
configPath:
|
|
142
|
-
process.env.CODEX_HOME ||
|
|
203
|
+
configPath: join2(
|
|
204
|
+
process.env.CODEX_HOME || join2(home, ".codex"),
|
|
143
205
|
"config.toml"
|
|
144
206
|
),
|
|
145
207
|
localConfigPath: ".codex/config.toml",
|
|
@@ -149,14 +211,14 @@ var agents = {
|
|
|
149
211
|
supportedTransports: ["stdio", "http", "sse"],
|
|
150
212
|
supportsHeaders: true,
|
|
151
213
|
detectGlobalInstall: async () => {
|
|
152
|
-
return existsSync(
|
|
214
|
+
return existsSync(join2(home, ".codex"));
|
|
153
215
|
},
|
|
154
216
|
transformConfig: transformCodexConfig
|
|
155
217
|
},
|
|
156
218
|
cursor: {
|
|
157
219
|
name: "cursor",
|
|
158
220
|
displayName: "Cursor",
|
|
159
|
-
configPath:
|
|
221
|
+
configPath: join2(home, ".cursor", "mcp.json"),
|
|
160
222
|
localConfigPath: ".cursor/mcp.json",
|
|
161
223
|
projectDetectPaths: [".cursor"],
|
|
162
224
|
configKey: "mcpServers",
|
|
@@ -164,13 +226,13 @@ var agents = {
|
|
|
164
226
|
supportedTransports: ["stdio", "http", "sse"],
|
|
165
227
|
supportsHeaders: true,
|
|
166
228
|
detectGlobalInstall: async () => {
|
|
167
|
-
return existsSync(
|
|
229
|
+
return existsSync(join2(home, ".cursor"));
|
|
168
230
|
}
|
|
169
231
|
},
|
|
170
232
|
"gemini-cli": {
|
|
171
233
|
name: "gemini-cli",
|
|
172
234
|
displayName: "Gemini CLI",
|
|
173
|
-
configPath:
|
|
235
|
+
configPath: join2(home, ".gemini", "settings.json"),
|
|
174
236
|
localConfigPath: ".gemini/settings.json",
|
|
175
237
|
projectDetectPaths: [".gemini"],
|
|
176
238
|
configKey: "mcpServers",
|
|
@@ -178,28 +240,28 @@ var agents = {
|
|
|
178
240
|
supportedTransports: ["stdio", "http", "sse"],
|
|
179
241
|
supportsHeaders: true,
|
|
180
242
|
detectGlobalInstall: async () => {
|
|
181
|
-
return existsSync(
|
|
243
|
+
return existsSync(join2(home, ".gemini"));
|
|
182
244
|
}
|
|
183
245
|
},
|
|
184
246
|
goose: {
|
|
185
247
|
name: "goose",
|
|
186
248
|
displayName: "Goose",
|
|
187
|
-
configPath:
|
|
188
|
-
|
|
189
|
-
|
|
249
|
+
configPath: gooseConfigPath,
|
|
250
|
+
projectDetectPaths: [],
|
|
251
|
+
// Global only - no project support
|
|
190
252
|
configKey: "extensions",
|
|
191
253
|
format: "yaml",
|
|
192
254
|
supportedTransports: ["stdio", "http", "sse"],
|
|
193
|
-
supportsHeaders:
|
|
255
|
+
supportsHeaders: true,
|
|
194
256
|
detectGlobalInstall: async () => {
|
|
195
|
-
return existsSync(
|
|
257
|
+
return existsSync(gooseConfigPath);
|
|
196
258
|
},
|
|
197
259
|
transformConfig: transformGooseConfig
|
|
198
260
|
},
|
|
199
261
|
opencode: {
|
|
200
262
|
name: "opencode",
|
|
201
263
|
displayName: "OpenCode",
|
|
202
|
-
configPath:
|
|
264
|
+
configPath: join2(home, ".config", "opencode", "opencode.json"),
|
|
203
265
|
localConfigPath: ".opencode.json",
|
|
204
266
|
projectDetectPaths: [".opencode.json", ".opencode"],
|
|
205
267
|
configKey: "mcp",
|
|
@@ -207,14 +269,14 @@ var agents = {
|
|
|
207
269
|
supportedTransports: ["stdio", "http", "sse"],
|
|
208
270
|
supportsHeaders: true,
|
|
209
271
|
detectGlobalInstall: async () => {
|
|
210
|
-
return existsSync(
|
|
272
|
+
return existsSync(join2(home, ".config", "opencode"));
|
|
211
273
|
},
|
|
212
274
|
transformConfig: transformOpenCodeConfig
|
|
213
275
|
},
|
|
214
276
|
vscode: {
|
|
215
277
|
name: "vscode",
|
|
216
278
|
displayName: "VS Code",
|
|
217
|
-
configPath:
|
|
279
|
+
configPath: join2(vscodePath, "mcp.json"),
|
|
218
280
|
localConfigPath: ".vscode/mcp.json",
|
|
219
281
|
projectDetectPaths: [".vscode"],
|
|
220
282
|
configKey: "servers",
|
|
@@ -228,7 +290,7 @@ var agents = {
|
|
|
228
290
|
zed: {
|
|
229
291
|
name: "zed",
|
|
230
292
|
displayName: "Zed",
|
|
231
|
-
configPath: process.platform === "darwin" || process.platform === "win32" ?
|
|
293
|
+
configPath: process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed", "settings.json") : join2(appSupport, "zed", "settings.json"),
|
|
232
294
|
localConfigPath: ".zed/settings.json",
|
|
233
295
|
projectDetectPaths: [".zed"],
|
|
234
296
|
configKey: "context_servers",
|
|
@@ -236,7 +298,7 @@ var agents = {
|
|
|
236
298
|
supportedTransports: ["stdio", "http", "sse"],
|
|
237
299
|
supportsHeaders: true,
|
|
238
300
|
detectGlobalInstall: async () => {
|
|
239
|
-
const configDir = process.platform === "darwin" || process.platform === "win32" ?
|
|
301
|
+
const configDir = process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed") : join2(appSupport, "zed");
|
|
240
302
|
return existsSync(configDir);
|
|
241
303
|
},
|
|
242
304
|
transformConfig: transformZedConfig
|
|
@@ -259,7 +321,7 @@ function detectProjectAgents(cwd) {
|
|
|
259
321
|
for (const [type, config] of Object.entries(agents)) {
|
|
260
322
|
if (!config.localConfigPath) continue;
|
|
261
323
|
for (const detectPath of config.projectDetectPaths) {
|
|
262
|
-
if (existsSync(
|
|
324
|
+
if (existsSync(join2(dir, detectPath))) {
|
|
263
325
|
detected.push(type);
|
|
264
326
|
break;
|
|
265
327
|
}
|
|
@@ -279,6 +341,91 @@ async function detectAllGlobalAgents() {
|
|
|
279
341
|
function isTransportSupported(agentType, transport) {
|
|
280
342
|
return agents[agentType].supportedTransports.includes(transport);
|
|
281
343
|
}
|
|
344
|
+
async function promptForAgents(message, choices, defaultToAll = false) {
|
|
345
|
+
let lastSelected;
|
|
346
|
+
try {
|
|
347
|
+
lastSelected = await getLastSelectedAgents();
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
const validAgents = choices.map((c) => c.value);
|
|
351
|
+
let initialValues;
|
|
352
|
+
if (lastSelected && lastSelected.length > 0) {
|
|
353
|
+
initialValues = lastSelected.filter(
|
|
354
|
+
(a) => validAgents.includes(a)
|
|
355
|
+
);
|
|
356
|
+
if (initialValues.length === 0 && defaultToAll) {
|
|
357
|
+
initialValues = validAgents;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
initialValues = defaultToAll ? validAgents : [];
|
|
361
|
+
}
|
|
362
|
+
const selected = await p.multiselect({
|
|
363
|
+
message,
|
|
364
|
+
options: choices,
|
|
365
|
+
required: true,
|
|
366
|
+
initialValues
|
|
367
|
+
});
|
|
368
|
+
if (!p.isCancel(selected)) {
|
|
369
|
+
try {
|
|
370
|
+
await saveSelectedAgents(selected);
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return selected;
|
|
375
|
+
}
|
|
376
|
+
async function selectAgentsInteractive(availableAgents, options) {
|
|
377
|
+
let lastSelected;
|
|
378
|
+
try {
|
|
379
|
+
lastSelected = await getLastSelectedAgents();
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
const validLastSelected = lastSelected?.filter(
|
|
383
|
+
(a) => availableAgents.includes(a)
|
|
384
|
+
);
|
|
385
|
+
const selectOptions = [];
|
|
386
|
+
const hasPrevious = validLastSelected && validLastSelected.length > 0;
|
|
387
|
+
if (hasPrevious) {
|
|
388
|
+
const agentNames = validLastSelected.map((a) => agents[a].displayName).join(", ");
|
|
389
|
+
selectOptions.push({
|
|
390
|
+
value: "previous",
|
|
391
|
+
label: "Same as last time (Recommended)",
|
|
392
|
+
hint: agentNames
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
selectOptions.push({
|
|
396
|
+
value: "all",
|
|
397
|
+
label: hasPrevious ? "All available agents" : "All available agents (Recommended)",
|
|
398
|
+
hint: `Install to all ${availableAgents.length} available agents`
|
|
399
|
+
});
|
|
400
|
+
selectOptions.push({
|
|
401
|
+
value: "select",
|
|
402
|
+
label: "Select specific agents",
|
|
403
|
+
hint: "Choose which agents to install to"
|
|
404
|
+
});
|
|
405
|
+
const installChoice = await p.select({
|
|
406
|
+
message: "Install to",
|
|
407
|
+
options: selectOptions
|
|
408
|
+
});
|
|
409
|
+
if (p.isCancel(installChoice)) {
|
|
410
|
+
return installChoice;
|
|
411
|
+
}
|
|
412
|
+
if (installChoice === "all") {
|
|
413
|
+
return availableAgents;
|
|
414
|
+
}
|
|
415
|
+
if (installChoice === "previous" && validLastSelected) {
|
|
416
|
+
return validLastSelected;
|
|
417
|
+
}
|
|
418
|
+
const agentChoices = availableAgents.map((agentType) => {
|
|
419
|
+
const localPath = agents[agentType].localConfigPath;
|
|
420
|
+
const hint = options.global ? shortenPath(agents[agentType].configPath) : localPath ?? shortenPath(agents[agentType].configPath);
|
|
421
|
+
return {
|
|
422
|
+
value: agentType,
|
|
423
|
+
label: agents[agentType].displayName,
|
|
424
|
+
hint
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
return promptForAgents("Select agents to install to", agentChoices, false);
|
|
428
|
+
}
|
|
282
429
|
|
|
283
430
|
// src/source-parser.ts
|
|
284
431
|
function isUrl(input) {
|
|
@@ -410,11 +557,11 @@ function isRemoteSource(parsed) {
|
|
|
410
557
|
|
|
411
558
|
// src/installer.ts
|
|
412
559
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
413
|
-
import { join as
|
|
560
|
+
import { join as join3, dirname as dirname5 } from "path";
|
|
414
561
|
|
|
415
562
|
// src/formats/json.ts
|
|
416
563
|
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
417
|
-
import { dirname } from "path";
|
|
564
|
+
import { dirname as dirname2 } from "path";
|
|
418
565
|
import * as jsonc from "jsonc-parser";
|
|
419
566
|
|
|
420
567
|
// src/formats/utils.ts
|
|
@@ -465,7 +612,7 @@ function detectIndent(text) {
|
|
|
465
612
|
return result || { tabSize: 2, insertSpaces: true };
|
|
466
613
|
}
|
|
467
614
|
function writeJsonConfig(filePath, config, configKey) {
|
|
468
|
-
const dir =
|
|
615
|
+
const dir = dirname2(filePath);
|
|
469
616
|
if (!existsSync2(dir)) {
|
|
470
617
|
mkdirSync(dir, { recursive: true });
|
|
471
618
|
}
|
|
@@ -507,7 +654,7 @@ function setNestedValue(obj, path, value) {
|
|
|
507
654
|
|
|
508
655
|
// src/formats/yaml.ts
|
|
509
656
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
510
|
-
import { dirname as
|
|
657
|
+
import { dirname as dirname3 } from "path";
|
|
511
658
|
import yaml from "js-yaml";
|
|
512
659
|
function readYamlConfig(filePath) {
|
|
513
660
|
if (!existsSync3(filePath)) {
|
|
@@ -518,7 +665,7 @@ function readYamlConfig(filePath) {
|
|
|
518
665
|
return parsed || {};
|
|
519
666
|
}
|
|
520
667
|
function writeYamlConfig(filePath, config) {
|
|
521
|
-
const dir =
|
|
668
|
+
const dir = dirname3(filePath);
|
|
522
669
|
if (!existsSync3(dir)) {
|
|
523
670
|
mkdirSync2(dir, { recursive: true });
|
|
524
671
|
}
|
|
@@ -537,7 +684,7 @@ function writeYamlConfig(filePath, config) {
|
|
|
537
684
|
|
|
538
685
|
// src/formats/toml.ts
|
|
539
686
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
540
|
-
import { dirname as
|
|
687
|
+
import { dirname as dirname4 } from "path";
|
|
541
688
|
import * as TOML from "@iarna/toml";
|
|
542
689
|
function readTomlConfig(filePath) {
|
|
543
690
|
if (!existsSync4(filePath)) {
|
|
@@ -548,7 +695,7 @@ function readTomlConfig(filePath) {
|
|
|
548
695
|
return parsed;
|
|
549
696
|
}
|
|
550
697
|
function writeTomlConfig(filePath, config) {
|
|
551
|
-
const dir =
|
|
698
|
+
const dir = dirname4(filePath);
|
|
552
699
|
if (!existsSync4(dir)) {
|
|
553
700
|
mkdirSync3(dir, { recursive: true });
|
|
554
701
|
}
|
|
@@ -614,7 +761,7 @@ function buildServerConfig(parsed, options = {}) {
|
|
|
614
761
|
function getConfigPath(agent, options = {}) {
|
|
615
762
|
if (options.local && agent.localConfigPath) {
|
|
616
763
|
const cwd = options.cwd || process.cwd();
|
|
617
|
-
return
|
|
764
|
+
return join3(cwd, agent.localConfigPath);
|
|
618
765
|
}
|
|
619
766
|
return agent.configPath;
|
|
620
767
|
}
|
|
@@ -622,7 +769,7 @@ function installServerForAgent(serverName, serverConfig, agentType, options = {}
|
|
|
622
769
|
const agent = agents[agentType];
|
|
623
770
|
const configPath = getConfigPath(agent, options);
|
|
624
771
|
try {
|
|
625
|
-
const dir =
|
|
772
|
+
const dir = dirname5(configPath);
|
|
626
773
|
if (!existsSync5(dir)) {
|
|
627
774
|
mkdirSync4(dir, { recursive: true });
|
|
628
775
|
}
|
|
@@ -667,7 +814,7 @@ function installServer(serverName, serverConfig, agentTypes, options = {}) {
|
|
|
667
814
|
// package.json
|
|
668
815
|
var package_default = {
|
|
669
816
|
name: "add-mcp",
|
|
670
|
-
version: "0.
|
|
817
|
+
version: "0.6.0",
|
|
671
818
|
description: "Add MCP servers to your favorite coding agents with a single command.",
|
|
672
819
|
author: "Andre Landgraf <andre@neon.tech>",
|
|
673
820
|
license: "Apache-2.0",
|
|
@@ -683,8 +830,8 @@ var package_default = {
|
|
|
683
830
|
fmt: "prettier --write .",
|
|
684
831
|
build: "tsup src/index.ts --format esm --dts --clean",
|
|
685
832
|
dev: "tsx src/index.ts",
|
|
686
|
-
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
|
|
687
|
-
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts",
|
|
833
|
+
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
|
|
834
|
+
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts",
|
|
688
835
|
"test:e2e": "tsx tests/e2e/install.test.ts",
|
|
689
836
|
typecheck: "tsc --noEmit",
|
|
690
837
|
prepublishOnly: "npm run build"
|
|
@@ -760,8 +907,8 @@ function showLogo() {
|
|
|
760
907
|
console.log(`${GRAYS[i]}${line}${RESET}`);
|
|
761
908
|
});
|
|
762
909
|
}
|
|
763
|
-
function
|
|
764
|
-
const home2 =
|
|
910
|
+
function shortenPath2(fullPath) {
|
|
911
|
+
const home2 = homedir3();
|
|
765
912
|
if (fullPath.startsWith(home2)) {
|
|
766
913
|
return fullPath.replace(home2, "~");
|
|
767
914
|
}
|
|
@@ -868,7 +1015,7 @@ async function main(target, options) {
|
|
|
868
1015
|
process.exit(0);
|
|
869
1016
|
}
|
|
870
1017
|
console.log();
|
|
871
|
-
const spinner2 =
|
|
1018
|
+
const spinner2 = p2.spinner();
|
|
872
1019
|
spinner2.start("Parsing source...");
|
|
873
1020
|
const parsed = parseSource(target);
|
|
874
1021
|
const isRemote = isRemoteSource(parsed);
|
|
@@ -877,7 +1024,7 @@ async function main(target, options) {
|
|
|
877
1024
|
const headerValues = options.header ?? [];
|
|
878
1025
|
const headerResult = parseHeaders(headerValues);
|
|
879
1026
|
if (headerResult.invalid.length > 0) {
|
|
880
|
-
|
|
1027
|
+
p2.log.error(
|
|
881
1028
|
`Invalid --header value(s): ${headerResult.invalid.join(", ")}. Use "Key: Value" format.`
|
|
882
1029
|
);
|
|
883
1030
|
process.exit(1);
|
|
@@ -885,23 +1032,23 @@ async function main(target, options) {
|
|
|
885
1032
|
const headerKeys = Object.keys(headerResult.headers);
|
|
886
1033
|
const hasHeaderValues = headerKeys.length > 0;
|
|
887
1034
|
if (hasHeaderValues && !isRemote) {
|
|
888
|
-
|
|
1035
|
+
p2.log.warn("--header is only used for remote URLs, ignoring");
|
|
889
1036
|
}
|
|
890
1037
|
const serverName = options.name || parsed.inferredName;
|
|
891
|
-
|
|
1038
|
+
p2.log.info(`Server name: ${chalk.cyan(serverName)}`);
|
|
892
1039
|
const transportValue = options.transport || options.type;
|
|
893
1040
|
let resolvedTransport;
|
|
894
1041
|
if (transportValue) {
|
|
895
1042
|
const validTransports = ["http", "sse"];
|
|
896
1043
|
if (!validTransports.includes(transportValue)) {
|
|
897
|
-
|
|
1044
|
+
p2.log.error(
|
|
898
1045
|
`Invalid transport: ${transportValue}. Valid options: ${validTransports.join(", ")}`
|
|
899
1046
|
);
|
|
900
1047
|
process.exit(1);
|
|
901
1048
|
}
|
|
902
1049
|
resolvedTransport = transportValue;
|
|
903
1050
|
if (!isRemoteSource(parsed)) {
|
|
904
|
-
|
|
1051
|
+
p2.log.warn("--transport is only used for remote URLs, ignoring");
|
|
905
1052
|
}
|
|
906
1053
|
}
|
|
907
1054
|
const serverConfig = buildServerConfig(parsed, {
|
|
@@ -925,14 +1072,14 @@ async function main(target, options) {
|
|
|
925
1072
|
}
|
|
926
1073
|
}
|
|
927
1074
|
if (invalid.length > 0) {
|
|
928
|
-
|
|
929
|
-
|
|
1075
|
+
p2.log.error(`Invalid agents: ${invalid.join(", ")}`);
|
|
1076
|
+
p2.log.info(`Valid agents: ${allAgentTypes.join(", ")}`);
|
|
930
1077
|
process.exit(1);
|
|
931
1078
|
}
|
|
932
1079
|
targetAgents = resolved;
|
|
933
1080
|
} else if (options.all) {
|
|
934
1081
|
targetAgents = allAgentTypes;
|
|
935
|
-
|
|
1082
|
+
p2.log.info(`Installing to all ${targetAgents.length} agents`);
|
|
936
1083
|
} else {
|
|
937
1084
|
spinner2.start("Detecting agents...");
|
|
938
1085
|
let detectedAgents;
|
|
@@ -953,61 +1100,63 @@ async function main(target, options) {
|
|
|
953
1100
|
);
|
|
954
1101
|
if (detectedAgents.length === 0) {
|
|
955
1102
|
if (options.yes) {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1103
|
+
if (options.global) {
|
|
1104
|
+
targetAgents = allAgentTypes;
|
|
1105
|
+
for (const agent of targetAgents) {
|
|
1106
|
+
agentRouting.set(agent, "global");
|
|
1107
|
+
}
|
|
1108
|
+
p2.log.info(
|
|
1109
|
+
`Installing to ${targetAgents.length} agents globally (none detected)`
|
|
1110
|
+
);
|
|
1111
|
+
} else {
|
|
1112
|
+
targetAgents = getProjectCapableAgents();
|
|
1113
|
+
for (const agent of targetAgents) {
|
|
1114
|
+
agentRouting.set(agent, "local");
|
|
1115
|
+
}
|
|
1116
|
+
p2.log.info(
|
|
1117
|
+
`Installing to ${targetAgents.length} project-capable agents (none detected)`
|
|
967
1118
|
);
|
|
968
|
-
process.exit(1);
|
|
969
1119
|
}
|
|
970
|
-
|
|
971
|
-
|
|
1120
|
+
} else {
|
|
1121
|
+
const availableAgents = options.global ? allAgentTypes : getProjectCapableAgents();
|
|
1122
|
+
p2.log.warn(
|
|
1123
|
+
options.global ? "No coding agents detected. You can still install MCP servers." : "No agents detected in this project. You can still install MCP servers."
|
|
972
1124
|
);
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
label: agents[type].displayName
|
|
976
|
-
}));
|
|
977
|
-
const selected = await p.multiselect({
|
|
978
|
-
message: "Select agents to install to",
|
|
979
|
-
options: allAgentChoices,
|
|
980
|
-
required: true
|
|
1125
|
+
const selected = await selectAgentsInteractive(availableAgents, {
|
|
1126
|
+
global: options.global
|
|
981
1127
|
});
|
|
982
|
-
if (
|
|
983
|
-
|
|
1128
|
+
if (p2.isCancel(selected)) {
|
|
1129
|
+
p2.cancel("Installation cancelled");
|
|
984
1130
|
process.exit(0);
|
|
985
1131
|
}
|
|
986
1132
|
selectedViaPrompt = true;
|
|
987
1133
|
targetAgents = selected;
|
|
1134
|
+
for (const agent of targetAgents) {
|
|
1135
|
+
agentRouting.set(agent, options.global ? "global" : "local");
|
|
1136
|
+
}
|
|
988
1137
|
}
|
|
989
1138
|
} else if (detectedAgents.length === 1 || options.yes) {
|
|
990
1139
|
targetAgents = detectedAgents;
|
|
991
1140
|
const agentNames = detectedAgents.map((a) => chalk.cyan(agents[a].displayName)).join(", ");
|
|
992
|
-
|
|
1141
|
+
p2.log.info(`Installing to: ${agentNames}`);
|
|
993
1142
|
} else {
|
|
994
1143
|
const agentChoices = detectedAgents.map((a) => {
|
|
995
1144
|
const routing = agentRouting.get(a);
|
|
996
|
-
const hint = routing === "local" ? "project" : routing === "global" ? "global" :
|
|
1145
|
+
const hint = routing === "local" ? "project" : routing === "global" ? "global" : shortenPath2(agents[a].configPath);
|
|
997
1146
|
return {
|
|
998
1147
|
value: a,
|
|
999
1148
|
label: agents[a].displayName,
|
|
1000
1149
|
hint
|
|
1001
1150
|
};
|
|
1002
1151
|
});
|
|
1003
|
-
const selected = await
|
|
1152
|
+
const selected = await p2.multiselect({
|
|
1004
1153
|
message: "Select agents to install to",
|
|
1005
1154
|
options: agentChoices,
|
|
1006
1155
|
required: true,
|
|
1007
1156
|
initialValues: detectedAgents
|
|
1008
1157
|
});
|
|
1009
|
-
if (
|
|
1010
|
-
|
|
1158
|
+
if (p2.isCancel(selected)) {
|
|
1159
|
+
p2.cancel("Installation cancelled");
|
|
1011
1160
|
process.exit(0);
|
|
1012
1161
|
}
|
|
1013
1162
|
selectedViaPrompt = true;
|
|
@@ -1021,18 +1170,18 @@ async function main(target, options) {
|
|
|
1021
1170
|
if (unsupportedAgents.length > 0) {
|
|
1022
1171
|
const unsupportedNames = unsupportedAgents.map((a) => agents[a].displayName).join(", ");
|
|
1023
1172
|
if (options.all) {
|
|
1024
|
-
|
|
1173
|
+
p2.log.warn(
|
|
1025
1174
|
`Skipping agents that don't support ${requiredTransport} transport: ${unsupportedNames}`
|
|
1026
1175
|
);
|
|
1027
1176
|
targetAgents = targetAgents.filter(
|
|
1028
1177
|
(a) => isTransportSupported(a, requiredTransport)
|
|
1029
1178
|
);
|
|
1030
1179
|
if (targetAgents.length === 0) {
|
|
1031
|
-
|
|
1180
|
+
p2.log.error("No agents support this transport type");
|
|
1032
1181
|
process.exit(1);
|
|
1033
1182
|
}
|
|
1034
1183
|
} else {
|
|
1035
|
-
|
|
1184
|
+
p2.log.error(
|
|
1036
1185
|
`The following agents don't support ${requiredTransport} transport: ${unsupportedNames}`
|
|
1037
1186
|
);
|
|
1038
1187
|
process.exit(1);
|
|
@@ -1047,7 +1196,7 @@ async function main(target, options) {
|
|
|
1047
1196
|
const unsupportedNames = unsupportedHeaderAgents.map((a) => agents[a].displayName).join(", ");
|
|
1048
1197
|
const hasExplicitAgentSelection = hasExplicitAgentFlags || selectedViaPrompt;
|
|
1049
1198
|
if (hasExplicitAgentSelection) {
|
|
1050
|
-
|
|
1199
|
+
p2.log.error(
|
|
1051
1200
|
`The following agents don't support HTTP headers: ${unsupportedNames}`
|
|
1052
1201
|
);
|
|
1053
1202
|
process.exit(1);
|
|
@@ -1056,10 +1205,10 @@ async function main(target, options) {
|
|
|
1056
1205
|
(a) => agents[a].supportsHeaders
|
|
1057
1206
|
);
|
|
1058
1207
|
if (supportedAgents.length === 0) {
|
|
1059
|
-
|
|
1208
|
+
p2.log.error("No selected agents support HTTP headers");
|
|
1060
1209
|
process.exit(1);
|
|
1061
1210
|
}
|
|
1062
|
-
|
|
1211
|
+
p2.log.warn(
|
|
1063
1212
|
`Skipping agents that don't support HTTP headers: ${unsupportedNames}`
|
|
1064
1213
|
);
|
|
1065
1214
|
targetAgents = supportedAgents;
|
|
@@ -1083,7 +1232,7 @@ async function main(target, options) {
|
|
|
1083
1232
|
if (selectedWithLocal.length > 0) {
|
|
1084
1233
|
let installLocally = true;
|
|
1085
1234
|
if (!options.yes) {
|
|
1086
|
-
const scope = await
|
|
1235
|
+
const scope = await p2.select({
|
|
1087
1236
|
message: "Installation scope",
|
|
1088
1237
|
options: [
|
|
1089
1238
|
{
|
|
@@ -1098,8 +1247,8 @@ async function main(target, options) {
|
|
|
1098
1247
|
}
|
|
1099
1248
|
]
|
|
1100
1249
|
});
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1250
|
+
if (p2.isCancel(scope)) {
|
|
1251
|
+
p2.cancel("Installation cancelled");
|
|
1103
1252
|
process.exit(0);
|
|
1104
1253
|
}
|
|
1105
1254
|
installLocally = scope;
|
|
@@ -1108,7 +1257,7 @@ async function main(target, options) {
|
|
|
1108
1257
|
agentRouting.set(agent, installLocally ? "local" : "global");
|
|
1109
1258
|
}
|
|
1110
1259
|
} else {
|
|
1111
|
-
|
|
1260
|
+
p2.log.info("Selected agents only support global installation");
|
|
1112
1261
|
}
|
|
1113
1262
|
}
|
|
1114
1263
|
const summaryLines = [];
|
|
@@ -1140,13 +1289,13 @@ async function main(target, options) {
|
|
|
1140
1289
|
);
|
|
1141
1290
|
}
|
|
1142
1291
|
console.log();
|
|
1143
|
-
|
|
1292
|
+
p2.note(summaryLines.join("\n"), "Installation Summary");
|
|
1144
1293
|
if (!options.yes) {
|
|
1145
|
-
const confirmed = await
|
|
1294
|
+
const confirmed = await p2.confirm({
|
|
1146
1295
|
message: "Proceed with installation?"
|
|
1147
1296
|
});
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
1297
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
1298
|
+
p2.cancel("Installation cancelled");
|
|
1150
1299
|
process.exit(0);
|
|
1151
1300
|
}
|
|
1152
1301
|
}
|
|
@@ -1162,12 +1311,12 @@ async function main(target, options) {
|
|
|
1162
1311
|
const resultLines = [];
|
|
1163
1312
|
for (const [agentType, result] of successful) {
|
|
1164
1313
|
const agent = agents[agentType];
|
|
1165
|
-
const shortPath =
|
|
1314
|
+
const shortPath = shortenPath2(result.path);
|
|
1166
1315
|
resultLines.push(
|
|
1167
1316
|
`${chalk.green("\u2713")} ${agent.displayName}: ${chalk.dim(shortPath)}`
|
|
1168
1317
|
);
|
|
1169
1318
|
}
|
|
1170
|
-
|
|
1319
|
+
p2.note(
|
|
1171
1320
|
resultLines.join("\n"),
|
|
1172
1321
|
chalk.green(
|
|
1173
1322
|
`Installed to ${successful.length} agent${successful.length !== 1 ? "s" : ""}`
|
|
@@ -1176,18 +1325,18 @@ async function main(target, options) {
|
|
|
1176
1325
|
}
|
|
1177
1326
|
if (failed.length > 0) {
|
|
1178
1327
|
console.log();
|
|
1179
|
-
|
|
1328
|
+
p2.log.error(
|
|
1180
1329
|
chalk.red(
|
|
1181
1330
|
`Failed to install to ${failed.length} agent${failed.length !== 1 ? "s" : ""}`
|
|
1182
1331
|
)
|
|
1183
1332
|
);
|
|
1184
1333
|
for (const [agentType, result] of failed) {
|
|
1185
1334
|
const agent = agents[agentType];
|
|
1186
|
-
|
|
1335
|
+
p2.log.message(
|
|
1187
1336
|
` ${chalk.red("\u2717")} ${agent.displayName}: ${chalk.dim(result.error)}`
|
|
1188
1337
|
);
|
|
1189
1338
|
}
|
|
1190
1339
|
}
|
|
1191
1340
|
console.log();
|
|
1192
|
-
|
|
1341
|
+
p2.outro(chalk.green("Done!"));
|
|
1193
1342
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Add MCP servers to your favorite coding agents with a single command.",
|
|
5
5
|
"author": "Andre Landgraf <andre@neon.tech>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"fmt": "prettier --write .",
|
|
17
17
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
18
18
|
"dev": "tsx src/index.ts",
|
|
19
|
-
"test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
|
|
20
|
-
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/installer.test.ts",
|
|
19
|
+
"test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts && tsx tests/e2e/install.test.ts",
|
|
20
|
+
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/mcp-lock.test.ts && tsx tests/installer.test.ts",
|
|
21
21
|
"test:e2e": "tsx tests/e2e/install.test.ts",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"prepublishOnly": "npm run build"
|