add-mcp 1.9.1 → 1.10.2
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 +41 -0
- package/dist/chunk-B27GVIBZ.js +1375 -0
- package/dist/index.js +145 -1441
- package/dist/lib.d.ts +102 -0
- package/dist/lib.js +79 -0
- package/package.json +13 -4
package/dist/index.js
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
agents,
|
|
4
|
+
buildAgentSelectionChoices,
|
|
5
|
+
buildServerConfig,
|
|
6
|
+
detectGlobalAgents,
|
|
7
|
+
detectProjectAgents,
|
|
8
|
+
findMatchingServers,
|
|
9
|
+
getAgentTypes,
|
|
10
|
+
getConfigPath,
|
|
11
|
+
getFindRegistries,
|
|
12
|
+
getLastSelectedAgents,
|
|
13
|
+
getProjectCapableAgents,
|
|
14
|
+
installServer,
|
|
15
|
+
installServerForAgent,
|
|
16
|
+
isRemoteSource,
|
|
17
|
+
isTransportSupported,
|
|
18
|
+
listInstalledServers,
|
|
19
|
+
parseSource,
|
|
20
|
+
removeServerFromConfig,
|
|
21
|
+
saveFindRegistries,
|
|
22
|
+
selectAgentsInteractive,
|
|
23
|
+
supportsProjectConfig,
|
|
24
|
+
updateGitignoreWithPaths
|
|
25
|
+
} from "./chunk-B27GVIBZ.js";
|
|
2
26
|
|
|
3
27
|
// src/index.ts
|
|
4
28
|
import { program } from "commander";
|
|
5
|
-
import * as
|
|
29
|
+
import * as p2 from "@clack/prompts";
|
|
6
30
|
import chalk from "chalk";
|
|
7
|
-
import { homedir
|
|
31
|
+
import { homedir } from "os";
|
|
8
32
|
|
|
9
33
|
// src/types.ts
|
|
10
34
|
var agentAliases = {
|
|
@@ -13,809 +37,8 @@ var agentAliases = {
|
|
|
13
37
|
"github-copilot": "vscode"
|
|
14
38
|
};
|
|
15
39
|
|
|
16
|
-
// src/agents.ts
|
|
17
|
-
import * as p from "@clack/prompts";
|
|
18
|
-
import { homedir as homedir2 } from "os";
|
|
19
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
20
|
-
import { existsSync } from "fs";
|
|
21
|
-
|
|
22
|
-
// src/config.ts
|
|
23
|
-
import { readFile, writeFile, mkdir, rm } from "fs/promises";
|
|
24
|
-
import { dirname, join } from "path";
|
|
25
|
-
import { homedir } from "os";
|
|
26
|
-
var CONFIG_DIR = "add-mcp";
|
|
27
|
-
var CONFIG_FILE = "config.json";
|
|
28
|
-
var CURRENT_VERSION = 1;
|
|
29
|
-
var LEGACY_AGENTS_DIR = ".agents";
|
|
30
|
-
var LEGACY_LOCK_FILE = ".mcp-lock.json";
|
|
31
|
-
function getXdgConfigHome() {
|
|
32
|
-
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
33
|
-
}
|
|
34
|
-
function getConfigPath() {
|
|
35
|
-
return join(getXdgConfigHome(), CONFIG_DIR, CONFIG_FILE);
|
|
36
|
-
}
|
|
37
|
-
function getLegacyConfigPath() {
|
|
38
|
-
return join(homedir(), LEGACY_AGENTS_DIR, LEGACY_LOCK_FILE);
|
|
39
|
-
}
|
|
40
|
-
async function readConfig() {
|
|
41
|
-
const configPath = getConfigPath();
|
|
42
|
-
try {
|
|
43
|
-
const content = await readFile(configPath, "utf-8");
|
|
44
|
-
const parsed = JSON.parse(content);
|
|
45
|
-
if (typeof parsed.version !== "number") {
|
|
46
|
-
return createEmptyConfig();
|
|
47
|
-
}
|
|
48
|
-
if (parsed.version < CURRENT_VERSION) {
|
|
49
|
-
return createEmptyConfig();
|
|
50
|
-
}
|
|
51
|
-
return parsed;
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
const legacyPath = getLegacyConfigPath();
|
|
55
|
-
try {
|
|
56
|
-
const content = await readFile(legacyPath, "utf-8");
|
|
57
|
-
const parsed = JSON.parse(content);
|
|
58
|
-
if (typeof parsed.version !== "number" || parsed.version < CURRENT_VERSION) {
|
|
59
|
-
return createEmptyConfig();
|
|
60
|
-
}
|
|
61
|
-
await writeConfig(parsed);
|
|
62
|
-
await cleanupLegacyConfig();
|
|
63
|
-
return parsed;
|
|
64
|
-
} catch {
|
|
65
|
-
return createEmptyConfig();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
async function writeConfig(config) {
|
|
69
|
-
const configPath = getConfigPath();
|
|
70
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
71
|
-
const content = JSON.stringify(config, null, 2);
|
|
72
|
-
await writeFile(configPath, content, "utf-8");
|
|
73
|
-
}
|
|
74
|
-
async function cleanupLegacyConfig() {
|
|
75
|
-
const legacyPath = getLegacyConfigPath();
|
|
76
|
-
try {
|
|
77
|
-
await rm(legacyPath, { force: true });
|
|
78
|
-
} catch {
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function getLastSelectedAgents() {
|
|
82
|
-
const config = await readConfig();
|
|
83
|
-
return config.lastSelectedAgents;
|
|
84
|
-
}
|
|
85
|
-
async function saveSelectedAgents(agents2) {
|
|
86
|
-
const config = await readConfig();
|
|
87
|
-
config.lastSelectedAgents = agents2;
|
|
88
|
-
await writeConfig(config);
|
|
89
|
-
}
|
|
90
|
-
async function getFindRegistries() {
|
|
91
|
-
const config = await readConfig();
|
|
92
|
-
if (!config.findRegistries) return [];
|
|
93
|
-
return config.findRegistries.map(normalizeFindRegistryEntry);
|
|
94
|
-
}
|
|
95
|
-
function normalizeFindRegistryEntry(raw) {
|
|
96
|
-
const legacy = raw;
|
|
97
|
-
const url = legacy.url ?? legacy.serversUrl;
|
|
98
|
-
if (!url || typeof url !== "string") {
|
|
99
|
-
throw new Error("Registry entry missing url");
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
url,
|
|
103
|
-
...legacy.label ? { label: legacy.label } : {}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
async function saveFindRegistries(registries) {
|
|
107
|
-
const config = await readConfig();
|
|
108
|
-
config.findRegistries = registries;
|
|
109
|
-
await writeConfig(config);
|
|
110
|
-
}
|
|
111
|
-
function createEmptyConfig() {
|
|
112
|
-
return {
|
|
113
|
-
version: CURRENT_VERSION
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/agents.ts
|
|
118
|
-
var home = homedir2();
|
|
119
|
-
function shortenPath(fullPath) {
|
|
120
|
-
if (fullPath.startsWith(home)) {
|
|
121
|
-
return fullPath.replace(home, "~");
|
|
122
|
-
}
|
|
123
|
-
return fullPath;
|
|
124
|
-
}
|
|
125
|
-
function getPlatformPaths() {
|
|
126
|
-
const platform = process.platform;
|
|
127
|
-
if (platform === "win32") {
|
|
128
|
-
const appData = process.env.APPDATA || join2(home, "AppData", "Roaming");
|
|
129
|
-
return {
|
|
130
|
-
appSupport: appData,
|
|
131
|
-
vscodePath: join2(appData, "Code", "User"),
|
|
132
|
-
gooseConfigPath: join2(appData, "Block", "goose", "config", "config.yaml")
|
|
133
|
-
};
|
|
134
|
-
} else if (platform === "darwin") {
|
|
135
|
-
return {
|
|
136
|
-
appSupport: join2(home, "Library", "Application Support"),
|
|
137
|
-
vscodePath: join2(home, "Library", "Application Support", "Code", "User"),
|
|
138
|
-
gooseConfigPath: join2(home, ".config", "goose", "config.yaml")
|
|
139
|
-
};
|
|
140
|
-
} else {
|
|
141
|
-
const configDir = process.env.XDG_CONFIG_HOME || join2(home, ".config");
|
|
142
|
-
return {
|
|
143
|
-
appSupport: configDir,
|
|
144
|
-
vscodePath: join2(configDir, "Code", "User"),
|
|
145
|
-
gooseConfigPath: join2(configDir, "goose", "config.yaml")
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
var { appSupport, vscodePath, gooseConfigPath } = getPlatformPaths();
|
|
150
|
-
var antigravityConfigPath = join2(
|
|
151
|
-
home,
|
|
152
|
-
".gemini",
|
|
153
|
-
"antigravity",
|
|
154
|
-
"mcp_config.json"
|
|
155
|
-
);
|
|
156
|
-
var clineCliConfigPath = join2(
|
|
157
|
-
process.env.CLINE_DIR || join2(home, ".cline"),
|
|
158
|
-
"data",
|
|
159
|
-
"settings",
|
|
160
|
-
"cline_mcp_settings.json"
|
|
161
|
-
);
|
|
162
|
-
var clineExtensionConfigPath = join2(
|
|
163
|
-
vscodePath,
|
|
164
|
-
"globalStorage",
|
|
165
|
-
"saoudrizwan.claude-dev",
|
|
166
|
-
"settings",
|
|
167
|
-
"cline_mcp_settings.json"
|
|
168
|
-
);
|
|
169
|
-
var copilotConfigPath = join2(
|
|
170
|
-
process.env.XDG_CONFIG_HOME || join2(home, ".copilot"),
|
|
171
|
-
"mcp-config.json"
|
|
172
|
-
);
|
|
173
|
-
function transformGooseConfig(serverName, config) {
|
|
174
|
-
if (config.url) {
|
|
175
|
-
const gooseType = config.type === "sse" ? "sse" : "streamable_http";
|
|
176
|
-
return {
|
|
177
|
-
name: serverName,
|
|
178
|
-
description: "",
|
|
179
|
-
type: gooseType,
|
|
180
|
-
uri: config.url,
|
|
181
|
-
headers: config.headers || {},
|
|
182
|
-
enabled: true,
|
|
183
|
-
timeout: 300
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
name: serverName,
|
|
188
|
-
description: "",
|
|
189
|
-
cmd: config.command,
|
|
190
|
-
args: config.args || [],
|
|
191
|
-
enabled: true,
|
|
192
|
-
envs: config.env || {},
|
|
193
|
-
type: "stdio",
|
|
194
|
-
timeout: 300
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function transformZedConfig(_serverName, config) {
|
|
198
|
-
if (config.url) {
|
|
199
|
-
return {
|
|
200
|
-
source: "custom",
|
|
201
|
-
type: config.type || "http",
|
|
202
|
-
url: config.url,
|
|
203
|
-
headers: config.headers || {}
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
source: "custom",
|
|
208
|
-
command: config.command,
|
|
209
|
-
args: config.args || [],
|
|
210
|
-
env: config.env || {}
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function transformOpenCodeConfig(_serverName, config) {
|
|
214
|
-
if (config.url) {
|
|
215
|
-
return {
|
|
216
|
-
type: "remote",
|
|
217
|
-
url: config.url,
|
|
218
|
-
enabled: true,
|
|
219
|
-
headers: config.headers
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
return {
|
|
223
|
-
type: "local",
|
|
224
|
-
command: [config.command, ...config.args || []],
|
|
225
|
-
enabled: true,
|
|
226
|
-
environment: config.env || {}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
function transformCodexConfig(_serverName, config) {
|
|
230
|
-
if (config.url) {
|
|
231
|
-
const remoteConfig = {
|
|
232
|
-
type: config.type || "http",
|
|
233
|
-
url: config.url
|
|
234
|
-
};
|
|
235
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
236
|
-
remoteConfig.http_headers = config.headers;
|
|
237
|
-
}
|
|
238
|
-
return remoteConfig;
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
command: config.command,
|
|
242
|
-
args: config.args || [],
|
|
243
|
-
env: config.env
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
function transformCursorConfig(_serverName, config) {
|
|
247
|
-
if (config.url) {
|
|
248
|
-
const remoteConfig = {
|
|
249
|
-
url: config.url
|
|
250
|
-
};
|
|
251
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
252
|
-
remoteConfig.headers = config.headers;
|
|
253
|
-
}
|
|
254
|
-
return remoteConfig;
|
|
255
|
-
}
|
|
256
|
-
return config;
|
|
257
|
-
}
|
|
258
|
-
function transformClineConfig(_serverName, config) {
|
|
259
|
-
if (config.url) {
|
|
260
|
-
const remoteConfig = {
|
|
261
|
-
url: config.url,
|
|
262
|
-
type: config.type === "sse" ? "sse" : "streamableHttp",
|
|
263
|
-
disabled: false
|
|
264
|
-
};
|
|
265
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
266
|
-
remoteConfig.headers = config.headers;
|
|
267
|
-
}
|
|
268
|
-
return remoteConfig;
|
|
269
|
-
}
|
|
270
|
-
const localConfig = {
|
|
271
|
-
command: config.command,
|
|
272
|
-
args: config.args || [],
|
|
273
|
-
disabled: false
|
|
274
|
-
};
|
|
275
|
-
if (config.env && Object.keys(config.env).length > 0) {
|
|
276
|
-
localConfig.env = config.env;
|
|
277
|
-
}
|
|
278
|
-
return localConfig;
|
|
279
|
-
}
|
|
280
|
-
function transformAntigravityConfig(_serverName, config) {
|
|
281
|
-
if (config.url) {
|
|
282
|
-
const remoteConfig = {
|
|
283
|
-
serverUrl: config.url
|
|
284
|
-
};
|
|
285
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
286
|
-
remoteConfig.headers = config.headers;
|
|
287
|
-
}
|
|
288
|
-
return remoteConfig;
|
|
289
|
-
}
|
|
290
|
-
const localConfig = {
|
|
291
|
-
command: config.command,
|
|
292
|
-
args: config.args || []
|
|
293
|
-
};
|
|
294
|
-
if (config.env && Object.keys(config.env).length > 0) {
|
|
295
|
-
localConfig.env = config.env;
|
|
296
|
-
}
|
|
297
|
-
return localConfig;
|
|
298
|
-
}
|
|
299
|
-
function transformGitHubCopilotCliConfig(_serverName, config, context) {
|
|
300
|
-
if (context?.local) {
|
|
301
|
-
return config;
|
|
302
|
-
}
|
|
303
|
-
if (config.url) {
|
|
304
|
-
const remoteConfig = {
|
|
305
|
-
type: config.type || "http",
|
|
306
|
-
url: config.url,
|
|
307
|
-
tools: ["*"]
|
|
308
|
-
};
|
|
309
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
310
|
-
remoteConfig.headers = config.headers;
|
|
311
|
-
}
|
|
312
|
-
return remoteConfig;
|
|
313
|
-
}
|
|
314
|
-
const localConfig = {
|
|
315
|
-
type: "stdio",
|
|
316
|
-
command: config.command,
|
|
317
|
-
args: config.args || [],
|
|
318
|
-
tools: ["*"]
|
|
319
|
-
};
|
|
320
|
-
if (config.env && Object.keys(config.env).length > 0) {
|
|
321
|
-
localConfig.env = config.env;
|
|
322
|
-
}
|
|
323
|
-
return localConfig;
|
|
324
|
-
}
|
|
325
|
-
function resolveMcporterConfigPath(agent, options) {
|
|
326
|
-
if (options.local && agent.localConfigPath) {
|
|
327
|
-
return join2(options.cwd, agent.localConfigPath);
|
|
328
|
-
}
|
|
329
|
-
const globalJsonPath = agent.configPath;
|
|
330
|
-
const globalJsoncPath = join2(dirname2(globalJsonPath), "mcporter.jsonc");
|
|
331
|
-
if (existsSync(globalJsonPath)) {
|
|
332
|
-
return globalJsonPath;
|
|
333
|
-
}
|
|
334
|
-
if (existsSync(globalJsoncPath)) {
|
|
335
|
-
return globalJsoncPath;
|
|
336
|
-
}
|
|
337
|
-
return globalJsonPath;
|
|
338
|
-
}
|
|
339
|
-
var agents = {
|
|
340
|
-
antigravity: {
|
|
341
|
-
name: "antigravity",
|
|
342
|
-
displayName: "Antigravity",
|
|
343
|
-
configPath: antigravityConfigPath,
|
|
344
|
-
projectDetectPaths: [],
|
|
345
|
-
// Global only - no project support
|
|
346
|
-
configKey: "mcpServers",
|
|
347
|
-
format: "json",
|
|
348
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
349
|
-
detectGlobalInstall: async () => {
|
|
350
|
-
return existsSync(join2(home, ".gemini"));
|
|
351
|
-
},
|
|
352
|
-
transformConfig: transformAntigravityConfig
|
|
353
|
-
},
|
|
354
|
-
cline: {
|
|
355
|
-
name: "cline",
|
|
356
|
-
displayName: "Cline VSCode Extension",
|
|
357
|
-
configPath: clineExtensionConfigPath,
|
|
358
|
-
projectDetectPaths: [],
|
|
359
|
-
// Global only - no project support
|
|
360
|
-
configKey: "mcpServers",
|
|
361
|
-
format: "json",
|
|
362
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
363
|
-
detectGlobalInstall: async () => {
|
|
364
|
-
return existsSync(dirname2(clineExtensionConfigPath));
|
|
365
|
-
},
|
|
366
|
-
transformConfig: transformClineConfig
|
|
367
|
-
},
|
|
368
|
-
"cline-cli": {
|
|
369
|
-
name: "cline-cli",
|
|
370
|
-
displayName: "Cline CLI",
|
|
371
|
-
configPath: clineCliConfigPath,
|
|
372
|
-
projectDetectPaths: [],
|
|
373
|
-
// Global only - no project support
|
|
374
|
-
configKey: "mcpServers",
|
|
375
|
-
format: "json",
|
|
376
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
377
|
-
detectGlobalInstall: async () => {
|
|
378
|
-
return existsSync(dirname2(clineCliConfigPath));
|
|
379
|
-
},
|
|
380
|
-
transformConfig: transformClineConfig
|
|
381
|
-
},
|
|
382
|
-
"claude-code": {
|
|
383
|
-
name: "claude-code",
|
|
384
|
-
displayName: "Claude Code",
|
|
385
|
-
configPath: join2(home, ".claude.json"),
|
|
386
|
-
localConfigPath: ".mcp.json",
|
|
387
|
-
projectDetectPaths: [".mcp.json", ".claude"],
|
|
388
|
-
configKey: "mcpServers",
|
|
389
|
-
format: "json",
|
|
390
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
391
|
-
detectGlobalInstall: async () => {
|
|
392
|
-
return existsSync(join2(home, ".claude"));
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
"claude-desktop": {
|
|
396
|
-
name: "claude-desktop",
|
|
397
|
-
displayName: "Claude Desktop",
|
|
398
|
-
configPath: join2(appSupport, "Claude", "claude_desktop_config.json"),
|
|
399
|
-
projectDetectPaths: [],
|
|
400
|
-
// Global only - no project support
|
|
401
|
-
configKey: "mcpServers",
|
|
402
|
-
format: "json",
|
|
403
|
-
supportedTransports: ["stdio"],
|
|
404
|
-
unsupportedTransportMessage: "Claude Desktop only supports local (stdio) servers via its config file. Add remote servers through Settings \u2192 Connectors in the app instead.",
|
|
405
|
-
detectGlobalInstall: async () => {
|
|
406
|
-
return existsSync(join2(appSupport, "Claude"));
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
codex: {
|
|
410
|
-
name: "codex",
|
|
411
|
-
displayName: "Codex",
|
|
412
|
-
configPath: join2(
|
|
413
|
-
process.env.CODEX_HOME || join2(home, ".codex"),
|
|
414
|
-
"config.toml"
|
|
415
|
-
),
|
|
416
|
-
localConfigPath: ".codex/config.toml",
|
|
417
|
-
projectDetectPaths: [".codex"],
|
|
418
|
-
configKey: "mcp_servers",
|
|
419
|
-
format: "toml",
|
|
420
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
421
|
-
detectGlobalInstall: async () => {
|
|
422
|
-
return existsSync(join2(home, ".codex"));
|
|
423
|
-
},
|
|
424
|
-
transformConfig: transformCodexConfig
|
|
425
|
-
},
|
|
426
|
-
cursor: {
|
|
427
|
-
name: "cursor",
|
|
428
|
-
displayName: "Cursor",
|
|
429
|
-
configPath: join2(home, ".cursor", "mcp.json"),
|
|
430
|
-
localConfigPath: ".cursor/mcp.json",
|
|
431
|
-
projectDetectPaths: [".cursor"],
|
|
432
|
-
configKey: "mcpServers",
|
|
433
|
-
format: "json",
|
|
434
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
435
|
-
detectGlobalInstall: async () => {
|
|
436
|
-
return existsSync(join2(home, ".cursor"));
|
|
437
|
-
},
|
|
438
|
-
transformConfig: transformCursorConfig
|
|
439
|
-
},
|
|
440
|
-
"gemini-cli": {
|
|
441
|
-
name: "gemini-cli",
|
|
442
|
-
displayName: "Gemini CLI",
|
|
443
|
-
configPath: join2(home, ".gemini", "settings.json"),
|
|
444
|
-
localConfigPath: ".gemini/settings.json",
|
|
445
|
-
projectDetectPaths: [".gemini"],
|
|
446
|
-
configKey: "mcpServers",
|
|
447
|
-
format: "json",
|
|
448
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
449
|
-
detectGlobalInstall: async () => {
|
|
450
|
-
return existsSync(join2(home, ".gemini"));
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
goose: {
|
|
454
|
-
name: "goose",
|
|
455
|
-
displayName: "Goose",
|
|
456
|
-
configPath: gooseConfigPath,
|
|
457
|
-
projectDetectPaths: [],
|
|
458
|
-
// Global only - no project support
|
|
459
|
-
configKey: "extensions",
|
|
460
|
-
format: "yaml",
|
|
461
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
462
|
-
detectGlobalInstall: async () => {
|
|
463
|
-
return existsSync(gooseConfigPath);
|
|
464
|
-
},
|
|
465
|
-
transformConfig: transformGooseConfig
|
|
466
|
-
},
|
|
467
|
-
"github-copilot-cli": {
|
|
468
|
-
name: "github-copilot-cli",
|
|
469
|
-
displayName: "GitHub Copilot CLI",
|
|
470
|
-
configPath: copilotConfigPath,
|
|
471
|
-
localConfigPath: ".vscode/mcp.json",
|
|
472
|
-
projectDetectPaths: [".vscode"],
|
|
473
|
-
configKey: "mcpServers",
|
|
474
|
-
localConfigKey: "servers",
|
|
475
|
-
format: "json",
|
|
476
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
477
|
-
detectGlobalInstall: async () => {
|
|
478
|
-
return existsSync(dirname2(copilotConfigPath));
|
|
479
|
-
},
|
|
480
|
-
transformConfig: transformGitHubCopilotCliConfig
|
|
481
|
-
},
|
|
482
|
-
mcporter: {
|
|
483
|
-
name: "mcporter",
|
|
484
|
-
displayName: "MCPorter",
|
|
485
|
-
configPath: join2(home, ".mcporter", "mcporter.json"),
|
|
486
|
-
localConfigPath: "config/mcporter.json",
|
|
487
|
-
projectDetectPaths: ["config/mcporter.json"],
|
|
488
|
-
configKey: "mcpServers",
|
|
489
|
-
format: "json",
|
|
490
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
491
|
-
detectGlobalInstall: async () => {
|
|
492
|
-
return existsSync(join2(home, ".mcporter"));
|
|
493
|
-
},
|
|
494
|
-
resolveConfigPath: resolveMcporterConfigPath
|
|
495
|
-
},
|
|
496
|
-
opencode: {
|
|
497
|
-
name: "opencode",
|
|
498
|
-
displayName: "OpenCode",
|
|
499
|
-
configPath: join2(home, ".config", "opencode", "opencode.json"),
|
|
500
|
-
localConfigPath: "opencode.json",
|
|
501
|
-
projectDetectPaths: ["opencode.json", ".opencode"],
|
|
502
|
-
configKey: "mcp",
|
|
503
|
-
format: "json",
|
|
504
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
505
|
-
detectGlobalInstall: async () => {
|
|
506
|
-
return existsSync(join2(home, ".config", "opencode"));
|
|
507
|
-
},
|
|
508
|
-
transformConfig: transformOpenCodeConfig
|
|
509
|
-
},
|
|
510
|
-
vscode: {
|
|
511
|
-
name: "vscode",
|
|
512
|
-
displayName: "VS Code",
|
|
513
|
-
configPath: join2(vscodePath, "mcp.json"),
|
|
514
|
-
localConfigPath: ".vscode/mcp.json",
|
|
515
|
-
projectDetectPaths: [".vscode"],
|
|
516
|
-
configKey: "servers",
|
|
517
|
-
format: "json",
|
|
518
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
519
|
-
detectGlobalInstall: async () => {
|
|
520
|
-
return existsSync(vscodePath);
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
zed: {
|
|
524
|
-
name: "zed",
|
|
525
|
-
displayName: "Zed",
|
|
526
|
-
configPath: process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed", "settings.json") : join2(appSupport, "zed", "settings.json"),
|
|
527
|
-
localConfigPath: ".zed/settings.json",
|
|
528
|
-
projectDetectPaths: [".zed"],
|
|
529
|
-
configKey: "context_servers",
|
|
530
|
-
format: "json",
|
|
531
|
-
supportedTransports: ["stdio", "http", "sse"],
|
|
532
|
-
detectGlobalInstall: async () => {
|
|
533
|
-
const configDir = process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed") : join2(appSupport, "zed");
|
|
534
|
-
return existsSync(configDir);
|
|
535
|
-
},
|
|
536
|
-
transformConfig: transformZedConfig
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
function getAgentTypes() {
|
|
540
|
-
return Object.keys(agents);
|
|
541
|
-
}
|
|
542
|
-
function supportsProjectConfig(agentType) {
|
|
543
|
-
return agents[agentType].localConfigPath !== void 0;
|
|
544
|
-
}
|
|
545
|
-
function getProjectCapableAgents() {
|
|
546
|
-
return Object.keys(agents).filter(
|
|
547
|
-
(type) => supportsProjectConfig(type)
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
function detectProjectAgents(cwd) {
|
|
551
|
-
const dir = cwd || process.cwd();
|
|
552
|
-
const detected = [];
|
|
553
|
-
for (const [type, config] of Object.entries(agents)) {
|
|
554
|
-
if (!config.localConfigPath) continue;
|
|
555
|
-
for (const detectPath of config.projectDetectPaths) {
|
|
556
|
-
if (existsSync(join2(dir, detectPath))) {
|
|
557
|
-
detected.push(type);
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return detected;
|
|
563
|
-
}
|
|
564
|
-
async function detectAllGlobalAgents() {
|
|
565
|
-
const detected = [];
|
|
566
|
-
for (const [type, config] of Object.entries(agents)) {
|
|
567
|
-
if (await config.detectGlobalInstall()) {
|
|
568
|
-
detected.push(type);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
return detected;
|
|
572
|
-
}
|
|
573
|
-
function isTransportSupported(agentType, transport) {
|
|
574
|
-
return agents[agentType].supportedTransports.includes(transport);
|
|
575
|
-
}
|
|
576
|
-
function buildAgentSelectionChoices(options) {
|
|
577
|
-
const { availableAgents, detectedAgents, agentRouting, lastSelected } = options;
|
|
578
|
-
const detectedSet = new Set(detectedAgents);
|
|
579
|
-
const validLastSelected = lastSelected?.filter(
|
|
580
|
-
(agent) => availableAgents.includes(agent) && !detectedSet.has(agent)
|
|
581
|
-
) ?? [];
|
|
582
|
-
const remainingAgents = availableAgents.filter(
|
|
583
|
-
(agent) => !detectedSet.has(agent) && !validLastSelected.includes(agent)
|
|
584
|
-
);
|
|
585
|
-
const orderedAgents = [
|
|
586
|
-
...detectedAgents,
|
|
587
|
-
...validLastSelected,
|
|
588
|
-
...remainingAgents
|
|
589
|
-
];
|
|
590
|
-
const choices = orderedAgents.map((agentType) => {
|
|
591
|
-
const routing = agentRouting.get(agentType);
|
|
592
|
-
const baseHint = routing === "local" ? "project" : routing === "global" ? "global" : shortenPath(agents[agentType].configPath);
|
|
593
|
-
const lastSelectedHint = validLastSelected.includes(agentType) ? "selected last time" : "";
|
|
594
|
-
const hint = lastSelectedHint ? `${baseHint} \xB7 ${lastSelectedHint}` : baseHint;
|
|
595
|
-
return {
|
|
596
|
-
value: agentType,
|
|
597
|
-
label: agents[agentType].displayName,
|
|
598
|
-
hint
|
|
599
|
-
};
|
|
600
|
-
});
|
|
601
|
-
return { choices, initialValues: detectedAgents };
|
|
602
|
-
}
|
|
603
|
-
async function promptForAgents(message, choices, defaultToAll = false) {
|
|
604
|
-
let lastSelected;
|
|
605
|
-
try {
|
|
606
|
-
lastSelected = await getLastSelectedAgents();
|
|
607
|
-
} catch {
|
|
608
|
-
}
|
|
609
|
-
const validAgents = choices.map((c) => c.value);
|
|
610
|
-
let initialValues;
|
|
611
|
-
if (lastSelected && lastSelected.length > 0) {
|
|
612
|
-
initialValues = lastSelected.filter(
|
|
613
|
-
(a) => validAgents.includes(a)
|
|
614
|
-
);
|
|
615
|
-
if (initialValues.length === 0 && defaultToAll) {
|
|
616
|
-
initialValues = validAgents;
|
|
617
|
-
}
|
|
618
|
-
} else {
|
|
619
|
-
initialValues = defaultToAll ? validAgents : [];
|
|
620
|
-
}
|
|
621
|
-
const selected = await p.multiselect({
|
|
622
|
-
message,
|
|
623
|
-
options: choices,
|
|
624
|
-
required: true,
|
|
625
|
-
initialValues
|
|
626
|
-
});
|
|
627
|
-
if (!p.isCancel(selected)) {
|
|
628
|
-
try {
|
|
629
|
-
await saveSelectedAgents(selected);
|
|
630
|
-
} catch {
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return selected;
|
|
634
|
-
}
|
|
635
|
-
async function selectAgentsInteractive(availableAgents, options) {
|
|
636
|
-
let lastSelected;
|
|
637
|
-
try {
|
|
638
|
-
lastSelected = await getLastSelectedAgents();
|
|
639
|
-
} catch {
|
|
640
|
-
}
|
|
641
|
-
const validLastSelected = lastSelected?.filter(
|
|
642
|
-
(a) => availableAgents.includes(a)
|
|
643
|
-
);
|
|
644
|
-
const selectOptions = [];
|
|
645
|
-
const hasPrevious = validLastSelected && validLastSelected.length > 0;
|
|
646
|
-
if (hasPrevious) {
|
|
647
|
-
const agentNames = validLastSelected.map((a) => agents[a].displayName).join(", ");
|
|
648
|
-
selectOptions.push({
|
|
649
|
-
value: "previous",
|
|
650
|
-
label: "Same as last time",
|
|
651
|
-
hint: agentNames
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
selectOptions.push({
|
|
655
|
-
value: "all",
|
|
656
|
-
label: hasPrevious ? "All available agents" : "All available agents",
|
|
657
|
-
hint: `Install to all ${availableAgents.length} available agents`
|
|
658
|
-
});
|
|
659
|
-
selectOptions.push({
|
|
660
|
-
value: "select",
|
|
661
|
-
label: "Select specific agents",
|
|
662
|
-
hint: "Choose which agents to install to"
|
|
663
|
-
});
|
|
664
|
-
const installChoice = await p.select({
|
|
665
|
-
message: "Install to",
|
|
666
|
-
options: selectOptions
|
|
667
|
-
});
|
|
668
|
-
if (p.isCancel(installChoice)) {
|
|
669
|
-
return installChoice;
|
|
670
|
-
}
|
|
671
|
-
if (installChoice === "all") {
|
|
672
|
-
return availableAgents;
|
|
673
|
-
}
|
|
674
|
-
if (installChoice === "previous" && validLastSelected) {
|
|
675
|
-
return validLastSelected;
|
|
676
|
-
}
|
|
677
|
-
const agentChoices = availableAgents.map((agentType) => {
|
|
678
|
-
const localPath = agents[agentType].localConfigPath;
|
|
679
|
-
const hint = options.global ? shortenPath(agents[agentType].configPath) : localPath ?? shortenPath(agents[agentType].configPath);
|
|
680
|
-
return {
|
|
681
|
-
value: agentType,
|
|
682
|
-
label: agents[agentType].displayName,
|
|
683
|
-
hint
|
|
684
|
-
};
|
|
685
|
-
});
|
|
686
|
-
return promptForAgents("Select agents to install to", agentChoices, false);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// src/source-parser.ts
|
|
690
|
-
function isUrl(input) {
|
|
691
|
-
return input.startsWith("http://") || input.startsWith("https://");
|
|
692
|
-
}
|
|
693
|
-
function isCommand(input) {
|
|
694
|
-
if (input.includes(" ")) {
|
|
695
|
-
return true;
|
|
696
|
-
}
|
|
697
|
-
if (input.startsWith("npx ") || input.startsWith("node ") || input.startsWith("python ")) {
|
|
698
|
-
return true;
|
|
699
|
-
}
|
|
700
|
-
return false;
|
|
701
|
-
}
|
|
702
|
-
function isPackageName(input) {
|
|
703
|
-
if (input.startsWith("@") && input.includes("/")) {
|
|
704
|
-
return true;
|
|
705
|
-
}
|
|
706
|
-
if (/^[a-z0-9][\w.-]*(@[\w.-]+)?$/i.test(input)) {
|
|
707
|
-
return true;
|
|
708
|
-
}
|
|
709
|
-
return false;
|
|
710
|
-
}
|
|
711
|
-
var commonTlds = /* @__PURE__ */ new Set([
|
|
712
|
-
"com",
|
|
713
|
-
"org",
|
|
714
|
-
"net",
|
|
715
|
-
"io",
|
|
716
|
-
"dev",
|
|
717
|
-
"ai",
|
|
718
|
-
"tech",
|
|
719
|
-
"co",
|
|
720
|
-
"app",
|
|
721
|
-
"cloud",
|
|
722
|
-
"sh",
|
|
723
|
-
"run"
|
|
724
|
-
]);
|
|
725
|
-
function extractBrandFromHostname(hostname) {
|
|
726
|
-
const parts = hostname.split(".");
|
|
727
|
-
const meaningfulParts = parts.filter((part) => {
|
|
728
|
-
const lower = part.toLowerCase();
|
|
729
|
-
if (commonTlds.has(lower)) return false;
|
|
730
|
-
if (lower === "mcp" || lower === "api" || lower === "www") return false;
|
|
731
|
-
return true;
|
|
732
|
-
});
|
|
733
|
-
if (meaningfulParts.length > 0) {
|
|
734
|
-
return meaningfulParts[0];
|
|
735
|
-
}
|
|
736
|
-
if (parts.length >= 2) {
|
|
737
|
-
return parts[parts.length - 2];
|
|
738
|
-
}
|
|
739
|
-
return "mcp-server";
|
|
740
|
-
}
|
|
741
|
-
function inferName(input, type) {
|
|
742
|
-
if (type === "remote") {
|
|
743
|
-
try {
|
|
744
|
-
const url = new URL(input);
|
|
745
|
-
return extractBrandFromHostname(url.hostname);
|
|
746
|
-
} catch {
|
|
747
|
-
return "mcp-server";
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
if (type === "command") {
|
|
751
|
-
const parts = input.split(" ");
|
|
752
|
-
let startIndex = 0;
|
|
753
|
-
if (parts[0] === "npx" || parts[0] === "node" || parts[0] === "python") {
|
|
754
|
-
startIndex = 1;
|
|
755
|
-
}
|
|
756
|
-
for (let i = startIndex; i < parts.length; i++) {
|
|
757
|
-
const part = parts[i];
|
|
758
|
-
if (part && !part.startsWith("-")) {
|
|
759
|
-
return extractPackageName(part);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
return "mcp-server";
|
|
763
|
-
}
|
|
764
|
-
return extractPackageName(input);
|
|
765
|
-
}
|
|
766
|
-
function extractPackageName(input) {
|
|
767
|
-
let name = input;
|
|
768
|
-
const atIndex = name.lastIndexOf("@");
|
|
769
|
-
if (atIndex > 0 && !name.startsWith("@")) {
|
|
770
|
-
name = name.slice(0, atIndex);
|
|
771
|
-
} else if (name.startsWith("@") && name.indexOf("@", 1) > 0) {
|
|
772
|
-
const secondAt = name.indexOf("@", 1);
|
|
773
|
-
name = name.slice(0, secondAt);
|
|
774
|
-
}
|
|
775
|
-
if (name.startsWith("@") && name.includes("/")) {
|
|
776
|
-
const parts = name.split("/");
|
|
777
|
-
name = parts[1] || name;
|
|
778
|
-
}
|
|
779
|
-
name = name.replace(/^mcp-server-/, "");
|
|
780
|
-
name = name.replace(/^server-/, "");
|
|
781
|
-
name = name.replace(/-mcp$/, "");
|
|
782
|
-
return name || "mcp-server";
|
|
783
|
-
}
|
|
784
|
-
function parseSource(input) {
|
|
785
|
-
const trimmed = input.trim();
|
|
786
|
-
if (isUrl(trimmed)) {
|
|
787
|
-
return {
|
|
788
|
-
type: "remote",
|
|
789
|
-
value: trimmed,
|
|
790
|
-
inferredName: inferName(trimmed, "remote")
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
if (isCommand(trimmed)) {
|
|
794
|
-
return {
|
|
795
|
-
type: "command",
|
|
796
|
-
value: trimmed,
|
|
797
|
-
inferredName: inferName(trimmed, "command")
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
if (isPackageName(trimmed)) {
|
|
801
|
-
return {
|
|
802
|
-
type: "package",
|
|
803
|
-
value: trimmed,
|
|
804
|
-
inferredName: inferName(trimmed, "package")
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
return {
|
|
808
|
-
type: "package",
|
|
809
|
-
value: trimmed,
|
|
810
|
-
inferredName: inferName(trimmed, "package")
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
function isRemoteSource(parsed) {
|
|
814
|
-
return parsed.type === "remote";
|
|
815
|
-
}
|
|
816
|
-
|
|
817
40
|
// src/find.ts
|
|
818
|
-
import * as
|
|
41
|
+
import * as p from "@clack/prompts";
|
|
819
42
|
|
|
820
43
|
// src/template.ts
|
|
821
44
|
var TEMPLATE_PATTERN = /\$\{([^}]+)\}/g;
|
|
@@ -936,8 +159,8 @@ function pushPositionalSegments(out, d, raw) {
|
|
|
936
159
|
out.push(raw);
|
|
937
160
|
return;
|
|
938
161
|
}
|
|
939
|
-
for (const
|
|
940
|
-
out.push(
|
|
162
|
+
for (const p3 of parts) {
|
|
163
|
+
out.push(p3);
|
|
941
164
|
}
|
|
942
165
|
return;
|
|
943
166
|
}
|
|
@@ -1250,7 +473,7 @@ function formatFindResultRow(entry) {
|
|
|
1250
473
|
return `${display} (${entry.name}) [${transportLabel(entry)}]`;
|
|
1251
474
|
}
|
|
1252
475
|
async function promptValue(field) {
|
|
1253
|
-
return
|
|
476
|
+
return p.text({
|
|
1254
477
|
message: `${field.label} ${field.isRequired ? "(required)" : "(optional)"}`,
|
|
1255
478
|
placeholder: field.placeholder
|
|
1256
479
|
});
|
|
@@ -1259,7 +482,7 @@ async function collectPromptValues(fields, ask) {
|
|
|
1259
482
|
const values = {};
|
|
1260
483
|
for (const field of fields) {
|
|
1261
484
|
const raw = await ask(field);
|
|
1262
|
-
if (
|
|
485
|
+
if (p.isCancel(raw)) {
|
|
1263
486
|
return { values, cancelled: true };
|
|
1264
487
|
}
|
|
1265
488
|
const value = raw != null && typeof raw === "string" ? raw.trim() : "";
|
|
@@ -1356,7 +579,7 @@ function resolveNonInteractivePackage(pkg) {
|
|
|
1356
579
|
};
|
|
1357
580
|
}
|
|
1358
581
|
async function resolveInteractivePackage(pkg) {
|
|
1359
|
-
const promptPackageArg = async (info) =>
|
|
582
|
+
const promptPackageArg = async (info) => p.text({
|
|
1360
583
|
message: `${info.label} ${info.isRequired ? "(required)" : "(optional)"}`,
|
|
1361
584
|
placeholder: info.placeholder
|
|
1362
585
|
});
|
|
@@ -1394,7 +617,7 @@ async function buildInstallPlanForEntry(entry, options) {
|
|
|
1394
617
|
if (options.yes) {
|
|
1395
618
|
mode = "remote";
|
|
1396
619
|
} else {
|
|
1397
|
-
const selected = await
|
|
620
|
+
const selected = await p.select({
|
|
1398
621
|
message: `Install mode for ${entry.name}`,
|
|
1399
622
|
initialValue: "remote",
|
|
1400
623
|
options: [
|
|
@@ -1406,7 +629,7 @@ async function buildInstallPlanForEntry(entry, options) {
|
|
|
1406
629
|
}
|
|
1407
630
|
]
|
|
1408
631
|
});
|
|
1409
|
-
if (
|
|
632
|
+
if (p.isCancel(selected)) return null;
|
|
1410
633
|
mode = selected;
|
|
1411
634
|
}
|
|
1412
635
|
} else {
|
|
@@ -1443,15 +666,15 @@ async function offerFallbackSearch(query, alreadyQueried) {
|
|
|
1443
666
|
let selectedRegistries;
|
|
1444
667
|
if (candidates.length === 1) {
|
|
1445
668
|
const candidate = candidates[0];
|
|
1446
|
-
const confirmed = await
|
|
669
|
+
const confirmed = await p.confirm({
|
|
1447
670
|
message: `Search "${candidate.label ?? candidate.url}" (${candidate.url}) instead?`
|
|
1448
671
|
});
|
|
1449
|
-
if (
|
|
672
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
1450
673
|
return null;
|
|
1451
674
|
}
|
|
1452
675
|
selectedRegistries = [candidate];
|
|
1453
676
|
} else {
|
|
1454
|
-
const selected = await
|
|
677
|
+
const selected = await p.multiselect({
|
|
1455
678
|
message: "Search other registries instead?",
|
|
1456
679
|
options: candidates.map((r) => ({
|
|
1457
680
|
value: r.url,
|
|
@@ -1459,7 +682,7 @@ async function offerFallbackSearch(query, alreadyQueried) {
|
|
|
1459
682
|
})),
|
|
1460
683
|
required: true
|
|
1461
684
|
});
|
|
1462
|
-
if (
|
|
685
|
+
if (p.isCancel(selected)) {
|
|
1463
686
|
return null;
|
|
1464
687
|
}
|
|
1465
688
|
selectedRegistries = candidates.filter(
|
|
@@ -1469,12 +692,12 @@ async function offerFallbackSearch(query, alreadyQueried) {
|
|
|
1469
692
|
const fallbackResult = await searchRegistry(query, selectedRegistries);
|
|
1470
693
|
if (fallbackResult.failedRegistries.length > 0) {
|
|
1471
694
|
for (const failure of fallbackResult.failedRegistries) {
|
|
1472
|
-
|
|
695
|
+
p.log.error(formatRegistryFailure(failure));
|
|
1473
696
|
}
|
|
1474
697
|
}
|
|
1475
698
|
if (fallbackResult.entries.length === 0) {
|
|
1476
699
|
if (fallbackResult.failedRegistries.length === 0) {
|
|
1477
|
-
|
|
700
|
+
p.log.warn(`No MCP servers found for "${query}"`);
|
|
1478
701
|
}
|
|
1479
702
|
return null;
|
|
1480
703
|
}
|
|
@@ -1500,8 +723,8 @@ async function selectEntryWithPagination(visibleEntries, message) {
|
|
|
1500
723
|
label: `\u25BC Show more results (${remaining} remaining)`
|
|
1501
724
|
});
|
|
1502
725
|
}
|
|
1503
|
-
const selected = await
|
|
1504
|
-
if (
|
|
726
|
+
const selected = await p.select({ message, options });
|
|
727
|
+
if (p.isCancel(selected)) return null;
|
|
1505
728
|
if (selected === LOAD_MORE_SENTINEL) {
|
|
1506
729
|
page++;
|
|
1507
730
|
continue;
|
|
@@ -1522,12 +745,12 @@ async function runFind(query, options) {
|
|
|
1522
745
|
const { failedRegistries } = result;
|
|
1523
746
|
if (failedRegistries.length > 0 && entries.length > 0) {
|
|
1524
747
|
for (const failure of failedRegistries) {
|
|
1525
|
-
|
|
748
|
+
p.log.warn(formatRegistryFailure(failure));
|
|
1526
749
|
}
|
|
1527
750
|
}
|
|
1528
751
|
if (entries.length === 0 && failedRegistries.length > 0) {
|
|
1529
752
|
for (const failure of failedRegistries) {
|
|
1530
|
-
|
|
753
|
+
p.log.error(formatRegistryFailure(failure));
|
|
1531
754
|
}
|
|
1532
755
|
if (!options.yes && !isBrowseMode) {
|
|
1533
756
|
const fallbackEntries = await offerFallbackSearch(query, registries);
|
|
@@ -1538,7 +761,7 @@ async function runFind(query, options) {
|
|
|
1538
761
|
}
|
|
1539
762
|
if (entries.length === 0) {
|
|
1540
763
|
if (failedRegistries.length === 0) {
|
|
1541
|
-
|
|
764
|
+
p.log.warn(
|
|
1542
765
|
isBrowseMode ? "No MCP servers found in the configured registries" : `No MCP servers found for "${query}"`
|
|
1543
766
|
);
|
|
1544
767
|
}
|
|
@@ -1554,542 +777,23 @@ async function runFind(query, options) {
|
|
|
1554
777
|
return buildInstallPlanForEntry(entry, options);
|
|
1555
778
|
}
|
|
1556
779
|
|
|
1557
|
-
// src/installer.ts
|
|
1558
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1559
|
-
import { join as join3, dirname as dirname6, isAbsolute, relative, sep } from "path";
|
|
1560
|
-
|
|
1561
|
-
// src/formats/json.ts
|
|
1562
|
-
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
1563
|
-
import { dirname as dirname3 } from "path";
|
|
1564
|
-
import * as jsonc from "jsonc-parser";
|
|
1565
|
-
|
|
1566
|
-
// src/formats/utils.ts
|
|
1567
|
-
function deepMerge(target, source) {
|
|
1568
|
-
const result = { ...target };
|
|
1569
|
-
for (const key in source) {
|
|
1570
|
-
const sourceValue = source[key];
|
|
1571
|
-
const targetValue = result[key];
|
|
1572
|
-
if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
|
|
1573
|
-
result[key] = deepMerge(
|
|
1574
|
-
targetValue && typeof targetValue === "object" ? targetValue : {},
|
|
1575
|
-
sourceValue
|
|
1576
|
-
);
|
|
1577
|
-
} else {
|
|
1578
|
-
result[key] = sourceValue;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
return result;
|
|
1582
|
-
}
|
|
1583
|
-
function getNestedValue(obj, path) {
|
|
1584
|
-
const keys = path.split(".");
|
|
1585
|
-
let current = obj;
|
|
1586
|
-
for (const key of keys) {
|
|
1587
|
-
if (current && typeof current === "object" && key in current) {
|
|
1588
|
-
current = current[key];
|
|
1589
|
-
} else {
|
|
1590
|
-
return void 0;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
return current;
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
// src/formats/json.ts
|
|
1597
|
-
function detectIndent(text3) {
|
|
1598
|
-
let result = null;
|
|
1599
|
-
jsonc.visit(text3, {
|
|
1600
|
-
onObjectProperty: (_property, offset, _length, startLine, startCharacter) => {
|
|
1601
|
-
if (result === null && startLine > 0 && startCharacter > 0) {
|
|
1602
|
-
const lineStart = text3.lastIndexOf("\n", offset - 1) + 1;
|
|
1603
|
-
const whitespace = text3.slice(lineStart, offset);
|
|
1604
|
-
result = {
|
|
1605
|
-
tabSize: startCharacter,
|
|
1606
|
-
insertSpaces: !whitespace.includes(" ")
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
});
|
|
1611
|
-
return result || { tabSize: 2, insertSpaces: true };
|
|
1612
|
-
}
|
|
1613
|
-
function readJsonConfig(filePath) {
|
|
1614
|
-
if (!existsSync2(filePath)) {
|
|
1615
|
-
return {};
|
|
1616
|
-
}
|
|
1617
|
-
const content = readFileSync(filePath, "utf-8");
|
|
1618
|
-
const parsed = jsonc.parse(content);
|
|
1619
|
-
return parsed;
|
|
1620
|
-
}
|
|
1621
|
-
function writeJsonConfig(filePath, config, configKey) {
|
|
1622
|
-
const dir = dirname3(filePath);
|
|
1623
|
-
if (!existsSync2(dir)) {
|
|
1624
|
-
mkdirSync(dir, { recursive: true });
|
|
1625
|
-
}
|
|
1626
|
-
let originalContent = "";
|
|
1627
|
-
let existingConfig = {};
|
|
1628
|
-
if (existsSync2(filePath)) {
|
|
1629
|
-
originalContent = readFileSync(filePath, "utf-8");
|
|
1630
|
-
existingConfig = jsonc.parse(originalContent);
|
|
1631
|
-
}
|
|
1632
|
-
const mergedConfig = deepMerge(existingConfig, config);
|
|
1633
|
-
if (originalContent) {
|
|
1634
|
-
try {
|
|
1635
|
-
const configKeyPath = configKey.split(".");
|
|
1636
|
-
const newValue = getNestedValue(mergedConfig, configKey);
|
|
1637
|
-
const edits = jsonc.modify(originalContent, configKeyPath, newValue, {
|
|
1638
|
-
formattingOptions: detectIndent(originalContent)
|
|
1639
|
-
});
|
|
1640
|
-
const updatedContent = jsonc.applyEdits(originalContent, edits);
|
|
1641
|
-
writeFileSync(filePath, updatedContent);
|
|
1642
|
-
return;
|
|
1643
|
-
} catch {
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
writeFileSync(filePath, JSON.stringify(mergedConfig, null, 2));
|
|
1647
|
-
}
|
|
1648
|
-
function removeJsonConfigKey(filePath, configKey, serverName) {
|
|
1649
|
-
if (!existsSync2(filePath)) {
|
|
1650
|
-
return;
|
|
1651
|
-
}
|
|
1652
|
-
const originalContent = readFileSync(filePath, "utf-8");
|
|
1653
|
-
const configKeyPath = configKey.split(".");
|
|
1654
|
-
try {
|
|
1655
|
-
const edits = jsonc.modify(
|
|
1656
|
-
originalContent,
|
|
1657
|
-
[...configKeyPath, serverName],
|
|
1658
|
-
void 0,
|
|
1659
|
-
{ formattingOptions: detectIndent(originalContent) }
|
|
1660
|
-
);
|
|
1661
|
-
const updatedContent = jsonc.applyEdits(originalContent, edits);
|
|
1662
|
-
writeFileSync(filePath, updatedContent);
|
|
1663
|
-
return;
|
|
1664
|
-
} catch {
|
|
1665
|
-
}
|
|
1666
|
-
const parsed = jsonc.parse(originalContent);
|
|
1667
|
-
const servers = getNestedValue(parsed, configKey);
|
|
1668
|
-
if (servers && typeof servers === "object" && serverName in servers) {
|
|
1669
|
-
delete servers[serverName];
|
|
1670
|
-
}
|
|
1671
|
-
writeFileSync(filePath, JSON.stringify(parsed, null, 2));
|
|
1672
|
-
}
|
|
1673
|
-
function setNestedValue(obj, path, value) {
|
|
1674
|
-
const keys = path.split(".");
|
|
1675
|
-
const lastKey = keys.pop();
|
|
1676
|
-
if (!lastKey) return;
|
|
1677
|
-
let current = obj;
|
|
1678
|
-
for (const key of keys) {
|
|
1679
|
-
if (!current[key] || typeof current[key] !== "object") {
|
|
1680
|
-
current[key] = {};
|
|
1681
|
-
}
|
|
1682
|
-
current = current[key];
|
|
1683
|
-
}
|
|
1684
|
-
current[lastKey] = value;
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
// src/formats/yaml.ts
|
|
1688
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
1689
|
-
import { dirname as dirname4 } from "path";
|
|
1690
|
-
import yaml from "js-yaml";
|
|
1691
|
-
function readYamlConfig(filePath) {
|
|
1692
|
-
if (!existsSync3(filePath)) {
|
|
1693
|
-
return {};
|
|
1694
|
-
}
|
|
1695
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
1696
|
-
const parsed = yaml.load(content);
|
|
1697
|
-
return parsed || {};
|
|
1698
|
-
}
|
|
1699
|
-
function removeYamlConfigKey(filePath, configKey, serverName) {
|
|
1700
|
-
if (!existsSync3(filePath)) {
|
|
1701
|
-
return;
|
|
1702
|
-
}
|
|
1703
|
-
const existing = readYamlConfig(filePath);
|
|
1704
|
-
const keys = configKey.split(".");
|
|
1705
|
-
let current = existing;
|
|
1706
|
-
for (const key of keys) {
|
|
1707
|
-
if (current && typeof current === "object" && key in current) {
|
|
1708
|
-
current = current[key];
|
|
1709
|
-
} else {
|
|
1710
|
-
return;
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
if (current && typeof current === "object" && serverName in current) {
|
|
1714
|
-
delete current[serverName];
|
|
1715
|
-
}
|
|
1716
|
-
const content = yaml.dump(existing, {
|
|
1717
|
-
indent: 2,
|
|
1718
|
-
lineWidth: -1,
|
|
1719
|
-
noRefs: true
|
|
1720
|
-
});
|
|
1721
|
-
writeFileSync2(filePath, content);
|
|
1722
|
-
}
|
|
1723
|
-
function writeYamlConfig(filePath, config) {
|
|
1724
|
-
const dir = dirname4(filePath);
|
|
1725
|
-
if (!existsSync3(dir)) {
|
|
1726
|
-
mkdirSync2(dir, { recursive: true });
|
|
1727
|
-
}
|
|
1728
|
-
let existingConfig = {};
|
|
1729
|
-
if (existsSync3(filePath)) {
|
|
1730
|
-
existingConfig = readYamlConfig(filePath);
|
|
1731
|
-
}
|
|
1732
|
-
const mergedConfig = deepMerge(existingConfig, config);
|
|
1733
|
-
const content = yaml.dump(mergedConfig, {
|
|
1734
|
-
indent: 2,
|
|
1735
|
-
lineWidth: -1,
|
|
1736
|
-
noRefs: true
|
|
1737
|
-
});
|
|
1738
|
-
writeFileSync2(filePath, content);
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
// src/formats/toml.ts
|
|
1742
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
1743
|
-
import { dirname as dirname5 } from "path";
|
|
1744
|
-
import * as TOML from "@iarna/toml";
|
|
1745
|
-
function readTomlConfig(filePath) {
|
|
1746
|
-
if (!existsSync4(filePath)) {
|
|
1747
|
-
return {};
|
|
1748
|
-
}
|
|
1749
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
1750
|
-
const parsed = TOML.parse(content);
|
|
1751
|
-
return parsed;
|
|
1752
|
-
}
|
|
1753
|
-
function removeTomlConfigKey(filePath, configKey, serverName) {
|
|
1754
|
-
if (!existsSync4(filePath)) {
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
const existing = readTomlConfig(filePath);
|
|
1758
|
-
const keys = configKey.split(".");
|
|
1759
|
-
let current = existing;
|
|
1760
|
-
for (const key of keys) {
|
|
1761
|
-
if (current && typeof current === "object" && key in current) {
|
|
1762
|
-
current = current[key];
|
|
1763
|
-
} else {
|
|
1764
|
-
return;
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
if (current && typeof current === "object" && serverName in current) {
|
|
1768
|
-
delete current[serverName];
|
|
1769
|
-
}
|
|
1770
|
-
const content = TOML.stringify(existing);
|
|
1771
|
-
writeFileSync3(filePath, content);
|
|
1772
|
-
}
|
|
1773
|
-
function writeTomlConfig(filePath, config) {
|
|
1774
|
-
const dir = dirname5(filePath);
|
|
1775
|
-
if (!existsSync4(dir)) {
|
|
1776
|
-
mkdirSync3(dir, { recursive: true });
|
|
1777
|
-
}
|
|
1778
|
-
let existingConfig = {};
|
|
1779
|
-
if (existsSync4(filePath)) {
|
|
1780
|
-
existingConfig = readTomlConfig(filePath);
|
|
1781
|
-
}
|
|
1782
|
-
const mergedConfig = deepMerge(existingConfig, config);
|
|
1783
|
-
const content = TOML.stringify(mergedConfig);
|
|
1784
|
-
writeFileSync3(filePath, content);
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
// src/formats/index.ts
|
|
1788
|
-
function readConfig2(filePath, format) {
|
|
1789
|
-
switch (format) {
|
|
1790
|
-
case "json":
|
|
1791
|
-
return readJsonConfig(filePath);
|
|
1792
|
-
case "yaml":
|
|
1793
|
-
return readYamlConfig(filePath);
|
|
1794
|
-
case "toml":
|
|
1795
|
-
return readTomlConfig(filePath);
|
|
1796
|
-
default:
|
|
1797
|
-
throw new Error(`Unsupported config format: ${format}`);
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
function removeServerFromConfig(filePath, format, configKey, serverName) {
|
|
1801
|
-
switch (format) {
|
|
1802
|
-
case "json":
|
|
1803
|
-
removeJsonConfigKey(filePath, configKey, serverName);
|
|
1804
|
-
break;
|
|
1805
|
-
case "yaml":
|
|
1806
|
-
removeYamlConfigKey(filePath, configKey, serverName);
|
|
1807
|
-
break;
|
|
1808
|
-
case "toml":
|
|
1809
|
-
removeTomlConfigKey(filePath, configKey, serverName);
|
|
1810
|
-
break;
|
|
1811
|
-
default:
|
|
1812
|
-
throw new Error(`Unsupported config format: ${format}`);
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
function writeConfig2(filePath, config, format, configKey) {
|
|
1816
|
-
switch (format) {
|
|
1817
|
-
case "json":
|
|
1818
|
-
writeJsonConfig(filePath, config, configKey);
|
|
1819
|
-
break;
|
|
1820
|
-
case "yaml":
|
|
1821
|
-
writeYamlConfig(filePath, config);
|
|
1822
|
-
break;
|
|
1823
|
-
case "toml":
|
|
1824
|
-
writeTomlConfig(filePath, config);
|
|
1825
|
-
break;
|
|
1826
|
-
default:
|
|
1827
|
-
throw new Error(`Unsupported config format: ${format}`);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
function buildConfigWithKey(configKey, serverName, serverConfig) {
|
|
1831
|
-
const config = {};
|
|
1832
|
-
const servers = {};
|
|
1833
|
-
servers[serverName] = serverConfig;
|
|
1834
|
-
setNestedValue(config, configKey, servers);
|
|
1835
|
-
return config;
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
// src/installer.ts
|
|
1839
|
-
function buildServerConfig(parsed, options = {}) {
|
|
1840
|
-
if (parsed.type === "remote") {
|
|
1841
|
-
const config2 = {
|
|
1842
|
-
type: options.transport ?? "http",
|
|
1843
|
-
url: parsed.value
|
|
1844
|
-
};
|
|
1845
|
-
if (options.headers && Object.keys(options.headers).length > 0) {
|
|
1846
|
-
config2.headers = options.headers;
|
|
1847
|
-
}
|
|
1848
|
-
return config2;
|
|
1849
|
-
}
|
|
1850
|
-
if (parsed.type === "command") {
|
|
1851
|
-
const parts = parsed.value.split(" ");
|
|
1852
|
-
const command = parts[0];
|
|
1853
|
-
const args = [...parts.slice(1), ...options.args ?? []];
|
|
1854
|
-
const config2 = {
|
|
1855
|
-
command,
|
|
1856
|
-
args
|
|
1857
|
-
};
|
|
1858
|
-
if (options.env && Object.keys(options.env).length > 0) {
|
|
1859
|
-
config2.env = options.env;
|
|
1860
|
-
}
|
|
1861
|
-
return config2;
|
|
1862
|
-
}
|
|
1863
|
-
const config = {
|
|
1864
|
-
command: "npx",
|
|
1865
|
-
args: ["-y", parsed.value, ...options.args ?? []]
|
|
1866
|
-
};
|
|
1867
|
-
if (options.env && Object.keys(options.env).length > 0) {
|
|
1868
|
-
config.env = options.env;
|
|
1869
|
-
}
|
|
1870
|
-
return config;
|
|
1871
|
-
}
|
|
1872
|
-
function updateGitignoreWithPaths(paths, options = {}) {
|
|
1873
|
-
const cwd = options.cwd || process.cwd();
|
|
1874
|
-
const gitignorePath = join3(cwd, ".gitignore");
|
|
1875
|
-
const existingContent = existsSync5(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : "";
|
|
1876
|
-
const existingEntries = new Set(
|
|
1877
|
-
existingContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0)
|
|
1878
|
-
);
|
|
1879
|
-
const entriesToAdd = [];
|
|
1880
|
-
for (const filePath of paths) {
|
|
1881
|
-
const relativePath = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
|
|
1882
|
-
if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
-
const normalizedPath = relativePath.split(sep).join("/");
|
|
1886
|
-
const cleanPath = normalizedPath.startsWith("./") ? normalizedPath.slice(2) : normalizedPath;
|
|
1887
|
-
if (!cleanPath || cleanPath === ".gitignore" || existingEntries.has(cleanPath)) {
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
existingEntries.add(cleanPath);
|
|
1891
|
-
entriesToAdd.push(cleanPath);
|
|
1892
|
-
}
|
|
1893
|
-
if (entriesToAdd.length > 0) {
|
|
1894
|
-
let nextContent = existingContent;
|
|
1895
|
-
if (nextContent.length > 0 && !nextContent.endsWith("\n")) {
|
|
1896
|
-
nextContent += "\n";
|
|
1897
|
-
}
|
|
1898
|
-
nextContent += `${entriesToAdd.join("\n")}
|
|
1899
|
-
`;
|
|
1900
|
-
writeFileSync4(gitignorePath, nextContent, "utf-8");
|
|
1901
|
-
}
|
|
1902
|
-
return {
|
|
1903
|
-
path: gitignorePath,
|
|
1904
|
-
added: entriesToAdd
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
function getConfigPath2(agent, options = {}) {
|
|
1908
|
-
const local = Boolean(options.local);
|
|
1909
|
-
const cwd = options.cwd || process.cwd();
|
|
1910
|
-
if (agent.resolveConfigPath) {
|
|
1911
|
-
return agent.resolveConfigPath(agent, { local, cwd });
|
|
1912
|
-
}
|
|
1913
|
-
if (local && agent.localConfigPath) {
|
|
1914
|
-
return join3(cwd, agent.localConfigPath);
|
|
1915
|
-
}
|
|
1916
|
-
return agent.configPath;
|
|
1917
|
-
}
|
|
1918
|
-
function getConfigKey(agent, options = {}) {
|
|
1919
|
-
if (options.local && agent.localConfigKey) {
|
|
1920
|
-
return agent.localConfigKey;
|
|
1921
|
-
}
|
|
1922
|
-
return agent.configKey;
|
|
1923
|
-
}
|
|
1924
|
-
function installServerForAgent(serverName, serverConfig, agentType, options = {}) {
|
|
1925
|
-
const agent = agents[agentType];
|
|
1926
|
-
const configPath = getConfigPath2(agent, options);
|
|
1927
|
-
try {
|
|
1928
|
-
const dir = dirname6(configPath);
|
|
1929
|
-
if (!existsSync5(dir)) {
|
|
1930
|
-
mkdirSync4(dir, { recursive: true });
|
|
1931
|
-
}
|
|
1932
|
-
const transformedConfig = agent.transformConfig ? agent.transformConfig(serverName, serverConfig, {
|
|
1933
|
-
local: Boolean(options.local)
|
|
1934
|
-
}) : serverConfig;
|
|
1935
|
-
const configKey = getConfigKey(agent, options);
|
|
1936
|
-
const config = buildConfigWithKey(configKey, serverName, transformedConfig);
|
|
1937
|
-
writeConfig2(configPath, config, agent.format, configKey);
|
|
1938
|
-
return {
|
|
1939
|
-
success: true,
|
|
1940
|
-
path: configPath
|
|
1941
|
-
};
|
|
1942
|
-
} catch (error) {
|
|
1943
|
-
return {
|
|
1944
|
-
success: false,
|
|
1945
|
-
path: configPath,
|
|
1946
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
function installServer(serverName, serverConfig, agentTypes, options = {}) {
|
|
1951
|
-
const results = /* @__PURE__ */ new Map();
|
|
1952
|
-
for (const agentType of agentTypes) {
|
|
1953
|
-
const routing = options.routing?.get(agentType);
|
|
1954
|
-
const installOptions = {
|
|
1955
|
-
local: routing === "local",
|
|
1956
|
-
cwd: options.cwd
|
|
1957
|
-
};
|
|
1958
|
-
const result = installServerForAgent(
|
|
1959
|
-
serverName,
|
|
1960
|
-
serverConfig,
|
|
1961
|
-
agentType,
|
|
1962
|
-
installOptions
|
|
1963
|
-
);
|
|
1964
|
-
results.set(agentType, result);
|
|
1965
|
-
}
|
|
1966
|
-
return results;
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
// src/reader.ts
|
|
1970
|
-
function extractServerIdentity(serverConfig) {
|
|
1971
|
-
for (const key of ["url", "uri", "serverUrl"]) {
|
|
1972
|
-
const value = serverConfig[key];
|
|
1973
|
-
if (typeof value === "string" && value.length > 0) {
|
|
1974
|
-
return value;
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
const command = typeof serverConfig.command === "string" ? serverConfig.command : typeof serverConfig.cmd === "string" ? serverConfig.cmd : void 0;
|
|
1978
|
-
if (!command) {
|
|
1979
|
-
return "";
|
|
1980
|
-
}
|
|
1981
|
-
const rawArgs = Array.isArray(serverConfig.args) ? serverConfig.args.filter((a) => typeof a === "string") : Array.isArray(serverConfig.command) ? serverConfig.command.slice(1).filter((a) => typeof a === "string") : [];
|
|
1982
|
-
if (command === "npx" || command === "bunx") {
|
|
1983
|
-
const yIndex = rawArgs.indexOf("-y");
|
|
1984
|
-
const pkgIndex = yIndex >= 0 ? yIndex + 1 : 0;
|
|
1985
|
-
const pkg = rawArgs[pkgIndex];
|
|
1986
|
-
if (pkg && !pkg.startsWith("-")) {
|
|
1987
|
-
return pkg;
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
if (Array.isArray(serverConfig.command)) {
|
|
1991
|
-
return serverConfig.command.join(" ");
|
|
1992
|
-
}
|
|
1993
|
-
if (rawArgs.length > 0) {
|
|
1994
|
-
return `${command} ${rawArgs.join(" ")}`;
|
|
1995
|
-
}
|
|
1996
|
-
return command;
|
|
1997
|
-
}
|
|
1998
|
-
function readServersForAgent(agentType, options) {
|
|
1999
|
-
const agent = agents[agentType];
|
|
2000
|
-
const installOptions = {
|
|
2001
|
-
local: options.scope === "local",
|
|
2002
|
-
cwd: options.cwd
|
|
2003
|
-
};
|
|
2004
|
-
const configPath = getConfigPath2(agent, installOptions);
|
|
2005
|
-
const configKey = getConfigKey(agent, installOptions);
|
|
2006
|
-
const fullConfig = readConfig2(configPath, agent.format);
|
|
2007
|
-
const serversObj = getNestedValue(fullConfig, configKey);
|
|
2008
|
-
const servers = [];
|
|
2009
|
-
if (serversObj && typeof serversObj === "object" && !Array.isArray(serversObj)) {
|
|
2010
|
-
for (const [serverName, serverConfig] of Object.entries(serversObj)) {
|
|
2011
|
-
if (serverConfig && typeof serverConfig === "object") {
|
|
2012
|
-
const config = serverConfig;
|
|
2013
|
-
servers.push({
|
|
2014
|
-
serverName,
|
|
2015
|
-
config,
|
|
2016
|
-
identity: extractServerIdentity(config),
|
|
2017
|
-
agentType,
|
|
2018
|
-
scope: options.scope,
|
|
2019
|
-
configPath
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
return {
|
|
2025
|
-
agentType,
|
|
2026
|
-
displayName: agent.displayName,
|
|
2027
|
-
detected: true,
|
|
2028
|
-
scope: options.scope,
|
|
2029
|
-
configPath,
|
|
2030
|
-
servers
|
|
2031
|
-
};
|
|
2032
|
-
}
|
|
2033
|
-
async function gatherInstalledServers(options) {
|
|
2034
|
-
const scope = options.global ? "global" : "local";
|
|
2035
|
-
const results = [];
|
|
2036
|
-
if (options.agents && options.agents.length > 0) {
|
|
2037
|
-
const detectedSet = new Set(
|
|
2038
|
-
options.global ? await detectAllGlobalAgents() : detectProjectAgents(options.cwd)
|
|
2039
|
-
);
|
|
2040
|
-
for (const agentType of options.agents) {
|
|
2041
|
-
const detected = detectedSet.has(agentType);
|
|
2042
|
-
if (detected) {
|
|
2043
|
-
results.push(
|
|
2044
|
-
readServersForAgent(agentType, { scope, cwd: options.cwd })
|
|
2045
|
-
);
|
|
2046
|
-
} else {
|
|
2047
|
-
const agent = agents[agentType];
|
|
2048
|
-
const installOptions = {
|
|
2049
|
-
local: scope === "local",
|
|
2050
|
-
cwd: options.cwd
|
|
2051
|
-
};
|
|
2052
|
-
results.push({
|
|
2053
|
-
agentType,
|
|
2054
|
-
displayName: agent.displayName,
|
|
2055
|
-
detected: false,
|
|
2056
|
-
scope,
|
|
2057
|
-
configPath: getConfigPath2(agent, installOptions),
|
|
2058
|
-
servers: []
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
} else {
|
|
2063
|
-
const detected = options.global ? await detectAllGlobalAgents() : detectProjectAgents(options.cwd);
|
|
2064
|
-
for (const agentType of detected) {
|
|
2065
|
-
results.push(readServersForAgent(agentType, { scope, cwd: options.cwd }));
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
return results;
|
|
2069
|
-
}
|
|
2070
|
-
function findMatchingServers(agentServersList, query) {
|
|
2071
|
-
const lowerQuery = query.toLowerCase();
|
|
2072
|
-
const matches = [];
|
|
2073
|
-
for (const agentServers of agentServersList) {
|
|
2074
|
-
for (const server of agentServers.servers) {
|
|
2075
|
-
const nameMatch = server.serverName.toLowerCase().includes(lowerQuery);
|
|
2076
|
-
const identityMatch = server.identity === query;
|
|
2077
|
-
if (nameMatch || identityMatch) {
|
|
2078
|
-
matches.push(server);
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
return matches;
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
780
|
// package.json
|
|
2086
781
|
var package_default = {
|
|
2087
782
|
name: "add-mcp",
|
|
2088
|
-
version: "1.
|
|
783
|
+
version: "1.10.2",
|
|
2089
784
|
description: "Add MCP servers to your favorite coding agents with a single command.",
|
|
2090
785
|
author: "Andre Landgraf <andre@neon.tech>",
|
|
2091
786
|
license: "Apache-2.0",
|
|
2092
787
|
type: "module",
|
|
788
|
+
main: "./dist/lib.js",
|
|
789
|
+
types: "./dist/lib.d.ts",
|
|
790
|
+
exports: {
|
|
791
|
+
".": {
|
|
792
|
+
types: "./dist/lib.d.ts",
|
|
793
|
+
import: "./dist/lib.js"
|
|
794
|
+
},
|
|
795
|
+
"./package.json": "./package.json"
|
|
796
|
+
},
|
|
2093
797
|
bin: {
|
|
2094
798
|
"add-mcp": "dist/index.js"
|
|
2095
799
|
},
|
|
@@ -2099,10 +803,10 @@ var package_default = {
|
|
|
2099
803
|
],
|
|
2100
804
|
scripts: {
|
|
2101
805
|
fmt: "prettier --write .",
|
|
2102
|
-
build: "tsup src/index.ts --format esm --dts --clean",
|
|
806
|
+
build: "tsup src/index.ts src/lib.ts --format esm --dts --clean",
|
|
2103
807
|
dev: "tsx src/index.ts",
|
|
2104
|
-
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
2105
|
-
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts",
|
|
808
|
+
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/lib.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
809
|
+
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/package-arguments.test.ts && tsx tests/template.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/lib.test.ts",
|
|
2106
810
|
"registry:sort": "tsx scripts/sort-registry.ts",
|
|
2107
811
|
"registry:verify": "tsx scripts/verify-registry.ts",
|
|
2108
812
|
"test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
@@ -2187,10 +891,10 @@ function showLogo() {
|
|
|
2187
891
|
console.log(`${GRAYS[i]}${line}${RESET}`);
|
|
2188
892
|
});
|
|
2189
893
|
}
|
|
2190
|
-
function
|
|
2191
|
-
const
|
|
2192
|
-
if (fullPath.startsWith(
|
|
2193
|
-
return fullPath.replace(
|
|
894
|
+
function shortenPath(fullPath) {
|
|
895
|
+
const home = homedir();
|
|
896
|
+
if (fullPath.startsWith(home)) {
|
|
897
|
+
return fullPath.replace(home, "~");
|
|
2194
898
|
}
|
|
2195
899
|
return fullPath;
|
|
2196
900
|
}
|
|
@@ -2209,13 +913,13 @@ async function ensureFindRegistriesConfigured(yes) {
|
|
|
2209
913
|
if (configured.length > 0) {
|
|
2210
914
|
return configured;
|
|
2211
915
|
}
|
|
2212
|
-
|
|
916
|
+
p2.log.warn("Find requires configuring one or more registries");
|
|
2213
917
|
if (yes) {
|
|
2214
|
-
|
|
918
|
+
p2.log.error("Re-run without --yes to configure registries for find/search");
|
|
2215
919
|
return null;
|
|
2216
920
|
}
|
|
2217
921
|
const defaults = getDefaultFindRegistries();
|
|
2218
|
-
const selected = await
|
|
922
|
+
const selected = await p2.multiselect({
|
|
2219
923
|
message: "[One time] Please select what MCP registries you would like to configure globally for search",
|
|
2220
924
|
options: defaults.map((registry) => ({
|
|
2221
925
|
value: registry.url,
|
|
@@ -2223,15 +927,15 @@ async function ensureFindRegistriesConfigured(yes) {
|
|
|
2223
927
|
})),
|
|
2224
928
|
required: true
|
|
2225
929
|
});
|
|
2226
|
-
if (
|
|
930
|
+
if (p2.isCancel(selected)) {
|
|
2227
931
|
return null;
|
|
2228
932
|
}
|
|
2229
933
|
const selectedRegistries = defaults.filter(
|
|
2230
934
|
(registry) => selected.includes(registry.url)
|
|
2231
935
|
);
|
|
2232
936
|
await saveFindRegistries(selectedRegistries);
|
|
2233
|
-
|
|
2234
|
-
`Selection has been saved to ${
|
|
937
|
+
p2.log.info(
|
|
938
|
+
`Selection has been saved to ${shortenPath(getConfigPath())} - you can remove or update it any time.`
|
|
2235
939
|
);
|
|
2236
940
|
return selectedRegistries;
|
|
2237
941
|
}
|
|
@@ -2426,7 +1130,7 @@ async function runFindCommand(keyword, rawOptions) {
|
|
|
2426
1130
|
const query = (keyword ?? "").trim();
|
|
2427
1131
|
const registries = await ensureFindRegistriesConfigured(options.yes);
|
|
2428
1132
|
if (!registries) {
|
|
2429
|
-
|
|
1133
|
+
p2.cancel("Find cancelled");
|
|
2430
1134
|
process.exit(0);
|
|
2431
1135
|
}
|
|
2432
1136
|
const installPlan = await runFind(query, {
|
|
@@ -2435,7 +1139,7 @@ async function runFindCommand(keyword, rawOptions) {
|
|
|
2435
1139
|
preferredTransport: inferFindPreferredTransport(options)
|
|
2436
1140
|
});
|
|
2437
1141
|
if (!installPlan) {
|
|
2438
|
-
|
|
1142
|
+
p2.cancel("Find cancelled");
|
|
2439
1143
|
process.exit(0);
|
|
2440
1144
|
}
|
|
2441
1145
|
const mergedOptions = {
|
|
@@ -2511,13 +1215,13 @@ async function runListCommand(options) {
|
|
|
2511
1215
|
showLogo();
|
|
2512
1216
|
console.log();
|
|
2513
1217
|
const explicitAgents = resolveAgentFlags(options.agent);
|
|
2514
|
-
const agentServersList = await
|
|
1218
|
+
const agentServersList = await listInstalledServers({
|
|
2515
1219
|
global: options.global,
|
|
2516
1220
|
agents: explicitAgents.length > 0 ? explicitAgents : void 0
|
|
2517
1221
|
});
|
|
2518
1222
|
if (agentServersList.length === 0) {
|
|
2519
1223
|
const hint = options.global ? "No agents detected globally. Use -a to target a specific agent." : "No agents detected in this project. Use -g for global or -a to target a specific agent.";
|
|
2520
|
-
|
|
1224
|
+
p2.log.info(hint);
|
|
2521
1225
|
console.log();
|
|
2522
1226
|
return;
|
|
2523
1227
|
}
|
|
@@ -2548,13 +1252,13 @@ async function runRemoveCommand(query, options) {
|
|
|
2548
1252
|
showLogo();
|
|
2549
1253
|
console.log();
|
|
2550
1254
|
const explicitAgents = resolveAgentFlags(options.agent);
|
|
2551
|
-
const agentServersList = await
|
|
1255
|
+
const agentServersList = await listInstalledServers({
|
|
2552
1256
|
global: options.global,
|
|
2553
1257
|
agents: explicitAgents.length > 0 ? explicitAgents : void 0
|
|
2554
1258
|
});
|
|
2555
1259
|
const matches = findMatchingServers(agentServersList, query);
|
|
2556
1260
|
if (matches.length === 0) {
|
|
2557
|
-
|
|
1261
|
+
p2.log.info(`No matching servers found for '${query}'`);
|
|
2558
1262
|
console.log();
|
|
2559
1263
|
return;
|
|
2560
1264
|
}
|
|
@@ -2566,24 +1270,24 @@ async function runRemoveCommand(query, options) {
|
|
|
2566
1270
|
let selectedIndices;
|
|
2567
1271
|
if (options.yes) {
|
|
2568
1272
|
selectedIndices = matches.map((_, i) => i);
|
|
2569
|
-
|
|
1273
|
+
p2.log.info(
|
|
2570
1274
|
`Removing ${matches.length} server${matches.length !== 1 ? "s" : ""} matching '${query}'`
|
|
2571
1275
|
);
|
|
2572
1276
|
} else {
|
|
2573
|
-
const selected = await
|
|
1277
|
+
const selected = await p2.multiselect({
|
|
2574
1278
|
message: `Select servers to remove (${matches.length} match${matches.length !== 1 ? "es" : ""} found)`,
|
|
2575
1279
|
options: matchOptions,
|
|
2576
1280
|
required: false,
|
|
2577
1281
|
initialValues: matches.map((_, i) => i)
|
|
2578
1282
|
});
|
|
2579
|
-
if (
|
|
2580
|
-
|
|
1283
|
+
if (p2.isCancel(selected)) {
|
|
1284
|
+
p2.log.info("No changes made");
|
|
2581
1285
|
console.log();
|
|
2582
1286
|
return;
|
|
2583
1287
|
}
|
|
2584
1288
|
selectedIndices = selected;
|
|
2585
1289
|
if (selectedIndices.length === 0) {
|
|
2586
|
-
|
|
1290
|
+
p2.log.info("No changes made");
|
|
2587
1291
|
console.log();
|
|
2588
1292
|
return;
|
|
2589
1293
|
}
|
|
@@ -2603,13 +1307,13 @@ async function runRemoveCommand(query, options) {
|
|
|
2603
1307
|
removedCount++;
|
|
2604
1308
|
affectedAgents.add(agent.displayName);
|
|
2605
1309
|
} catch (error) {
|
|
2606
|
-
|
|
1310
|
+
p2.log.error(
|
|
2607
1311
|
`Failed to remove ${server.serverName} from ${agent.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2608
1312
|
);
|
|
2609
1313
|
}
|
|
2610
1314
|
}
|
|
2611
1315
|
if (removedCount > 0) {
|
|
2612
|
-
|
|
1316
|
+
p2.log.success(
|
|
2613
1317
|
`Removed ${removedCount} server${removedCount !== 1 ? "s" : ""} from ${affectedAgents.size} agent${affectedAgents.size !== 1 ? "s" : ""}`
|
|
2614
1318
|
);
|
|
2615
1319
|
}
|
|
@@ -2709,14 +1413,14 @@ function buildSyncGroups(agentServersList) {
|
|
|
2709
1413
|
async function runSyncCommand(options) {
|
|
2710
1414
|
showLogo();
|
|
2711
1415
|
console.log();
|
|
2712
|
-
const agentServersList = await
|
|
1416
|
+
const agentServersList = await listInstalledServers({
|
|
2713
1417
|
global: options.global
|
|
2714
1418
|
});
|
|
2715
1419
|
const agentsWithServers = agentServersList.filter(
|
|
2716
1420
|
(a) => a.servers.length > 0
|
|
2717
1421
|
);
|
|
2718
1422
|
if (agentServersList.length < 2) {
|
|
2719
|
-
|
|
1423
|
+
p2.log.info("Need at least 2 detected agents to sync");
|
|
2720
1424
|
console.log();
|
|
2721
1425
|
return;
|
|
2722
1426
|
}
|
|
@@ -2747,7 +1451,7 @@ async function runSyncCommand(options) {
|
|
|
2747
1451
|
}
|
|
2748
1452
|
}
|
|
2749
1453
|
if (renames.length === 0 && additions.length === 0 && skipped.length === 0) {
|
|
2750
|
-
|
|
1454
|
+
p2.log.info("All servers are already in sync");
|
|
2751
1455
|
console.log();
|
|
2752
1456
|
return;
|
|
2753
1457
|
}
|
|
@@ -2775,20 +1479,20 @@ async function runSyncCommand(options) {
|
|
|
2775
1479
|
}
|
|
2776
1480
|
}
|
|
2777
1481
|
if (renames.length === 0 && additions.length === 0) {
|
|
2778
|
-
|
|
2779
|
-
|
|
1482
|
+
p2.note(planLines.join("\n"), "Sync Plan");
|
|
1483
|
+
p2.log.info(
|
|
2780
1484
|
"All servers are already in sync (some skipped due to conflicts)"
|
|
2781
1485
|
);
|
|
2782
1486
|
console.log();
|
|
2783
1487
|
return;
|
|
2784
1488
|
}
|
|
2785
|
-
|
|
1489
|
+
p2.note(planLines.join("\n"), "Sync Plan");
|
|
2786
1490
|
if (!options.yes) {
|
|
2787
|
-
const confirmed = await
|
|
1491
|
+
const confirmed = await p2.confirm({
|
|
2788
1492
|
message: "Proceed with sync?"
|
|
2789
1493
|
});
|
|
2790
|
-
if (
|
|
2791
|
-
|
|
1494
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
1495
|
+
p2.log.info("No changes made");
|
|
2792
1496
|
console.log();
|
|
2793
1497
|
return;
|
|
2794
1498
|
}
|
|
@@ -2806,7 +1510,7 @@ async function runSyncCommand(options) {
|
|
|
2806
1510
|
if (result.success) {
|
|
2807
1511
|
changeCount++;
|
|
2808
1512
|
} else {
|
|
2809
|
-
|
|
1513
|
+
p2.log.error(
|
|
2810
1514
|
`Failed to write ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
|
|
2811
1515
|
);
|
|
2812
1516
|
}
|
|
@@ -2822,7 +1526,7 @@ async function runSyncCommand(options) {
|
|
|
2822
1526
|
if (result.success) {
|
|
2823
1527
|
changeCount++;
|
|
2824
1528
|
} else {
|
|
2825
|
-
|
|
1529
|
+
p2.log.error(
|
|
2826
1530
|
`Failed to add ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
|
|
2827
1531
|
);
|
|
2828
1532
|
}
|
|
@@ -2840,12 +1544,12 @@ async function runSyncCommand(options) {
|
|
|
2840
1544
|
oldName
|
|
2841
1545
|
);
|
|
2842
1546
|
} catch (error) {
|
|
2843
|
-
|
|
1547
|
+
p2.log.error(
|
|
2844
1548
|
`Failed to remove old alias ${oldName} from ${agentConfig.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2845
1549
|
);
|
|
2846
1550
|
}
|
|
2847
1551
|
}
|
|
2848
|
-
|
|
1552
|
+
p2.log.success(
|
|
2849
1553
|
`Synced ${changeCount} server${changeCount !== 1 ? "s" : ""} across ${detectedAgentTypes.size} agent${detectedAgentTypes.size !== 1 ? "s" : ""}`
|
|
2850
1554
|
);
|
|
2851
1555
|
console.log();
|
|
@@ -2899,8 +1603,8 @@ function resolveAgentFlags(agentFlags) {
|
|
|
2899
1603
|
}
|
|
2900
1604
|
}
|
|
2901
1605
|
if (invalid.length > 0) {
|
|
2902
|
-
|
|
2903
|
-
|
|
1606
|
+
p2.log.error(`Invalid agents: ${invalid.join(", ")}`);
|
|
1607
|
+
p2.log.info(`Valid agents: ${getAgentTypes().join(", ")}`);
|
|
2904
1608
|
process.exit(1);
|
|
2905
1609
|
}
|
|
2906
1610
|
return resolved;
|
|
@@ -2982,7 +1686,7 @@ async function main(target, options) {
|
|
|
2982
1686
|
process.exit(0);
|
|
2983
1687
|
}
|
|
2984
1688
|
console.log();
|
|
2985
|
-
const spinner2 =
|
|
1689
|
+
const spinner2 = p2.spinner();
|
|
2986
1690
|
spinner2.start("Parsing source...");
|
|
2987
1691
|
const parsed = parseSource(target);
|
|
2988
1692
|
const isRemote = isRemoteSource(parsed);
|
|
@@ -2992,40 +1696,40 @@ async function main(target, options) {
|
|
|
2992
1696
|
const headerResult = parseHeaders(headerValues);
|
|
2993
1697
|
if (headerResult.invalid.length > 0) {
|
|
2994
1698
|
const hint = looksLikeEatenShellVar(headerResult.invalid, ":") ? " (looks like your shell expanded a ${VAR} to an empty string; use single quotes: --header 'Key: ${VAR}' to pass the template literally)" : "";
|
|
2995
|
-
|
|
2996
|
-
`Invalid --header value(s): ${headerResult.invalid.join(", ")}. Use
|
|
1699
|
+
p2.log.error(
|
|
1700
|
+
`Invalid --header value(s): ${headerResult.invalid.join(", ")}. Use 'Key: Value' format.${hint}`
|
|
2997
1701
|
);
|
|
2998
1702
|
process.exit(1);
|
|
2999
1703
|
}
|
|
3000
1704
|
const headerKeys = Object.keys(headerResult.headers);
|
|
3001
1705
|
const hasHeaderValues = headerKeys.length > 0;
|
|
3002
1706
|
if (hasHeaderValues && !isRemote) {
|
|
3003
|
-
|
|
1707
|
+
p2.log.warn("--header is only used for remote URLs, ignoring");
|
|
3004
1708
|
}
|
|
3005
1709
|
const envValues = options.env ?? [];
|
|
3006
1710
|
const envResult = parseEnv(envValues);
|
|
3007
1711
|
if (envResult.invalid.length > 0) {
|
|
3008
1712
|
const hint = looksLikeEatenShellVar(envResult.invalid, "=") ? " (looks like your shell expanded a ${VAR} to an empty string; use single quotes: --env 'KEY=${VAR}' to pass the template literally)" : "";
|
|
3009
|
-
|
|
3010
|
-
`Invalid --env value(s): ${envResult.invalid.join(", ")}. Use
|
|
1713
|
+
p2.log.error(
|
|
1714
|
+
`Invalid --env value(s): ${envResult.invalid.join(", ")}. Use 'KEY=VALUE' format.${hint}`
|
|
3011
1715
|
);
|
|
3012
1716
|
process.exit(1);
|
|
3013
1717
|
}
|
|
3014
1718
|
const envKeys = Object.keys(envResult.env);
|
|
3015
1719
|
const hasEnvValues = envKeys.length > 0;
|
|
3016
1720
|
if (hasEnvValues && isRemote) {
|
|
3017
|
-
|
|
1721
|
+
p2.log.warn(
|
|
3018
1722
|
"--env is only used for local/package/command installs, ignoring"
|
|
3019
1723
|
);
|
|
3020
1724
|
}
|
|
3021
1725
|
const argsValues = options.args ?? [];
|
|
3022
1726
|
const hasArgsValues = argsValues.length > 0;
|
|
3023
1727
|
if (hasArgsValues && isRemote) {
|
|
3024
|
-
|
|
1728
|
+
p2.log.warn(
|
|
3025
1729
|
"--args is only used for local/package/command installs, ignoring"
|
|
3026
1730
|
);
|
|
3027
1731
|
}
|
|
3028
|
-
const promptTemplateVar = (varName) =>
|
|
1732
|
+
const promptTemplateVar = (varName) => p2.text({
|
|
3029
1733
|
message: `Enter value for ${varName}`,
|
|
3030
1734
|
placeholder: `<${varName}>`
|
|
3031
1735
|
});
|
|
@@ -3036,7 +1740,7 @@ async function main(target, options) {
|
|
|
3036
1740
|
promptTemplateVar
|
|
3037
1741
|
);
|
|
3038
1742
|
if (result.cancelled) {
|
|
3039
|
-
|
|
1743
|
+
p2.cancel("Cancelled");
|
|
3040
1744
|
process.exit(0);
|
|
3041
1745
|
}
|
|
3042
1746
|
for (const [key, value] of Object.entries(result.resolved)) {
|
|
@@ -3049,7 +1753,7 @@ async function main(target, options) {
|
|
|
3049
1753
|
promptTemplateVar
|
|
3050
1754
|
);
|
|
3051
1755
|
if (result.cancelled) {
|
|
3052
|
-
|
|
1756
|
+
p2.cancel("Cancelled");
|
|
3053
1757
|
process.exit(0);
|
|
3054
1758
|
}
|
|
3055
1759
|
for (const [key, value] of Object.entries(result.resolved)) {
|
|
@@ -3059,7 +1763,7 @@ async function main(target, options) {
|
|
|
3059
1763
|
if (!options.yes && hasArgsValues && hasTemplateVars(resolvedArgs)) {
|
|
3060
1764
|
const result = await resolveArrayTemplates(resolvedArgs, promptTemplateVar);
|
|
3061
1765
|
if (result.cancelled) {
|
|
3062
|
-
|
|
1766
|
+
p2.cancel("Cancelled");
|
|
3063
1767
|
process.exit(0);
|
|
3064
1768
|
}
|
|
3065
1769
|
resolvedArgs = result.resolved;
|
|
@@ -3068,20 +1772,20 @@ async function main(target, options) {
|
|
|
3068
1772
|
const envForConfig = !isRemote && hasEnvValues ? omitEmptyStringValues(envResult.env) : void 0;
|
|
3069
1773
|
const argsForConfig = !isRemote && hasArgsValues ? resolvedArgs.filter((a) => a.trim().length > 0) : void 0;
|
|
3070
1774
|
const serverName = options.name || parsed.inferredName;
|
|
3071
|
-
|
|
1775
|
+
p2.log.info(`Server name: ${chalk.cyan(serverName)}`);
|
|
3072
1776
|
const transportValue = options.transport || options.type;
|
|
3073
1777
|
let resolvedTransport;
|
|
3074
1778
|
if (transportValue) {
|
|
3075
1779
|
const validTransports = ["http", "sse"];
|
|
3076
1780
|
if (!validTransports.includes(transportValue)) {
|
|
3077
|
-
|
|
1781
|
+
p2.log.error(
|
|
3078
1782
|
`Invalid transport: ${transportValue}. Valid options: ${validTransports.join(", ")}`
|
|
3079
1783
|
);
|
|
3080
1784
|
process.exit(1);
|
|
3081
1785
|
}
|
|
3082
1786
|
resolvedTransport = transportValue;
|
|
3083
1787
|
if (!isRemoteSource(parsed)) {
|
|
3084
|
-
|
|
1788
|
+
p2.log.warn("--transport is only used for remote URLs, ignoring");
|
|
3085
1789
|
}
|
|
3086
1790
|
}
|
|
3087
1791
|
const serverConfig = buildServerConfig(parsed, {
|
|
@@ -3107,19 +1811,19 @@ async function main(target, options) {
|
|
|
3107
1811
|
}
|
|
3108
1812
|
}
|
|
3109
1813
|
if (invalid.length > 0) {
|
|
3110
|
-
|
|
3111
|
-
|
|
1814
|
+
p2.log.error(`Invalid agents: ${invalid.join(", ")}`);
|
|
1815
|
+
p2.log.info(`Valid agents: ${allAgentTypes.join(", ")}`);
|
|
3112
1816
|
process.exit(1);
|
|
3113
1817
|
}
|
|
3114
1818
|
targetAgents = resolved;
|
|
3115
1819
|
} else if (options.all) {
|
|
3116
1820
|
targetAgents = allAgentTypes;
|
|
3117
|
-
|
|
1821
|
+
p2.log.info(`Installing to all ${targetAgents.length} agents`);
|
|
3118
1822
|
} else {
|
|
3119
1823
|
spinner2.start("Detecting agents...");
|
|
3120
1824
|
let detectedAgents;
|
|
3121
1825
|
if (options.global) {
|
|
3122
|
-
detectedAgents = await
|
|
1826
|
+
detectedAgents = await detectGlobalAgents();
|
|
3123
1827
|
for (const agent of detectedAgents) {
|
|
3124
1828
|
agentRouting.set(agent, "global");
|
|
3125
1829
|
}
|
|
@@ -3140,7 +1844,7 @@ async function main(target, options) {
|
|
|
3140
1844
|
for (const agent of targetAgents) {
|
|
3141
1845
|
agentRouting.set(agent, "global");
|
|
3142
1846
|
}
|
|
3143
|
-
|
|
1847
|
+
p2.log.info(
|
|
3144
1848
|
`Installing to ${targetAgents.length} agents globally (none detected)`
|
|
3145
1849
|
);
|
|
3146
1850
|
} else {
|
|
@@ -3148,20 +1852,20 @@ async function main(target, options) {
|
|
|
3148
1852
|
for (const agent of targetAgents) {
|
|
3149
1853
|
agentRouting.set(agent, "local");
|
|
3150
1854
|
}
|
|
3151
|
-
|
|
1855
|
+
p2.log.info(
|
|
3152
1856
|
`Installing to ${targetAgents.length} project-capable agents (none detected)`
|
|
3153
1857
|
);
|
|
3154
1858
|
}
|
|
3155
1859
|
} else {
|
|
3156
1860
|
const availableAgents = options.global ? allAgentTypes : getProjectCapableAgents();
|
|
3157
|
-
|
|
1861
|
+
p2.log.warn(
|
|
3158
1862
|
options.global ? "No coding agents detected." : "No agents detected in this project."
|
|
3159
1863
|
);
|
|
3160
1864
|
const selected = await selectAgentsInteractive(availableAgents, {
|
|
3161
1865
|
global: options.global
|
|
3162
1866
|
});
|
|
3163
|
-
if (
|
|
3164
|
-
|
|
1867
|
+
if (p2.isCancel(selected)) {
|
|
1868
|
+
p2.cancel("Installation cancelled");
|
|
3165
1869
|
process.exit(0);
|
|
3166
1870
|
}
|
|
3167
1871
|
selectedViaPrompt = true;
|
|
@@ -3173,7 +1877,7 @@ async function main(target, options) {
|
|
|
3173
1877
|
} else if (options.yes) {
|
|
3174
1878
|
targetAgents = detectedAgents;
|
|
3175
1879
|
const agentNames = detectedAgents.map((a) => chalk.cyan(agents[a].displayName)).join(", ");
|
|
3176
|
-
|
|
1880
|
+
p2.log.info(`Installing to: ${agentNames}`);
|
|
3177
1881
|
} else {
|
|
3178
1882
|
const availableAgents = options.global ? allAgentTypes : getProjectCapableAgents();
|
|
3179
1883
|
let lastSelected;
|
|
@@ -3187,14 +1891,14 @@ async function main(target, options) {
|
|
|
3187
1891
|
agentRouting,
|
|
3188
1892
|
lastSelected
|
|
3189
1893
|
});
|
|
3190
|
-
const selected = await
|
|
1894
|
+
const selected = await p2.multiselect({
|
|
3191
1895
|
message: "Select agents to install to",
|
|
3192
1896
|
options: agentChoices,
|
|
3193
1897
|
required: true,
|
|
3194
1898
|
initialValues
|
|
3195
1899
|
});
|
|
3196
|
-
if (
|
|
3197
|
-
|
|
1900
|
+
if (p2.isCancel(selected)) {
|
|
1901
|
+
p2.cancel("Installation cancelled");
|
|
3198
1902
|
process.exit(0);
|
|
3199
1903
|
}
|
|
3200
1904
|
selectedViaPrompt = true;
|
|
@@ -3212,25 +1916,25 @@ async function main(target, options) {
|
|
|
3212
1916
|
const unsupportedNames = unsupportedAgents.map((a) => agents[a].displayName).join(", ");
|
|
3213
1917
|
const hints = unsupportedAgents.map((a) => agents[a].unsupportedTransportMessage).filter(Boolean);
|
|
3214
1918
|
if (options.all) {
|
|
3215
|
-
|
|
1919
|
+
p2.log.warn(
|
|
3216
1920
|
`Skipping agents that don't support ${requiredTransport} transport: ${unsupportedNames}`
|
|
3217
1921
|
);
|
|
3218
1922
|
for (const hint of hints) {
|
|
3219
|
-
|
|
1923
|
+
p2.log.info(hint);
|
|
3220
1924
|
}
|
|
3221
1925
|
targetAgents = targetAgents.filter(
|
|
3222
1926
|
(a) => isTransportSupported(a, requiredTransport)
|
|
3223
1927
|
);
|
|
3224
1928
|
if (targetAgents.length === 0) {
|
|
3225
|
-
|
|
1929
|
+
p2.log.error("No agents support this transport type");
|
|
3226
1930
|
process.exit(1);
|
|
3227
1931
|
}
|
|
3228
1932
|
} else {
|
|
3229
|
-
|
|
1933
|
+
p2.log.error(
|
|
3230
1934
|
`The following agents don't support ${requiredTransport} transport: ${unsupportedNames}`
|
|
3231
1935
|
);
|
|
3232
1936
|
for (const hint of hints) {
|
|
3233
|
-
|
|
1937
|
+
p2.log.info(hint);
|
|
3234
1938
|
}
|
|
3235
1939
|
process.exit(1);
|
|
3236
1940
|
}
|
|
@@ -3253,7 +1957,7 @@ async function main(target, options) {
|
|
|
3253
1957
|
if (selectedWithLocal.length > 0) {
|
|
3254
1958
|
let installLocally = true;
|
|
3255
1959
|
if (!options.yes) {
|
|
3256
|
-
const scope = await
|
|
1960
|
+
const scope = await p2.select({
|
|
3257
1961
|
message: "Installation scope",
|
|
3258
1962
|
options: [
|
|
3259
1963
|
{
|
|
@@ -3268,8 +1972,8 @@ async function main(target, options) {
|
|
|
3268
1972
|
}
|
|
3269
1973
|
]
|
|
3270
1974
|
});
|
|
3271
|
-
if (
|
|
3272
|
-
|
|
1975
|
+
if (p2.isCancel(scope)) {
|
|
1976
|
+
p2.cancel("Installation cancelled");
|
|
3273
1977
|
process.exit(0);
|
|
3274
1978
|
}
|
|
3275
1979
|
installLocally = scope;
|
|
@@ -3278,7 +1982,7 @@ async function main(target, options) {
|
|
|
3278
1982
|
agentRouting.set(agent, installLocally ? "local" : "global");
|
|
3279
1983
|
}
|
|
3280
1984
|
} else {
|
|
3281
|
-
|
|
1985
|
+
p2.log.info("Selected agents only support global installation");
|
|
3282
1986
|
}
|
|
3283
1987
|
}
|
|
3284
1988
|
const summaryLines = [];
|
|
@@ -3310,13 +2014,13 @@ async function main(target, options) {
|
|
|
3310
2014
|
);
|
|
3311
2015
|
}
|
|
3312
2016
|
console.log();
|
|
3313
|
-
|
|
2017
|
+
p2.note(summaryLines.join("\n"), "Installation Summary");
|
|
3314
2018
|
if (!options.yes) {
|
|
3315
|
-
const confirmed = await
|
|
2019
|
+
const confirmed = await p2.confirm({
|
|
3316
2020
|
message: "Proceed with installation?"
|
|
3317
2021
|
});
|
|
3318
|
-
if (
|
|
3319
|
-
|
|
2022
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
2023
|
+
p2.cancel("Installation cancelled");
|
|
3320
2024
|
process.exit(0);
|
|
3321
2025
|
}
|
|
3322
2026
|
}
|
|
@@ -3332,12 +2036,12 @@ async function main(target, options) {
|
|
|
3332
2036
|
const resultLines = [];
|
|
3333
2037
|
for (const [agentType, result] of successful) {
|
|
3334
2038
|
const agent = agents[agentType];
|
|
3335
|
-
const shortPath =
|
|
2039
|
+
const shortPath = shortenPath(result.path);
|
|
3336
2040
|
resultLines.push(
|
|
3337
2041
|
`${chalk.green("\u2713")} ${agent.displayName}: ${chalk.dim(shortPath)}`
|
|
3338
2042
|
);
|
|
3339
2043
|
}
|
|
3340
|
-
|
|
2044
|
+
p2.note(
|
|
3341
2045
|
resultLines.join("\n"),
|
|
3342
2046
|
chalk.green(
|
|
3343
2047
|
`Installed to ${successful.length} agent${successful.length !== 1 ? "s" : ""}`
|
|
@@ -3346,33 +2050,33 @@ async function main(target, options) {
|
|
|
3346
2050
|
}
|
|
3347
2051
|
if (failed.length > 0) {
|
|
3348
2052
|
console.log();
|
|
3349
|
-
|
|
2053
|
+
p2.log.error(
|
|
3350
2054
|
chalk.red(
|
|
3351
2055
|
`Failed to install to ${failed.length} agent${failed.length !== 1 ? "s" : ""}`
|
|
3352
2056
|
)
|
|
3353
2057
|
);
|
|
3354
2058
|
for (const [agentType, result] of failed) {
|
|
3355
2059
|
const agent = agents[agentType];
|
|
3356
|
-
|
|
2060
|
+
p2.log.message(
|
|
3357
2061
|
` ${chalk.red("\u2717")} ${agent.displayName}: ${chalk.dim(result.error)}`
|
|
3358
2062
|
);
|
|
3359
2063
|
}
|
|
3360
2064
|
}
|
|
3361
2065
|
if (options.gitignore && options.global) {
|
|
3362
|
-
|
|
2066
|
+
p2.log.warn(
|
|
3363
2067
|
"--gitignore is only supported for project-scoped installations; ignoring."
|
|
3364
2068
|
);
|
|
3365
2069
|
} else if (options.gitignore) {
|
|
3366
2070
|
const successfulPaths = successful.map(([_, result]) => result.path);
|
|
3367
2071
|
const gitignoreUpdate = updateGitignoreWithPaths(successfulPaths);
|
|
3368
2072
|
if (gitignoreUpdate.added.length > 0) {
|
|
3369
|
-
|
|
2073
|
+
p2.log.info(
|
|
3370
2074
|
`Added ${gitignoreUpdate.added.length} entr${gitignoreUpdate.added.length === 1 ? "y" : "ies"} to .gitignore`
|
|
3371
2075
|
);
|
|
3372
2076
|
} else {
|
|
3373
|
-
|
|
2077
|
+
p2.log.info("No new local config paths to add to .gitignore");
|
|
3374
2078
|
}
|
|
3375
2079
|
}
|
|
3376
2080
|
console.log();
|
|
3377
|
-
|
|
2081
|
+
p2.outro(chalk.green("Done!"));
|
|
3378
2082
|
}
|