add-mcp 1.9.0 → 1.10.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 +41 -0
- package/dist/chunk-56AZIA6W.js +1221 -0
- package/dist/index.js +160 -1318
- package/dist/lib.d.ts +102 -0
- package/dist/lib.js +79 -0
- package/package.json +13 -4
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { readFile, writeFile, mkdir, rm } from "fs/promises";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var CONFIG_DIR = "add-mcp";
|
|
6
|
+
var CONFIG_FILE = "config.json";
|
|
7
|
+
var CURRENT_VERSION = 1;
|
|
8
|
+
var LEGACY_AGENTS_DIR = ".agents";
|
|
9
|
+
var LEGACY_LOCK_FILE = ".mcp-lock.json";
|
|
10
|
+
function getXdgConfigHome() {
|
|
11
|
+
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
12
|
+
}
|
|
13
|
+
function getConfigPath() {
|
|
14
|
+
return join(getXdgConfigHome(), CONFIG_DIR, CONFIG_FILE);
|
|
15
|
+
}
|
|
16
|
+
function getLegacyConfigPath() {
|
|
17
|
+
return join(homedir(), LEGACY_AGENTS_DIR, LEGACY_LOCK_FILE);
|
|
18
|
+
}
|
|
19
|
+
async function readConfig() {
|
|
20
|
+
const configPath = getConfigPath();
|
|
21
|
+
try {
|
|
22
|
+
const content = await readFile(configPath, "utf-8");
|
|
23
|
+
const parsed = JSON.parse(content);
|
|
24
|
+
if (typeof parsed.version !== "number") {
|
|
25
|
+
return createEmptyConfig();
|
|
26
|
+
}
|
|
27
|
+
if (parsed.version < CURRENT_VERSION) {
|
|
28
|
+
return createEmptyConfig();
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
const legacyPath = getLegacyConfigPath();
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(legacyPath, "utf-8");
|
|
36
|
+
const parsed = JSON.parse(content);
|
|
37
|
+
if (typeof parsed.version !== "number" || parsed.version < CURRENT_VERSION) {
|
|
38
|
+
return createEmptyConfig();
|
|
39
|
+
}
|
|
40
|
+
await writeConfig(parsed);
|
|
41
|
+
await cleanupLegacyConfig();
|
|
42
|
+
return parsed;
|
|
43
|
+
} catch {
|
|
44
|
+
return createEmptyConfig();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function writeConfig(config) {
|
|
48
|
+
const configPath = getConfigPath();
|
|
49
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
50
|
+
const content = JSON.stringify(config, null, 2);
|
|
51
|
+
await writeFile(configPath, content, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
async function cleanupLegacyConfig() {
|
|
54
|
+
const legacyPath = getLegacyConfigPath();
|
|
55
|
+
try {
|
|
56
|
+
await rm(legacyPath, { force: true });
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function getLastSelectedAgents() {
|
|
61
|
+
const config = await readConfig();
|
|
62
|
+
return config.lastSelectedAgents;
|
|
63
|
+
}
|
|
64
|
+
async function saveSelectedAgents(agents2) {
|
|
65
|
+
const config = await readConfig();
|
|
66
|
+
config.lastSelectedAgents = agents2;
|
|
67
|
+
await writeConfig(config);
|
|
68
|
+
}
|
|
69
|
+
async function getFindRegistries() {
|
|
70
|
+
const config = await readConfig();
|
|
71
|
+
if (!config.findRegistries) return [];
|
|
72
|
+
return config.findRegistries.map(normalizeFindRegistryEntry);
|
|
73
|
+
}
|
|
74
|
+
function normalizeFindRegistryEntry(raw) {
|
|
75
|
+
const legacy = raw;
|
|
76
|
+
const url = legacy.url ?? legacy.serversUrl;
|
|
77
|
+
if (!url || typeof url !== "string") {
|
|
78
|
+
throw new Error("Registry entry missing url");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
url,
|
|
82
|
+
...legacy.label ? { label: legacy.label } : {}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function saveFindRegistries(registries) {
|
|
86
|
+
const config = await readConfig();
|
|
87
|
+
config.findRegistries = registries;
|
|
88
|
+
await writeConfig(config);
|
|
89
|
+
}
|
|
90
|
+
function createEmptyConfig() {
|
|
91
|
+
return {
|
|
92
|
+
version: CURRENT_VERSION
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/agents.ts
|
|
97
|
+
import * as p from "@clack/prompts";
|
|
98
|
+
import { homedir as homedir2 } from "os";
|
|
99
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
100
|
+
import { existsSync } from "fs";
|
|
101
|
+
var home = homedir2();
|
|
102
|
+
function shortenPath(fullPath) {
|
|
103
|
+
if (fullPath.startsWith(home)) {
|
|
104
|
+
return fullPath.replace(home, "~");
|
|
105
|
+
}
|
|
106
|
+
return fullPath;
|
|
107
|
+
}
|
|
108
|
+
function getPlatformPaths() {
|
|
109
|
+
const platform = process.platform;
|
|
110
|
+
if (platform === "win32") {
|
|
111
|
+
const appData = process.env.APPDATA || join2(home, "AppData", "Roaming");
|
|
112
|
+
return {
|
|
113
|
+
appSupport: appData,
|
|
114
|
+
vscodePath: join2(appData, "Code", "User"),
|
|
115
|
+
gooseConfigPath: join2(appData, "Block", "goose", "config", "config.yaml")
|
|
116
|
+
};
|
|
117
|
+
} else if (platform === "darwin") {
|
|
118
|
+
return {
|
|
119
|
+
appSupport: join2(home, "Library", "Application Support"),
|
|
120
|
+
vscodePath: join2(home, "Library", "Application Support", "Code", "User"),
|
|
121
|
+
gooseConfigPath: join2(home, ".config", "goose", "config.yaml")
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
const configDir = process.env.XDG_CONFIG_HOME || join2(home, ".config");
|
|
125
|
+
return {
|
|
126
|
+
appSupport: configDir,
|
|
127
|
+
vscodePath: join2(configDir, "Code", "User"),
|
|
128
|
+
gooseConfigPath: join2(configDir, "goose", "config.yaml")
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
var { appSupport, vscodePath, gooseConfigPath } = getPlatformPaths();
|
|
133
|
+
var antigravityConfigPath = join2(
|
|
134
|
+
home,
|
|
135
|
+
".gemini",
|
|
136
|
+
"antigravity",
|
|
137
|
+
"mcp_config.json"
|
|
138
|
+
);
|
|
139
|
+
var clineCliConfigPath = join2(
|
|
140
|
+
process.env.CLINE_DIR || join2(home, ".cline"),
|
|
141
|
+
"data",
|
|
142
|
+
"settings",
|
|
143
|
+
"cline_mcp_settings.json"
|
|
144
|
+
);
|
|
145
|
+
var clineExtensionConfigPath = join2(
|
|
146
|
+
vscodePath,
|
|
147
|
+
"globalStorage",
|
|
148
|
+
"saoudrizwan.claude-dev",
|
|
149
|
+
"settings",
|
|
150
|
+
"cline_mcp_settings.json"
|
|
151
|
+
);
|
|
152
|
+
var copilotConfigPath = join2(
|
|
153
|
+
process.env.XDG_CONFIG_HOME || join2(home, ".copilot"),
|
|
154
|
+
"mcp-config.json"
|
|
155
|
+
);
|
|
156
|
+
function transformGooseConfig(serverName, config) {
|
|
157
|
+
if (config.url) {
|
|
158
|
+
const gooseType = config.type === "sse" ? "sse" : "streamable_http";
|
|
159
|
+
return {
|
|
160
|
+
name: serverName,
|
|
161
|
+
description: "",
|
|
162
|
+
type: gooseType,
|
|
163
|
+
uri: config.url,
|
|
164
|
+
headers: config.headers || {},
|
|
165
|
+
enabled: true,
|
|
166
|
+
timeout: 300
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
name: serverName,
|
|
171
|
+
description: "",
|
|
172
|
+
cmd: config.command,
|
|
173
|
+
args: config.args || [],
|
|
174
|
+
enabled: true,
|
|
175
|
+
envs: config.env || {},
|
|
176
|
+
type: "stdio",
|
|
177
|
+
timeout: 300
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function transformZedConfig(_serverName, config) {
|
|
181
|
+
if (config.url) {
|
|
182
|
+
return {
|
|
183
|
+
source: "custom",
|
|
184
|
+
type: config.type || "http",
|
|
185
|
+
url: config.url,
|
|
186
|
+
headers: config.headers || {}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
source: "custom",
|
|
191
|
+
command: config.command,
|
|
192
|
+
args: config.args || [],
|
|
193
|
+
env: config.env || {}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function transformOpenCodeConfig(_serverName, config) {
|
|
197
|
+
if (config.url) {
|
|
198
|
+
return {
|
|
199
|
+
type: "remote",
|
|
200
|
+
url: config.url,
|
|
201
|
+
enabled: true,
|
|
202
|
+
headers: config.headers
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
type: "local",
|
|
207
|
+
command: [config.command, ...config.args || []],
|
|
208
|
+
enabled: true,
|
|
209
|
+
environment: config.env || {}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function transformCodexConfig(_serverName, config) {
|
|
213
|
+
if (config.url) {
|
|
214
|
+
const remoteConfig = {
|
|
215
|
+
type: config.type || "http",
|
|
216
|
+
url: config.url
|
|
217
|
+
};
|
|
218
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
219
|
+
remoteConfig.http_headers = config.headers;
|
|
220
|
+
}
|
|
221
|
+
return remoteConfig;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
command: config.command,
|
|
225
|
+
args: config.args || [],
|
|
226
|
+
env: config.env
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function transformCursorConfig(_serverName, config) {
|
|
230
|
+
if (config.url) {
|
|
231
|
+
const remoteConfig = {
|
|
232
|
+
url: config.url
|
|
233
|
+
};
|
|
234
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
235
|
+
remoteConfig.headers = config.headers;
|
|
236
|
+
}
|
|
237
|
+
return remoteConfig;
|
|
238
|
+
}
|
|
239
|
+
return config;
|
|
240
|
+
}
|
|
241
|
+
function transformClineConfig(_serverName, config) {
|
|
242
|
+
if (config.url) {
|
|
243
|
+
const remoteConfig = {
|
|
244
|
+
url: config.url,
|
|
245
|
+
type: config.type === "sse" ? "sse" : "streamableHttp",
|
|
246
|
+
disabled: false
|
|
247
|
+
};
|
|
248
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
249
|
+
remoteConfig.headers = config.headers;
|
|
250
|
+
}
|
|
251
|
+
return remoteConfig;
|
|
252
|
+
}
|
|
253
|
+
const localConfig = {
|
|
254
|
+
command: config.command,
|
|
255
|
+
args: config.args || [],
|
|
256
|
+
disabled: false
|
|
257
|
+
};
|
|
258
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
259
|
+
localConfig.env = config.env;
|
|
260
|
+
}
|
|
261
|
+
return localConfig;
|
|
262
|
+
}
|
|
263
|
+
function transformAntigravityConfig(_serverName, config) {
|
|
264
|
+
if (config.url) {
|
|
265
|
+
const remoteConfig = {
|
|
266
|
+
serverUrl: config.url
|
|
267
|
+
};
|
|
268
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
269
|
+
remoteConfig.headers = config.headers;
|
|
270
|
+
}
|
|
271
|
+
return remoteConfig;
|
|
272
|
+
}
|
|
273
|
+
const localConfig = {
|
|
274
|
+
command: config.command,
|
|
275
|
+
args: config.args || []
|
|
276
|
+
};
|
|
277
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
278
|
+
localConfig.env = config.env;
|
|
279
|
+
}
|
|
280
|
+
return localConfig;
|
|
281
|
+
}
|
|
282
|
+
function transformGitHubCopilotCliConfig(_serverName, config, context) {
|
|
283
|
+
if (context?.local) {
|
|
284
|
+
return config;
|
|
285
|
+
}
|
|
286
|
+
if (config.url) {
|
|
287
|
+
const remoteConfig = {
|
|
288
|
+
type: config.type || "http",
|
|
289
|
+
url: config.url,
|
|
290
|
+
tools: ["*"]
|
|
291
|
+
};
|
|
292
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
293
|
+
remoteConfig.headers = config.headers;
|
|
294
|
+
}
|
|
295
|
+
return remoteConfig;
|
|
296
|
+
}
|
|
297
|
+
const localConfig = {
|
|
298
|
+
type: "stdio",
|
|
299
|
+
command: config.command,
|
|
300
|
+
args: config.args || [],
|
|
301
|
+
tools: ["*"]
|
|
302
|
+
};
|
|
303
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
304
|
+
localConfig.env = config.env;
|
|
305
|
+
}
|
|
306
|
+
return localConfig;
|
|
307
|
+
}
|
|
308
|
+
function resolveMcporterConfigPath(agent, options) {
|
|
309
|
+
if (options.local && agent.localConfigPath) {
|
|
310
|
+
return join2(options.cwd, agent.localConfigPath);
|
|
311
|
+
}
|
|
312
|
+
const globalJsonPath = agent.configPath;
|
|
313
|
+
const globalJsoncPath = join2(dirname2(globalJsonPath), "mcporter.jsonc");
|
|
314
|
+
if (existsSync(globalJsonPath)) {
|
|
315
|
+
return globalJsonPath;
|
|
316
|
+
}
|
|
317
|
+
if (existsSync(globalJsoncPath)) {
|
|
318
|
+
return globalJsoncPath;
|
|
319
|
+
}
|
|
320
|
+
return globalJsonPath;
|
|
321
|
+
}
|
|
322
|
+
var agents = {
|
|
323
|
+
antigravity: {
|
|
324
|
+
name: "antigravity",
|
|
325
|
+
displayName: "Antigravity",
|
|
326
|
+
configPath: antigravityConfigPath,
|
|
327
|
+
projectDetectPaths: [],
|
|
328
|
+
// Global only - no project support
|
|
329
|
+
configKey: "mcpServers",
|
|
330
|
+
format: "json",
|
|
331
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
332
|
+
detectGlobalInstall: async () => {
|
|
333
|
+
return existsSync(join2(home, ".gemini"));
|
|
334
|
+
},
|
|
335
|
+
transformConfig: transformAntigravityConfig
|
|
336
|
+
},
|
|
337
|
+
cline: {
|
|
338
|
+
name: "cline",
|
|
339
|
+
displayName: "Cline VSCode Extension",
|
|
340
|
+
configPath: clineExtensionConfigPath,
|
|
341
|
+
projectDetectPaths: [],
|
|
342
|
+
// Global only - no project support
|
|
343
|
+
configKey: "mcpServers",
|
|
344
|
+
format: "json",
|
|
345
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
346
|
+
detectGlobalInstall: async () => {
|
|
347
|
+
return existsSync(dirname2(clineExtensionConfigPath));
|
|
348
|
+
},
|
|
349
|
+
transformConfig: transformClineConfig
|
|
350
|
+
},
|
|
351
|
+
"cline-cli": {
|
|
352
|
+
name: "cline-cli",
|
|
353
|
+
displayName: "Cline CLI",
|
|
354
|
+
configPath: clineCliConfigPath,
|
|
355
|
+
projectDetectPaths: [],
|
|
356
|
+
// Global only - no project support
|
|
357
|
+
configKey: "mcpServers",
|
|
358
|
+
format: "json",
|
|
359
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
360
|
+
detectGlobalInstall: async () => {
|
|
361
|
+
return existsSync(dirname2(clineCliConfigPath));
|
|
362
|
+
},
|
|
363
|
+
transformConfig: transformClineConfig
|
|
364
|
+
},
|
|
365
|
+
"claude-code": {
|
|
366
|
+
name: "claude-code",
|
|
367
|
+
displayName: "Claude Code",
|
|
368
|
+
configPath: join2(home, ".claude.json"),
|
|
369
|
+
localConfigPath: ".mcp.json",
|
|
370
|
+
projectDetectPaths: [".mcp.json", ".claude"],
|
|
371
|
+
configKey: "mcpServers",
|
|
372
|
+
format: "json",
|
|
373
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
374
|
+
detectGlobalInstall: async () => {
|
|
375
|
+
return existsSync(join2(home, ".claude"));
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
"claude-desktop": {
|
|
379
|
+
name: "claude-desktop",
|
|
380
|
+
displayName: "Claude Desktop",
|
|
381
|
+
configPath: join2(appSupport, "Claude", "claude_desktop_config.json"),
|
|
382
|
+
projectDetectPaths: [],
|
|
383
|
+
// Global only - no project support
|
|
384
|
+
configKey: "mcpServers",
|
|
385
|
+
format: "json",
|
|
386
|
+
supportedTransports: ["stdio"],
|
|
387
|
+
unsupportedTransportMessage: "Claude Desktop only supports local (stdio) servers via its config file. Add remote servers through Settings \u2192 Connectors in the app instead.",
|
|
388
|
+
detectGlobalInstall: async () => {
|
|
389
|
+
return existsSync(join2(appSupport, "Claude"));
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
codex: {
|
|
393
|
+
name: "codex",
|
|
394
|
+
displayName: "Codex",
|
|
395
|
+
configPath: join2(
|
|
396
|
+
process.env.CODEX_HOME || join2(home, ".codex"),
|
|
397
|
+
"config.toml"
|
|
398
|
+
),
|
|
399
|
+
localConfigPath: ".codex/config.toml",
|
|
400
|
+
projectDetectPaths: [".codex"],
|
|
401
|
+
configKey: "mcp_servers",
|
|
402
|
+
format: "toml",
|
|
403
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
404
|
+
detectGlobalInstall: async () => {
|
|
405
|
+
return existsSync(join2(home, ".codex"));
|
|
406
|
+
},
|
|
407
|
+
transformConfig: transformCodexConfig
|
|
408
|
+
},
|
|
409
|
+
cursor: {
|
|
410
|
+
name: "cursor",
|
|
411
|
+
displayName: "Cursor",
|
|
412
|
+
configPath: join2(home, ".cursor", "mcp.json"),
|
|
413
|
+
localConfigPath: ".cursor/mcp.json",
|
|
414
|
+
projectDetectPaths: [".cursor"],
|
|
415
|
+
configKey: "mcpServers",
|
|
416
|
+
format: "json",
|
|
417
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
418
|
+
detectGlobalInstall: async () => {
|
|
419
|
+
return existsSync(join2(home, ".cursor"));
|
|
420
|
+
},
|
|
421
|
+
transformConfig: transformCursorConfig
|
|
422
|
+
},
|
|
423
|
+
"gemini-cli": {
|
|
424
|
+
name: "gemini-cli",
|
|
425
|
+
displayName: "Gemini CLI",
|
|
426
|
+
configPath: join2(home, ".gemini", "settings.json"),
|
|
427
|
+
localConfigPath: ".gemini/settings.json",
|
|
428
|
+
projectDetectPaths: [".gemini"],
|
|
429
|
+
configKey: "mcpServers",
|
|
430
|
+
format: "json",
|
|
431
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
432
|
+
detectGlobalInstall: async () => {
|
|
433
|
+
return existsSync(join2(home, ".gemini"));
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
goose: {
|
|
437
|
+
name: "goose",
|
|
438
|
+
displayName: "Goose",
|
|
439
|
+
configPath: gooseConfigPath,
|
|
440
|
+
projectDetectPaths: [],
|
|
441
|
+
// Global only - no project support
|
|
442
|
+
configKey: "extensions",
|
|
443
|
+
format: "yaml",
|
|
444
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
445
|
+
detectGlobalInstall: async () => {
|
|
446
|
+
return existsSync(gooseConfigPath);
|
|
447
|
+
},
|
|
448
|
+
transformConfig: transformGooseConfig
|
|
449
|
+
},
|
|
450
|
+
"github-copilot-cli": {
|
|
451
|
+
name: "github-copilot-cli",
|
|
452
|
+
displayName: "GitHub Copilot CLI",
|
|
453
|
+
configPath: copilotConfigPath,
|
|
454
|
+
localConfigPath: ".vscode/mcp.json",
|
|
455
|
+
projectDetectPaths: [".vscode"],
|
|
456
|
+
configKey: "mcpServers",
|
|
457
|
+
localConfigKey: "servers",
|
|
458
|
+
format: "json",
|
|
459
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
460
|
+
detectGlobalInstall: async () => {
|
|
461
|
+
return existsSync(dirname2(copilotConfigPath));
|
|
462
|
+
},
|
|
463
|
+
transformConfig: transformGitHubCopilotCliConfig
|
|
464
|
+
},
|
|
465
|
+
mcporter: {
|
|
466
|
+
name: "mcporter",
|
|
467
|
+
displayName: "MCPorter",
|
|
468
|
+
configPath: join2(home, ".mcporter", "mcporter.json"),
|
|
469
|
+
localConfigPath: "config/mcporter.json",
|
|
470
|
+
projectDetectPaths: ["config/mcporter.json"],
|
|
471
|
+
configKey: "mcpServers",
|
|
472
|
+
format: "json",
|
|
473
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
474
|
+
detectGlobalInstall: async () => {
|
|
475
|
+
return existsSync(join2(home, ".mcporter"));
|
|
476
|
+
},
|
|
477
|
+
resolveConfigPath: resolveMcporterConfigPath
|
|
478
|
+
},
|
|
479
|
+
opencode: {
|
|
480
|
+
name: "opencode",
|
|
481
|
+
displayName: "OpenCode",
|
|
482
|
+
configPath: join2(home, ".config", "opencode", "opencode.json"),
|
|
483
|
+
localConfigPath: "opencode.json",
|
|
484
|
+
projectDetectPaths: ["opencode.json", ".opencode"],
|
|
485
|
+
configKey: "mcp",
|
|
486
|
+
format: "json",
|
|
487
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
488
|
+
detectGlobalInstall: async () => {
|
|
489
|
+
return existsSync(join2(home, ".config", "opencode"));
|
|
490
|
+
},
|
|
491
|
+
transformConfig: transformOpenCodeConfig
|
|
492
|
+
},
|
|
493
|
+
vscode: {
|
|
494
|
+
name: "vscode",
|
|
495
|
+
displayName: "VS Code",
|
|
496
|
+
configPath: join2(vscodePath, "mcp.json"),
|
|
497
|
+
localConfigPath: ".vscode/mcp.json",
|
|
498
|
+
projectDetectPaths: [".vscode"],
|
|
499
|
+
configKey: "servers",
|
|
500
|
+
format: "json",
|
|
501
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
502
|
+
detectGlobalInstall: async () => {
|
|
503
|
+
return existsSync(vscodePath);
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
zed: {
|
|
507
|
+
name: "zed",
|
|
508
|
+
displayName: "Zed",
|
|
509
|
+
configPath: process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed", "settings.json") : join2(appSupport, "zed", "settings.json"),
|
|
510
|
+
localConfigPath: ".zed/settings.json",
|
|
511
|
+
projectDetectPaths: [".zed"],
|
|
512
|
+
configKey: "context_servers",
|
|
513
|
+
format: "json",
|
|
514
|
+
supportedTransports: ["stdio", "http", "sse"],
|
|
515
|
+
detectGlobalInstall: async () => {
|
|
516
|
+
const configDir = process.platform === "darwin" || process.platform === "win32" ? join2(appSupport, "Zed") : join2(appSupport, "zed");
|
|
517
|
+
return existsSync(configDir);
|
|
518
|
+
},
|
|
519
|
+
transformConfig: transformZedConfig
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
function getAgentTypes() {
|
|
523
|
+
return Object.keys(agents);
|
|
524
|
+
}
|
|
525
|
+
function supportsProjectConfig(agentType) {
|
|
526
|
+
return agents[agentType].localConfigPath !== void 0;
|
|
527
|
+
}
|
|
528
|
+
function getProjectCapableAgents() {
|
|
529
|
+
return Object.keys(agents).filter(
|
|
530
|
+
(type) => supportsProjectConfig(type)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
function detectProjectAgents(cwd) {
|
|
534
|
+
const dir = cwd || process.cwd();
|
|
535
|
+
const detected = [];
|
|
536
|
+
for (const [type, config] of Object.entries(agents)) {
|
|
537
|
+
if (!config.localConfigPath) continue;
|
|
538
|
+
for (const detectPath of config.projectDetectPaths) {
|
|
539
|
+
if (existsSync(join2(dir, detectPath))) {
|
|
540
|
+
detected.push(type);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return detected;
|
|
546
|
+
}
|
|
547
|
+
async function detectGlobalAgents() {
|
|
548
|
+
const detected = [];
|
|
549
|
+
for (const [type, config] of Object.entries(agents)) {
|
|
550
|
+
if (await config.detectGlobalInstall()) {
|
|
551
|
+
detected.push(type);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return detected;
|
|
555
|
+
}
|
|
556
|
+
function isTransportSupported(agentType, transport) {
|
|
557
|
+
return agents[agentType].supportedTransports.includes(transport);
|
|
558
|
+
}
|
|
559
|
+
function buildAgentSelectionChoices(options) {
|
|
560
|
+
const { availableAgents, detectedAgents, agentRouting, lastSelected } = options;
|
|
561
|
+
const detectedSet = new Set(detectedAgents);
|
|
562
|
+
const validLastSelected = lastSelected?.filter(
|
|
563
|
+
(agent) => availableAgents.includes(agent) && !detectedSet.has(agent)
|
|
564
|
+
) ?? [];
|
|
565
|
+
const remainingAgents = availableAgents.filter(
|
|
566
|
+
(agent) => !detectedSet.has(agent) && !validLastSelected.includes(agent)
|
|
567
|
+
);
|
|
568
|
+
const orderedAgents = [
|
|
569
|
+
...detectedAgents,
|
|
570
|
+
...validLastSelected,
|
|
571
|
+
...remainingAgents
|
|
572
|
+
];
|
|
573
|
+
const choices = orderedAgents.map((agentType) => {
|
|
574
|
+
const routing = agentRouting.get(agentType);
|
|
575
|
+
const baseHint = routing === "local" ? "project" : routing === "global" ? "global" : shortenPath(agents[agentType].configPath);
|
|
576
|
+
const lastSelectedHint = validLastSelected.includes(agentType) ? "selected last time" : "";
|
|
577
|
+
const hint = lastSelectedHint ? `${baseHint} \xB7 ${lastSelectedHint}` : baseHint;
|
|
578
|
+
return {
|
|
579
|
+
value: agentType,
|
|
580
|
+
label: agents[agentType].displayName,
|
|
581
|
+
hint
|
|
582
|
+
};
|
|
583
|
+
});
|
|
584
|
+
return { choices, initialValues: detectedAgents };
|
|
585
|
+
}
|
|
586
|
+
async function promptForAgents(message, choices, defaultToAll = false) {
|
|
587
|
+
let lastSelected;
|
|
588
|
+
try {
|
|
589
|
+
lastSelected = await getLastSelectedAgents();
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
const validAgents = choices.map((c) => c.value);
|
|
593
|
+
let initialValues;
|
|
594
|
+
if (lastSelected && lastSelected.length > 0) {
|
|
595
|
+
initialValues = lastSelected.filter(
|
|
596
|
+
(a) => validAgents.includes(a)
|
|
597
|
+
);
|
|
598
|
+
if (initialValues.length === 0 && defaultToAll) {
|
|
599
|
+
initialValues = validAgents;
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
initialValues = defaultToAll ? validAgents : [];
|
|
603
|
+
}
|
|
604
|
+
const selected = await p.multiselect({
|
|
605
|
+
message,
|
|
606
|
+
options: choices,
|
|
607
|
+
required: true,
|
|
608
|
+
initialValues
|
|
609
|
+
});
|
|
610
|
+
if (!p.isCancel(selected)) {
|
|
611
|
+
try {
|
|
612
|
+
await saveSelectedAgents(selected);
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return selected;
|
|
617
|
+
}
|
|
618
|
+
async function selectAgentsInteractive(availableAgents, options) {
|
|
619
|
+
let lastSelected;
|
|
620
|
+
try {
|
|
621
|
+
lastSelected = await getLastSelectedAgents();
|
|
622
|
+
} catch {
|
|
623
|
+
}
|
|
624
|
+
const validLastSelected = lastSelected?.filter(
|
|
625
|
+
(a) => availableAgents.includes(a)
|
|
626
|
+
);
|
|
627
|
+
const selectOptions = [];
|
|
628
|
+
const hasPrevious = validLastSelected && validLastSelected.length > 0;
|
|
629
|
+
if (hasPrevious) {
|
|
630
|
+
const agentNames = validLastSelected.map((a) => agents[a].displayName).join(", ");
|
|
631
|
+
selectOptions.push({
|
|
632
|
+
value: "previous",
|
|
633
|
+
label: "Same as last time",
|
|
634
|
+
hint: agentNames
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
selectOptions.push({
|
|
638
|
+
value: "all",
|
|
639
|
+
label: hasPrevious ? "All available agents" : "All available agents",
|
|
640
|
+
hint: `Install to all ${availableAgents.length} available agents`
|
|
641
|
+
});
|
|
642
|
+
selectOptions.push({
|
|
643
|
+
value: "select",
|
|
644
|
+
label: "Select specific agents",
|
|
645
|
+
hint: "Choose which agents to install to"
|
|
646
|
+
});
|
|
647
|
+
const installChoice = await p.select({
|
|
648
|
+
message: "Install to",
|
|
649
|
+
options: selectOptions
|
|
650
|
+
});
|
|
651
|
+
if (p.isCancel(installChoice)) {
|
|
652
|
+
return installChoice;
|
|
653
|
+
}
|
|
654
|
+
if (installChoice === "all") {
|
|
655
|
+
return availableAgents;
|
|
656
|
+
}
|
|
657
|
+
if (installChoice === "previous" && validLastSelected) {
|
|
658
|
+
return validLastSelected;
|
|
659
|
+
}
|
|
660
|
+
const agentChoices = availableAgents.map((agentType) => {
|
|
661
|
+
const localPath = agents[agentType].localConfigPath;
|
|
662
|
+
const hint = options.global ? shortenPath(agents[agentType].configPath) : localPath ?? shortenPath(agents[agentType].configPath);
|
|
663
|
+
return {
|
|
664
|
+
value: agentType,
|
|
665
|
+
label: agents[agentType].displayName,
|
|
666
|
+
hint
|
|
667
|
+
};
|
|
668
|
+
});
|
|
669
|
+
return promptForAgents("Select agents to install to", agentChoices, false);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/formats/utils.ts
|
|
673
|
+
function deepMerge(target, source) {
|
|
674
|
+
const result = { ...target };
|
|
675
|
+
for (const key in source) {
|
|
676
|
+
const sourceValue = source[key];
|
|
677
|
+
const targetValue = result[key];
|
|
678
|
+
if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
|
|
679
|
+
result[key] = deepMerge(
|
|
680
|
+
targetValue && typeof targetValue === "object" ? targetValue : {},
|
|
681
|
+
sourceValue
|
|
682
|
+
);
|
|
683
|
+
} else {
|
|
684
|
+
result[key] = sourceValue;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
function getNestedValue(obj, path) {
|
|
690
|
+
const keys = path.split(".");
|
|
691
|
+
let current = obj;
|
|
692
|
+
for (const key of keys) {
|
|
693
|
+
if (current && typeof current === "object" && key in current) {
|
|
694
|
+
current = current[key];
|
|
695
|
+
} else {
|
|
696
|
+
return void 0;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return current;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/formats/json.ts
|
|
703
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
704
|
+
import { dirname as dirname3 } from "path";
|
|
705
|
+
import * as jsonc from "jsonc-parser";
|
|
706
|
+
function detectIndent(text) {
|
|
707
|
+
let result = null;
|
|
708
|
+
jsonc.visit(text, {
|
|
709
|
+
onObjectProperty: (_property, offset, _length, startLine, startCharacter) => {
|
|
710
|
+
if (result === null && startLine > 0 && startCharacter > 0) {
|
|
711
|
+
const lineStart = text.lastIndexOf("\n", offset - 1) + 1;
|
|
712
|
+
const whitespace = text.slice(lineStart, offset);
|
|
713
|
+
result = {
|
|
714
|
+
tabSize: startCharacter,
|
|
715
|
+
insertSpaces: !whitespace.includes(" ")
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
return result || { tabSize: 2, insertSpaces: true };
|
|
721
|
+
}
|
|
722
|
+
function readJsonConfig(filePath) {
|
|
723
|
+
if (!existsSync2(filePath)) {
|
|
724
|
+
return {};
|
|
725
|
+
}
|
|
726
|
+
const content = readFileSync(filePath, "utf-8");
|
|
727
|
+
const parsed = jsonc.parse(content);
|
|
728
|
+
return parsed;
|
|
729
|
+
}
|
|
730
|
+
function writeJsonConfig(filePath, config, configKey) {
|
|
731
|
+
const dir = dirname3(filePath);
|
|
732
|
+
if (!existsSync2(dir)) {
|
|
733
|
+
mkdirSync(dir, { recursive: true });
|
|
734
|
+
}
|
|
735
|
+
let originalContent = "";
|
|
736
|
+
let existingConfig = {};
|
|
737
|
+
if (existsSync2(filePath)) {
|
|
738
|
+
originalContent = readFileSync(filePath, "utf-8");
|
|
739
|
+
existingConfig = jsonc.parse(originalContent);
|
|
740
|
+
}
|
|
741
|
+
const mergedConfig = deepMerge(existingConfig, config);
|
|
742
|
+
if (originalContent) {
|
|
743
|
+
try {
|
|
744
|
+
const configKeyPath = configKey.split(".");
|
|
745
|
+
const newValue = getNestedValue(mergedConfig, configKey);
|
|
746
|
+
const edits = jsonc.modify(originalContent, configKeyPath, newValue, {
|
|
747
|
+
formattingOptions: detectIndent(originalContent)
|
|
748
|
+
});
|
|
749
|
+
const updatedContent = jsonc.applyEdits(originalContent, edits);
|
|
750
|
+
writeFileSync(filePath, updatedContent);
|
|
751
|
+
return;
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
writeFileSync(filePath, JSON.stringify(mergedConfig, null, 2));
|
|
756
|
+
}
|
|
757
|
+
function removeJsonConfigKey(filePath, configKey, serverName) {
|
|
758
|
+
if (!existsSync2(filePath)) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const originalContent = readFileSync(filePath, "utf-8");
|
|
762
|
+
const configKeyPath = configKey.split(".");
|
|
763
|
+
try {
|
|
764
|
+
const edits = jsonc.modify(
|
|
765
|
+
originalContent,
|
|
766
|
+
[...configKeyPath, serverName],
|
|
767
|
+
void 0,
|
|
768
|
+
{ formattingOptions: detectIndent(originalContent) }
|
|
769
|
+
);
|
|
770
|
+
const updatedContent = jsonc.applyEdits(originalContent, edits);
|
|
771
|
+
writeFileSync(filePath, updatedContent);
|
|
772
|
+
return;
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
const parsed = jsonc.parse(originalContent);
|
|
776
|
+
const servers = getNestedValue(parsed, configKey);
|
|
777
|
+
if (servers && typeof servers === "object" && serverName in servers) {
|
|
778
|
+
delete servers[serverName];
|
|
779
|
+
}
|
|
780
|
+
writeFileSync(filePath, JSON.stringify(parsed, null, 2));
|
|
781
|
+
}
|
|
782
|
+
function setNestedValue(obj, path, value) {
|
|
783
|
+
const keys = path.split(".");
|
|
784
|
+
const lastKey = keys.pop();
|
|
785
|
+
if (!lastKey) return;
|
|
786
|
+
let current = obj;
|
|
787
|
+
for (const key of keys) {
|
|
788
|
+
if (!current[key] || typeof current[key] !== "object") {
|
|
789
|
+
current[key] = {};
|
|
790
|
+
}
|
|
791
|
+
current = current[key];
|
|
792
|
+
}
|
|
793
|
+
current[lastKey] = value;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/formats/yaml.ts
|
|
797
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
798
|
+
import { dirname as dirname4 } from "path";
|
|
799
|
+
import yaml from "js-yaml";
|
|
800
|
+
function readYamlConfig(filePath) {
|
|
801
|
+
if (!existsSync3(filePath)) {
|
|
802
|
+
return {};
|
|
803
|
+
}
|
|
804
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
805
|
+
const parsed = yaml.load(content);
|
|
806
|
+
return parsed || {};
|
|
807
|
+
}
|
|
808
|
+
function removeYamlConfigKey(filePath, configKey, serverName) {
|
|
809
|
+
if (!existsSync3(filePath)) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const existing = readYamlConfig(filePath);
|
|
813
|
+
const keys = configKey.split(".");
|
|
814
|
+
let current = existing;
|
|
815
|
+
for (const key of keys) {
|
|
816
|
+
if (current && typeof current === "object" && key in current) {
|
|
817
|
+
current = current[key];
|
|
818
|
+
} else {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (current && typeof current === "object" && serverName in current) {
|
|
823
|
+
delete current[serverName];
|
|
824
|
+
}
|
|
825
|
+
const content = yaml.dump(existing, {
|
|
826
|
+
indent: 2,
|
|
827
|
+
lineWidth: -1,
|
|
828
|
+
noRefs: true
|
|
829
|
+
});
|
|
830
|
+
writeFileSync2(filePath, content);
|
|
831
|
+
}
|
|
832
|
+
function writeYamlConfig(filePath, config) {
|
|
833
|
+
const dir = dirname4(filePath);
|
|
834
|
+
if (!existsSync3(dir)) {
|
|
835
|
+
mkdirSync2(dir, { recursive: true });
|
|
836
|
+
}
|
|
837
|
+
let existingConfig = {};
|
|
838
|
+
if (existsSync3(filePath)) {
|
|
839
|
+
existingConfig = readYamlConfig(filePath);
|
|
840
|
+
}
|
|
841
|
+
const mergedConfig = deepMerge(existingConfig, config);
|
|
842
|
+
const content = yaml.dump(mergedConfig, {
|
|
843
|
+
indent: 2,
|
|
844
|
+
lineWidth: -1,
|
|
845
|
+
noRefs: true
|
|
846
|
+
});
|
|
847
|
+
writeFileSync2(filePath, content);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/formats/toml.ts
|
|
851
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
852
|
+
import { dirname as dirname5 } from "path";
|
|
853
|
+
import * as TOML from "@iarna/toml";
|
|
854
|
+
function readTomlConfig(filePath) {
|
|
855
|
+
if (!existsSync4(filePath)) {
|
|
856
|
+
return {};
|
|
857
|
+
}
|
|
858
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
859
|
+
const parsed = TOML.parse(content);
|
|
860
|
+
return parsed;
|
|
861
|
+
}
|
|
862
|
+
function removeTomlConfigKey(filePath, configKey, serverName) {
|
|
863
|
+
if (!existsSync4(filePath)) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const existing = readTomlConfig(filePath);
|
|
867
|
+
const keys = configKey.split(".");
|
|
868
|
+
let current = existing;
|
|
869
|
+
for (const key of keys) {
|
|
870
|
+
if (current && typeof current === "object" && key in current) {
|
|
871
|
+
current = current[key];
|
|
872
|
+
} else {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (current && typeof current === "object" && serverName in current) {
|
|
877
|
+
delete current[serverName];
|
|
878
|
+
}
|
|
879
|
+
const content = TOML.stringify(existing);
|
|
880
|
+
writeFileSync3(filePath, content);
|
|
881
|
+
}
|
|
882
|
+
function writeTomlConfig(filePath, config) {
|
|
883
|
+
const dir = dirname5(filePath);
|
|
884
|
+
if (!existsSync4(dir)) {
|
|
885
|
+
mkdirSync3(dir, { recursive: true });
|
|
886
|
+
}
|
|
887
|
+
let existingConfig = {};
|
|
888
|
+
if (existsSync4(filePath)) {
|
|
889
|
+
existingConfig = readTomlConfig(filePath);
|
|
890
|
+
}
|
|
891
|
+
const mergedConfig = deepMerge(existingConfig, config);
|
|
892
|
+
const content = TOML.stringify(mergedConfig);
|
|
893
|
+
writeFileSync3(filePath, content);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/formats/index.ts
|
|
897
|
+
function readConfig2(filePath, format) {
|
|
898
|
+
switch (format) {
|
|
899
|
+
case "json":
|
|
900
|
+
return readJsonConfig(filePath);
|
|
901
|
+
case "yaml":
|
|
902
|
+
return readYamlConfig(filePath);
|
|
903
|
+
case "toml":
|
|
904
|
+
return readTomlConfig(filePath);
|
|
905
|
+
default:
|
|
906
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function removeServerFromConfig(filePath, format, configKey, serverName) {
|
|
910
|
+
switch (format) {
|
|
911
|
+
case "json":
|
|
912
|
+
removeJsonConfigKey(filePath, configKey, serverName);
|
|
913
|
+
break;
|
|
914
|
+
case "yaml":
|
|
915
|
+
removeYamlConfigKey(filePath, configKey, serverName);
|
|
916
|
+
break;
|
|
917
|
+
case "toml":
|
|
918
|
+
removeTomlConfigKey(filePath, configKey, serverName);
|
|
919
|
+
break;
|
|
920
|
+
default:
|
|
921
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function writeConfig2(filePath, config, format, configKey) {
|
|
925
|
+
switch (format) {
|
|
926
|
+
case "json":
|
|
927
|
+
writeJsonConfig(filePath, config, configKey);
|
|
928
|
+
break;
|
|
929
|
+
case "yaml":
|
|
930
|
+
writeYamlConfig(filePath, config);
|
|
931
|
+
break;
|
|
932
|
+
case "toml":
|
|
933
|
+
writeTomlConfig(filePath, config);
|
|
934
|
+
break;
|
|
935
|
+
default:
|
|
936
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function buildConfigWithKey(configKey, serverName, serverConfig) {
|
|
940
|
+
const config = {};
|
|
941
|
+
const servers = {};
|
|
942
|
+
servers[serverName] = serverConfig;
|
|
943
|
+
setNestedValue(config, configKey, servers);
|
|
944
|
+
return config;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/installer.ts
|
|
948
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
949
|
+
import { join as join3, dirname as dirname6, isAbsolute, relative, sep } from "path";
|
|
950
|
+
function buildServerConfig(parsed, options = {}) {
|
|
951
|
+
if (parsed.type === "remote") {
|
|
952
|
+
const config2 = {
|
|
953
|
+
type: options.transport ?? "http",
|
|
954
|
+
url: parsed.value
|
|
955
|
+
};
|
|
956
|
+
if (options.headers && Object.keys(options.headers).length > 0) {
|
|
957
|
+
config2.headers = options.headers;
|
|
958
|
+
}
|
|
959
|
+
return config2;
|
|
960
|
+
}
|
|
961
|
+
if (parsed.type === "command") {
|
|
962
|
+
const parts = parsed.value.split(" ");
|
|
963
|
+
const command = parts[0];
|
|
964
|
+
const args = [...parts.slice(1), ...options.args ?? []];
|
|
965
|
+
const config2 = {
|
|
966
|
+
command,
|
|
967
|
+
args
|
|
968
|
+
};
|
|
969
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
970
|
+
config2.env = options.env;
|
|
971
|
+
}
|
|
972
|
+
return config2;
|
|
973
|
+
}
|
|
974
|
+
const config = {
|
|
975
|
+
command: "npx",
|
|
976
|
+
args: ["-y", parsed.value, ...options.args ?? []]
|
|
977
|
+
};
|
|
978
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
979
|
+
config.env = options.env;
|
|
980
|
+
}
|
|
981
|
+
return config;
|
|
982
|
+
}
|
|
983
|
+
function updateGitignoreWithPaths(paths, options = {}) {
|
|
984
|
+
const cwd = options.cwd || process.cwd();
|
|
985
|
+
const gitignorePath = join3(cwd, ".gitignore");
|
|
986
|
+
const existingContent = existsSync5(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : "";
|
|
987
|
+
const existingEntries = new Set(
|
|
988
|
+
existingContent.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0)
|
|
989
|
+
);
|
|
990
|
+
const entriesToAdd = [];
|
|
991
|
+
for (const filePath of paths) {
|
|
992
|
+
const relativePath = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
|
|
993
|
+
if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const normalizedPath = relativePath.split(sep).join("/");
|
|
997
|
+
const cleanPath = normalizedPath.startsWith("./") ? normalizedPath.slice(2) : normalizedPath;
|
|
998
|
+
if (!cleanPath || cleanPath === ".gitignore" || existingEntries.has(cleanPath)) {
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
existingEntries.add(cleanPath);
|
|
1002
|
+
entriesToAdd.push(cleanPath);
|
|
1003
|
+
}
|
|
1004
|
+
if (entriesToAdd.length > 0) {
|
|
1005
|
+
let nextContent = existingContent;
|
|
1006
|
+
if (nextContent.length > 0 && !nextContent.endsWith("\n")) {
|
|
1007
|
+
nextContent += "\n";
|
|
1008
|
+
}
|
|
1009
|
+
nextContent += `${entriesToAdd.join("\n")}
|
|
1010
|
+
`;
|
|
1011
|
+
writeFileSync4(gitignorePath, nextContent, "utf-8");
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
path: gitignorePath,
|
|
1015
|
+
added: entriesToAdd
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
function getConfigPath2(agent, options = {}) {
|
|
1019
|
+
const local = Boolean(options.local);
|
|
1020
|
+
const cwd = options.cwd || process.cwd();
|
|
1021
|
+
if (agent.resolveConfigPath) {
|
|
1022
|
+
return agent.resolveConfigPath(agent, { local, cwd });
|
|
1023
|
+
}
|
|
1024
|
+
if (local && agent.localConfigPath) {
|
|
1025
|
+
return join3(cwd, agent.localConfigPath);
|
|
1026
|
+
}
|
|
1027
|
+
return agent.configPath;
|
|
1028
|
+
}
|
|
1029
|
+
function getConfigKey(agent, options = {}) {
|
|
1030
|
+
if (options.local && agent.localConfigKey) {
|
|
1031
|
+
return agent.localConfigKey;
|
|
1032
|
+
}
|
|
1033
|
+
return agent.configKey;
|
|
1034
|
+
}
|
|
1035
|
+
function installServerForAgent(serverName, serverConfig, agentType, options = {}) {
|
|
1036
|
+
const agent = agents[agentType];
|
|
1037
|
+
const configPath = getConfigPath2(agent, options);
|
|
1038
|
+
try {
|
|
1039
|
+
const dir = dirname6(configPath);
|
|
1040
|
+
if (!existsSync5(dir)) {
|
|
1041
|
+
mkdirSync4(dir, { recursive: true });
|
|
1042
|
+
}
|
|
1043
|
+
const transformedConfig = agent.transformConfig ? agent.transformConfig(serverName, serverConfig, {
|
|
1044
|
+
local: Boolean(options.local)
|
|
1045
|
+
}) : serverConfig;
|
|
1046
|
+
const configKey = getConfigKey(agent, options);
|
|
1047
|
+
const config = buildConfigWithKey(configKey, serverName, transformedConfig);
|
|
1048
|
+
writeConfig2(configPath, config, agent.format, configKey);
|
|
1049
|
+
return {
|
|
1050
|
+
success: true,
|
|
1051
|
+
path: configPath
|
|
1052
|
+
};
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
return {
|
|
1055
|
+
success: false,
|
|
1056
|
+
path: configPath,
|
|
1057
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function installServer(serverName, serverConfig, agentTypes, options = {}) {
|
|
1062
|
+
const results = /* @__PURE__ */ new Map();
|
|
1063
|
+
for (const agentType of agentTypes) {
|
|
1064
|
+
const routing = options.routing?.get(agentType);
|
|
1065
|
+
const installOptions = {
|
|
1066
|
+
local: routing === "local",
|
|
1067
|
+
cwd: options.cwd
|
|
1068
|
+
};
|
|
1069
|
+
const result = installServerForAgent(
|
|
1070
|
+
serverName,
|
|
1071
|
+
serverConfig,
|
|
1072
|
+
agentType,
|
|
1073
|
+
installOptions
|
|
1074
|
+
);
|
|
1075
|
+
results.set(agentType, result);
|
|
1076
|
+
}
|
|
1077
|
+
return results;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/reader.ts
|
|
1081
|
+
function extractServerIdentity(serverConfig) {
|
|
1082
|
+
for (const key of ["url", "uri", "serverUrl"]) {
|
|
1083
|
+
const value = serverConfig[key];
|
|
1084
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1085
|
+
return value;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const command = typeof serverConfig.command === "string" ? serverConfig.command : typeof serverConfig.cmd === "string" ? serverConfig.cmd : void 0;
|
|
1089
|
+
if (!command) {
|
|
1090
|
+
return "";
|
|
1091
|
+
}
|
|
1092
|
+
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") : [];
|
|
1093
|
+
if (command === "npx" || command === "bunx") {
|
|
1094
|
+
const yIndex = rawArgs.indexOf("-y");
|
|
1095
|
+
const pkgIndex = yIndex >= 0 ? yIndex + 1 : 0;
|
|
1096
|
+
const pkg = rawArgs[pkgIndex];
|
|
1097
|
+
if (pkg && !pkg.startsWith("-")) {
|
|
1098
|
+
return pkg;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (Array.isArray(serverConfig.command)) {
|
|
1102
|
+
return serverConfig.command.join(" ");
|
|
1103
|
+
}
|
|
1104
|
+
if (rawArgs.length > 0) {
|
|
1105
|
+
return `${command} ${rawArgs.join(" ")}`;
|
|
1106
|
+
}
|
|
1107
|
+
return command;
|
|
1108
|
+
}
|
|
1109
|
+
function readServersForAgent(agentType, options) {
|
|
1110
|
+
const agent = agents[agentType];
|
|
1111
|
+
const installOptions = {
|
|
1112
|
+
local: options.scope === "local",
|
|
1113
|
+
cwd: options.cwd
|
|
1114
|
+
};
|
|
1115
|
+
const configPath = getConfigPath2(agent, installOptions);
|
|
1116
|
+
const configKey = getConfigKey(agent, installOptions);
|
|
1117
|
+
const fullConfig = readConfig2(configPath, agent.format);
|
|
1118
|
+
const serversObj = getNestedValue(fullConfig, configKey);
|
|
1119
|
+
const servers = [];
|
|
1120
|
+
if (serversObj && typeof serversObj === "object" && !Array.isArray(serversObj)) {
|
|
1121
|
+
for (const [serverName, serverConfig] of Object.entries(serversObj)) {
|
|
1122
|
+
if (serverConfig && typeof serverConfig === "object") {
|
|
1123
|
+
const config = serverConfig;
|
|
1124
|
+
servers.push({
|
|
1125
|
+
serverName,
|
|
1126
|
+
config,
|
|
1127
|
+
identity: extractServerIdentity(config),
|
|
1128
|
+
agentType,
|
|
1129
|
+
scope: options.scope,
|
|
1130
|
+
configPath
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
agentType,
|
|
1137
|
+
displayName: agent.displayName,
|
|
1138
|
+
detected: true,
|
|
1139
|
+
scope: options.scope,
|
|
1140
|
+
configPath,
|
|
1141
|
+
servers
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
async function listInstalledServers(options) {
|
|
1145
|
+
const scope = options.global ? "global" : "local";
|
|
1146
|
+
const results = [];
|
|
1147
|
+
if (options.agents && options.agents.length > 0) {
|
|
1148
|
+
const detectedSet = new Set(
|
|
1149
|
+
options.global ? await detectGlobalAgents() : detectProjectAgents(options.cwd)
|
|
1150
|
+
);
|
|
1151
|
+
for (const agentType of options.agents) {
|
|
1152
|
+
const detected = detectedSet.has(agentType);
|
|
1153
|
+
if (detected) {
|
|
1154
|
+
results.push(
|
|
1155
|
+
readServersForAgent(agentType, { scope, cwd: options.cwd })
|
|
1156
|
+
);
|
|
1157
|
+
} else {
|
|
1158
|
+
const agent = agents[agentType];
|
|
1159
|
+
const installOptions = {
|
|
1160
|
+
local: scope === "local",
|
|
1161
|
+
cwd: options.cwd
|
|
1162
|
+
};
|
|
1163
|
+
results.push({
|
|
1164
|
+
agentType,
|
|
1165
|
+
displayName: agent.displayName,
|
|
1166
|
+
detected: false,
|
|
1167
|
+
scope,
|
|
1168
|
+
configPath: getConfigPath2(agent, installOptions),
|
|
1169
|
+
servers: []
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
} else {
|
|
1174
|
+
const detected = options.global ? await detectGlobalAgents() : detectProjectAgents(options.cwd);
|
|
1175
|
+
for (const agentType of detected) {
|
|
1176
|
+
results.push(readServersForAgent(agentType, { scope, cwd: options.cwd }));
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return results;
|
|
1180
|
+
}
|
|
1181
|
+
function findMatchingServers(agentServersList, query) {
|
|
1182
|
+
const lowerQuery = query.toLowerCase();
|
|
1183
|
+
const matches = [];
|
|
1184
|
+
for (const agentServers of agentServersList) {
|
|
1185
|
+
for (const server of agentServers.servers) {
|
|
1186
|
+
const nameMatch = server.serverName.toLowerCase().includes(lowerQuery);
|
|
1187
|
+
const identityMatch = server.identity === query;
|
|
1188
|
+
if (nameMatch || identityMatch) {
|
|
1189
|
+
matches.push(server);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return matches;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
export {
|
|
1197
|
+
getConfigPath,
|
|
1198
|
+
getLastSelectedAgents,
|
|
1199
|
+
getFindRegistries,
|
|
1200
|
+
saveFindRegistries,
|
|
1201
|
+
agents,
|
|
1202
|
+
getAgentTypes,
|
|
1203
|
+
supportsProjectConfig,
|
|
1204
|
+
getProjectCapableAgents,
|
|
1205
|
+
detectProjectAgents,
|
|
1206
|
+
detectGlobalAgents,
|
|
1207
|
+
isTransportSupported,
|
|
1208
|
+
buildAgentSelectionChoices,
|
|
1209
|
+
selectAgentsInteractive,
|
|
1210
|
+
getNestedValue,
|
|
1211
|
+
readConfig2 as readConfig,
|
|
1212
|
+
removeServerFromConfig,
|
|
1213
|
+
buildServerConfig,
|
|
1214
|
+
updateGitignoreWithPaths,
|
|
1215
|
+
getConfigPath2,
|
|
1216
|
+
getConfigKey,
|
|
1217
|
+
installServerForAgent,
|
|
1218
|
+
installServer,
|
|
1219
|
+
listInstalledServers,
|
|
1220
|
+
findMatchingServers
|
|
1221
|
+
};
|