@wuyax/mcps 0.1.0-beta.1
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 +318 -0
- package/bin/mcps.mjs +11 -0
- package/dist/chunk-XW2KL6W3.js +2360 -0
- package/dist/cli.cjs +2534 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +228 -0
- package/dist/index.cjs +2453 -0
- package/dist/index.d.cts +378 -0
- package/dist/index.d.ts +378 -0
- package/dist/index.js +111 -0
- package/package.json +52 -0
|
@@ -0,0 +1,2360 @@
|
|
|
1
|
+
// src/agents.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { homedir, platform } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var home = homedir();
|
|
6
|
+
var getPlatformPaths = () => {
|
|
7
|
+
const currentPlatform = platform();
|
|
8
|
+
if (currentPlatform === "win32") {
|
|
9
|
+
const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
|
|
10
|
+
return {
|
|
11
|
+
appSupport: appData,
|
|
12
|
+
vscodePath: join(appData, "Code", "User"),
|
|
13
|
+
traePath: join(appData, "Trae", "User"),
|
|
14
|
+
gooseConfigPath: join(appData, "Block", "goose", "config", "config.yaml"),
|
|
15
|
+
zedConfigPath: join(appData, "Zed", "settings.json")
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (currentPlatform === "darwin") {
|
|
19
|
+
return {
|
|
20
|
+
appSupport: join(home, "Library", "Application Support"),
|
|
21
|
+
vscodePath: join(home, "Library", "Application Support", "Code", "User"),
|
|
22
|
+
traePath: join(home, "Library", "Application Support", "Trae", "User"),
|
|
23
|
+
gooseConfigPath: join(home, ".config", "goose", "config.yaml"),
|
|
24
|
+
zedConfigPath: join(home, ".config", "zed", "settings.json")
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const configDir = process.env.XDG_CONFIG_HOME || join(home, ".config");
|
|
28
|
+
return {
|
|
29
|
+
appSupport: configDir,
|
|
30
|
+
vscodePath: join(configDir, "Code", "User"),
|
|
31
|
+
traePath: join(configDir, "Trae", "User"),
|
|
32
|
+
gooseConfigPath: join(configDir, "goose", "config.yaml"),
|
|
33
|
+
zedConfigPath: join(configDir, "zed", "settings.json")
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
var { appSupport, vscodePath, traePath, gooseConfigPath, zedConfigPath } = getPlatformPaths();
|
|
37
|
+
var ampConfigDir = process.env.AMP_HOME?.trim() || join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "amp");
|
|
38
|
+
var ampGlobalConfigPath = existsSync(join(ampConfigDir, "settings.jsonc")) ? join(ampConfigDir, "settings.jsonc") : join(ampConfigDir, "settings.json");
|
|
39
|
+
var antigravityMcpConfigPath = join(home, ".gemini", "config", "mcp_config.json");
|
|
40
|
+
var augmentConfigDir = process.env.AUGMENT_HOME?.trim() || join(home, ".augment");
|
|
41
|
+
var augmentGlobalConfigPath = existsSync(join(augmentConfigDir, "settings.jsonc")) ? join(augmentConfigDir, "settings.jsonc") : join(augmentConfigDir, "settings.json");
|
|
42
|
+
var clineDir = process.env.CLINE_DIR || join(home, ".cline");
|
|
43
|
+
var clineCliConfigPath = join(clineDir, "mcp.json");
|
|
44
|
+
var clineExtensionConfigPath = join(
|
|
45
|
+
vscodePath,
|
|
46
|
+
"globalStorage",
|
|
47
|
+
"saoudrizwan.claude-dev",
|
|
48
|
+
"settings",
|
|
49
|
+
"cline_mcp_settings.json"
|
|
50
|
+
);
|
|
51
|
+
var copilotConfigPath = join(
|
|
52
|
+
process.env.COPILOT_HOME?.trim() || join(home, ".copilot"),
|
|
53
|
+
"mcp-config.json"
|
|
54
|
+
);
|
|
55
|
+
var grokConfigPath = join(
|
|
56
|
+
process.env.GROK_HOME?.trim() || join(home, ".grok"),
|
|
57
|
+
"config.toml"
|
|
58
|
+
);
|
|
59
|
+
var kimiCodeConfigPath = join(
|
|
60
|
+
process.env.KIMI_CODE_HOME?.trim() || join(home, ".kimi-code"),
|
|
61
|
+
"mcp.json"
|
|
62
|
+
);
|
|
63
|
+
var kiroConfigPath = join(
|
|
64
|
+
process.env.KIRO_HOME?.trim() || join(home, ".kiro"),
|
|
65
|
+
"settings",
|
|
66
|
+
"mcp.json"
|
|
67
|
+
);
|
|
68
|
+
var qoderConfigPath = join(
|
|
69
|
+
process.env.QODER_HOME?.trim() || join(home, ".qoder"),
|
|
70
|
+
"settings.json"
|
|
71
|
+
);
|
|
72
|
+
var qwenCodeConfigPath = join(
|
|
73
|
+
process.env.QWEN_CODE_HOME?.trim() || process.env.QWEN_HOME?.trim() || join(home, ".qwen"),
|
|
74
|
+
"settings.json"
|
|
75
|
+
);
|
|
76
|
+
var traeConfigPath = existsSync(join(home, ".trae", "mcp.json")) ? join(home, ".trae", "mcp.json") : join(traePath, "mcp.json");
|
|
77
|
+
var ALL_TRANSPORTS = ["stdio", "http", "sse"];
|
|
78
|
+
var mcpAgents = {
|
|
79
|
+
// https://ampcode.com/docs/markdown/customize/mcp
|
|
80
|
+
amp: {
|
|
81
|
+
name: "amp",
|
|
82
|
+
displayName: "Amp",
|
|
83
|
+
globalConfigPath: ampGlobalConfigPath,
|
|
84
|
+
projectConfigPath: ".amp/settings.json",
|
|
85
|
+
configKey: "amp.mcpServers",
|
|
86
|
+
format: "jsonc",
|
|
87
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
88
|
+
detectGlobalInstall: () => existsSync(ampConfigDir) || existsSync(join(home, ".amp")),
|
|
89
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".amp", "settings.json")) || existsSync(join(cwd, ".amp", "settings.jsonc")) || existsSync(join(cwd, ".amp")),
|
|
90
|
+
resolveConfigPath: ({ global: isGlobal, cwd }) => {
|
|
91
|
+
if (isGlobal) {
|
|
92
|
+
if (existsSync(join(ampConfigDir, "settings.jsonc"))) {
|
|
93
|
+
return join(ampConfigDir, "settings.jsonc");
|
|
94
|
+
}
|
|
95
|
+
return ampGlobalConfigPath;
|
|
96
|
+
}
|
|
97
|
+
if (existsSync(join(cwd, ".amp", "settings.jsonc"))) {
|
|
98
|
+
return join(cwd, ".amp", "settings.jsonc");
|
|
99
|
+
}
|
|
100
|
+
return join(cwd, ".amp", "settings.json");
|
|
101
|
+
},
|
|
102
|
+
transformDialect: "amp"
|
|
103
|
+
},
|
|
104
|
+
antigravity: {
|
|
105
|
+
name: "antigravity",
|
|
106
|
+
displayName: "Antigravity",
|
|
107
|
+
globalConfigPath: antigravityMcpConfigPath,
|
|
108
|
+
projectConfigPath: ".agents/mcp_config.json",
|
|
109
|
+
configKey: "mcpServers",
|
|
110
|
+
format: "jsonc",
|
|
111
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
112
|
+
detectGlobalInstall: () => existsSync(join(home, ".gemini", "antigravity")) || existsSync(join(home, ".gemini", "config")),
|
|
113
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".agents", "mcp_config.json")) || existsSync(join(cwd, ".agents"))
|
|
114
|
+
},
|
|
115
|
+
// https://antigravity.google/docs/cli/mcp/
|
|
116
|
+
"antigravity-cli": {
|
|
117
|
+
name: "antigravity-cli",
|
|
118
|
+
displayName: "Antigravity CLI",
|
|
119
|
+
globalConfigPath: antigravityMcpConfigPath,
|
|
120
|
+
projectConfigPath: ".agents/mcp_config.json",
|
|
121
|
+
configKey: "mcpServers",
|
|
122
|
+
format: "jsonc",
|
|
123
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
124
|
+
detectGlobalInstall: () => existsSync(join(home, ".gemini", "antigravity-cli")),
|
|
125
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".agents", "mcp_config.json")) || existsSync(join(cwd, ".agents"))
|
|
126
|
+
},
|
|
127
|
+
// https://docs.augmentcode.com/cli/integrations.md
|
|
128
|
+
augment: {
|
|
129
|
+
name: "augment",
|
|
130
|
+
displayName: "Augment",
|
|
131
|
+
globalConfigPath: augmentGlobalConfigPath,
|
|
132
|
+
projectConfigPath: ".augment/settings.json",
|
|
133
|
+
configKey: "mcpServers",
|
|
134
|
+
format: "jsonc",
|
|
135
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
136
|
+
detectGlobalInstall: () => existsSync(augmentConfigDir) || existsSync(join(home, ".augment")),
|
|
137
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".augment", "settings.json")) || existsSync(join(cwd, ".augment", "settings.jsonc")) || existsSync(join(cwd, ".augment")),
|
|
138
|
+
resolveConfigPath: ({ global: isGlobal, cwd }) => {
|
|
139
|
+
if (isGlobal) {
|
|
140
|
+
if (existsSync(join(augmentConfigDir, "settings.jsonc"))) {
|
|
141
|
+
return join(augmentConfigDir, "settings.jsonc");
|
|
142
|
+
}
|
|
143
|
+
return augmentGlobalConfigPath;
|
|
144
|
+
}
|
|
145
|
+
if (existsSync(join(cwd, ".augment", "settings.jsonc"))) {
|
|
146
|
+
return join(cwd, ".augment", "settings.jsonc");
|
|
147
|
+
}
|
|
148
|
+
return join(cwd, ".augment", "settings.json");
|
|
149
|
+
},
|
|
150
|
+
transformDialect: "augment"
|
|
151
|
+
},
|
|
152
|
+
cline: {
|
|
153
|
+
name: "cline",
|
|
154
|
+
displayName: "Cline (VSCode extension)",
|
|
155
|
+
globalConfigPath: clineExtensionConfigPath,
|
|
156
|
+
projectConfigPath: ".cline/mcp.json",
|
|
157
|
+
configKey: "mcpServers",
|
|
158
|
+
format: "jsonc",
|
|
159
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
160
|
+
detectGlobalInstall: () => existsSync(clineExtensionConfigPath),
|
|
161
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".cline", "mcp.json")) || existsSync(join(cwd, ".cline")),
|
|
162
|
+
resolveConfigPath: ({ global: isGlobal, cwd }) => {
|
|
163
|
+
if (isGlobal) {
|
|
164
|
+
return clineExtensionConfigPath;
|
|
165
|
+
}
|
|
166
|
+
return join(cwd, ".cline", "mcp.json");
|
|
167
|
+
},
|
|
168
|
+
transformDialect: "cline"
|
|
169
|
+
},
|
|
170
|
+
// https://docs.cline.bot/mcp/mcp-overview.md
|
|
171
|
+
"cline-cli": {
|
|
172
|
+
name: "cline-cli",
|
|
173
|
+
displayName: "Cline CLI",
|
|
174
|
+
globalConfigPath: clineCliConfigPath,
|
|
175
|
+
projectConfigPath: ".cline/mcp.json",
|
|
176
|
+
configKey: "mcpServers",
|
|
177
|
+
format: "jsonc",
|
|
178
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
179
|
+
detectGlobalInstall: () => existsSync(clineDir),
|
|
180
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".cline", "mcp.json")) || existsSync(join(cwd, ".cline")),
|
|
181
|
+
resolveConfigPath: ({ global: isGlobal, cwd }) => {
|
|
182
|
+
if (isGlobal) {
|
|
183
|
+
return clineCliConfigPath;
|
|
184
|
+
}
|
|
185
|
+
return join(cwd, ".cline", "mcp.json");
|
|
186
|
+
},
|
|
187
|
+
transformDialect: "cline"
|
|
188
|
+
},
|
|
189
|
+
// https://code.claude.com/docs/en/mcp-quickstart#edit-mcp-json-directly
|
|
190
|
+
"claude-code": {
|
|
191
|
+
name: "claude-code",
|
|
192
|
+
displayName: "Claude Code",
|
|
193
|
+
globalConfigPath: join(home, ".claude.json"),
|
|
194
|
+
projectConfigPath: ".mcp.json",
|
|
195
|
+
configKey: "mcpServers",
|
|
196
|
+
format: "jsonc",
|
|
197
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
198
|
+
detectGlobalInstall: () => existsSync(join(home, ".claude.json")),
|
|
199
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".mcp.json"))
|
|
200
|
+
},
|
|
201
|
+
"claude-desktop": {
|
|
202
|
+
name: "claude-desktop",
|
|
203
|
+
displayName: "Claude Desktop",
|
|
204
|
+
globalConfigPath: join(appSupport, "Claude", "claude_desktop_config.json"),
|
|
205
|
+
configKey: "mcpServers",
|
|
206
|
+
format: "jsonc",
|
|
207
|
+
supportedTransports: ["stdio"],
|
|
208
|
+
unsupportedTransportMessage: "Claude Desktop currently supports only stdio MCP servers. Use a package name or command instead of a URL.",
|
|
209
|
+
detectGlobalInstall: () => existsSync(join(appSupport, "Claude", "claude_desktop_config.json"))
|
|
210
|
+
},
|
|
211
|
+
// https://learn.chatgpt.com/docs/extend/mcp?surface=app
|
|
212
|
+
codex: {
|
|
213
|
+
name: "codex",
|
|
214
|
+
displayName: "Codex",
|
|
215
|
+
globalConfigPath: join(process.env.CODEX_HOME?.trim() || join(home, ".codex"), "config.toml"),
|
|
216
|
+
projectConfigPath: ".codex/config.toml",
|
|
217
|
+
configKey: "mcp_servers",
|
|
218
|
+
format: "toml",
|
|
219
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
220
|
+
detectGlobalInstall: () => existsSync(process.env.CODEX_HOME?.trim() || join(home, ".codex")),
|
|
221
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".codex", "config.toml")),
|
|
222
|
+
transformDialect: "augment"
|
|
223
|
+
},
|
|
224
|
+
// https://cursor.com/help/customization/mcp
|
|
225
|
+
cursor: {
|
|
226
|
+
name: "cursor",
|
|
227
|
+
displayName: "Cursor",
|
|
228
|
+
globalConfigPath: join(home, ".cursor", "mcp.json"),
|
|
229
|
+
projectConfigPath: ".cursor/mcp.json",
|
|
230
|
+
configKey: "mcpServers",
|
|
231
|
+
format: "jsonc",
|
|
232
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
233
|
+
detectGlobalInstall: () => existsSync(join(home, ".cursor")),
|
|
234
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".cursor", "mcp.json"))
|
|
235
|
+
},
|
|
236
|
+
"gemini-cli": {
|
|
237
|
+
name: "gemini-cli",
|
|
238
|
+
displayName: "Gemini CLI",
|
|
239
|
+
globalConfigPath: join(home, ".gemini", "settings.json"),
|
|
240
|
+
projectConfigPath: ".gemini/settings.json",
|
|
241
|
+
configKey: "mcpServers",
|
|
242
|
+
format: "jsonc",
|
|
243
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
244
|
+
detectGlobalInstall: () => existsSync(join(home, ".gemini")),
|
|
245
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".gemini", "settings.json"))
|
|
246
|
+
},
|
|
247
|
+
// https://docs.x.ai/build/features/mcp-servers.md
|
|
248
|
+
grok: {
|
|
249
|
+
name: "grok",
|
|
250
|
+
displayName: "Grok",
|
|
251
|
+
globalConfigPath: grokConfigPath,
|
|
252
|
+
projectConfigPath: ".grok/config.toml",
|
|
253
|
+
configKey: "mcp_servers",
|
|
254
|
+
format: "toml",
|
|
255
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
256
|
+
detectGlobalInstall: () => existsSync(grokConfigPath) || existsSync(process.env.GROK_HOME?.trim() || join(home, ".grok")),
|
|
257
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".grok", "config.toml")) || existsSync(join(cwd, ".grok")),
|
|
258
|
+
transformDialect: "grok"
|
|
259
|
+
},
|
|
260
|
+
// https://goose-docs.ai/docs/guides/config-files/
|
|
261
|
+
goose: {
|
|
262
|
+
name: "goose",
|
|
263
|
+
displayName: "Goose",
|
|
264
|
+
globalConfigPath: gooseConfigPath,
|
|
265
|
+
projectConfigPath: ".goose/config.yaml",
|
|
266
|
+
configKey: "extensions",
|
|
267
|
+
format: "yaml",
|
|
268
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
269
|
+
detectGlobalInstall: () => existsSync(gooseConfigPath),
|
|
270
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".goose", "config.yaml")),
|
|
271
|
+
transformDialect: "goose"
|
|
272
|
+
},
|
|
273
|
+
"github-copilot-cli": {
|
|
274
|
+
name: "github-copilot-cli",
|
|
275
|
+
displayName: "GitHub Copilot CLI",
|
|
276
|
+
globalConfigPath: copilotConfigPath,
|
|
277
|
+
projectConfigPath: ".mcp.json",
|
|
278
|
+
configKey: "mcpServers",
|
|
279
|
+
format: "jsonc",
|
|
280
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
281
|
+
detectGlobalInstall: () => existsSync(copilotConfigPath) || existsSync(process.env.COPILOT_HOME?.trim() || join(home, ".copilot")),
|
|
282
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".mcp.json")) || existsSync(join(cwd, ".github", "mcp.json")),
|
|
283
|
+
transformDialect: "vscode"
|
|
284
|
+
},
|
|
285
|
+
// https://www.kimi.com/code/docs/en/kimi-code-cli/customization/mcp.html
|
|
286
|
+
"kimi-code-cli": {
|
|
287
|
+
name: "kimi-code-cli",
|
|
288
|
+
displayName: "Kimi Code CLI",
|
|
289
|
+
globalConfigPath: kimiCodeConfigPath,
|
|
290
|
+
projectConfigPath: ".kimi-code/mcp.json",
|
|
291
|
+
configKey: "mcpServers",
|
|
292
|
+
format: "jsonc",
|
|
293
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
294
|
+
detectGlobalInstall: () => existsSync(kimiCodeConfigPath) || existsSync(process.env.KIMI_CODE_HOME?.trim() || join(home, ".kimi-code")),
|
|
295
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".kimi-code", "mcp.json")) || existsSync(join(cwd, ".kimi-code")),
|
|
296
|
+
transformDialect: "kimi-code"
|
|
297
|
+
},
|
|
298
|
+
// https://kiro.dev/docs/mcp/configuration.md
|
|
299
|
+
kiro: {
|
|
300
|
+
name: "kiro",
|
|
301
|
+
displayName: "Kiro",
|
|
302
|
+
globalConfigPath: kiroConfigPath,
|
|
303
|
+
projectConfigPath: ".kiro/settings/mcp.json",
|
|
304
|
+
configKey: "mcpServers",
|
|
305
|
+
format: "jsonc",
|
|
306
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
307
|
+
detectGlobalInstall: () => existsSync(kiroConfigPath) || existsSync(process.env.KIRO_HOME?.trim() || join(home, ".kiro")),
|
|
308
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".kiro", "settings", "mcp.json")) || existsSync(join(cwd, ".kiro")),
|
|
309
|
+
transformDialect: "kiro"
|
|
310
|
+
},
|
|
311
|
+
// https://opencode.ai/docs/config/
|
|
312
|
+
opencode: {
|
|
313
|
+
name: "opencode",
|
|
314
|
+
displayName: "OpenCode",
|
|
315
|
+
globalConfigPath: join(
|
|
316
|
+
process.env.XDG_CONFIG_HOME || join(home, ".config"),
|
|
317
|
+
"opencode",
|
|
318
|
+
"opencode.json"
|
|
319
|
+
),
|
|
320
|
+
projectConfigPath: "opencode.json",
|
|
321
|
+
configKey: "mcp",
|
|
322
|
+
format: "jsonc",
|
|
323
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
324
|
+
detectGlobalInstall: () => existsSync(join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "opencode")),
|
|
325
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, "opencode.json")),
|
|
326
|
+
transformDialect: "opencode"
|
|
327
|
+
},
|
|
328
|
+
// https://pi.dev/packages/pi-mcp-extension
|
|
329
|
+
pi: {
|
|
330
|
+
name: "pi",
|
|
331
|
+
displayName: "Pi",
|
|
332
|
+
globalConfigPath: join(home, ".pi", "agent", "mcp.json"),
|
|
333
|
+
projectConfigPath: ".pi/mcp.json",
|
|
334
|
+
configKey: "mcpServers",
|
|
335
|
+
format: "jsonc",
|
|
336
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
337
|
+
detectGlobalInstall: () => existsSync(join(home, ".pi")),
|
|
338
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".pi", "mcp.json")) || existsSync(join(cwd, ".pi")),
|
|
339
|
+
transformDialect: "pi"
|
|
340
|
+
},
|
|
341
|
+
// https://docs.qoder.com/zh/cli/mcp-servers.md
|
|
342
|
+
qoder: {
|
|
343
|
+
name: "qoder",
|
|
344
|
+
displayName: "Qoder",
|
|
345
|
+
globalConfigPath: qoderConfigPath,
|
|
346
|
+
projectConfigPath: ".mcp.json",
|
|
347
|
+
configKey: "mcpServers",
|
|
348
|
+
format: "jsonc",
|
|
349
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
350
|
+
detectGlobalInstall: () => existsSync(qoderConfigPath) || existsSync(process.env.QODER_HOME?.trim() || join(home, ".qoder")),
|
|
351
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".mcp.json")) || existsSync(join(cwd, ".qoder", "settings.json")) || existsSync(join(cwd, ".qoder")),
|
|
352
|
+
resolveConfigPath: ({ global: isGlobal, cwd }) => {
|
|
353
|
+
if (isGlobal) return qoderConfigPath;
|
|
354
|
+
if (existsSync(join(cwd, ".qoder", "settings.json"))) {
|
|
355
|
+
return join(cwd, ".qoder", "settings.json");
|
|
356
|
+
}
|
|
357
|
+
return join(cwd, ".mcp.json");
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
// https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/
|
|
361
|
+
"qwen-code": {
|
|
362
|
+
name: "qwen-code",
|
|
363
|
+
displayName: "Qwen Code",
|
|
364
|
+
globalConfigPath: qwenCodeConfigPath,
|
|
365
|
+
projectConfigPath: ".qwen/settings.json",
|
|
366
|
+
configKey: "mcpServers",
|
|
367
|
+
format: "jsonc",
|
|
368
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
369
|
+
detectGlobalInstall: () => existsSync(qwenCodeConfigPath) || existsSync(
|
|
370
|
+
process.env.QWEN_CODE_HOME?.trim() || process.env.QWEN_HOME?.trim() || join(home, ".qwen")
|
|
371
|
+
),
|
|
372
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".qwen", "settings.json")) || existsSync(join(cwd, ".qwen")),
|
|
373
|
+
transformDialect: "qwen-code"
|
|
374
|
+
},
|
|
375
|
+
// https://docs.trae.ai/ide/add-mcp-servers?_lang=en
|
|
376
|
+
trae: {
|
|
377
|
+
name: "trae",
|
|
378
|
+
displayName: "Trae",
|
|
379
|
+
globalConfigPath: traeConfigPath,
|
|
380
|
+
projectConfigPath: ".trae/mcp.json",
|
|
381
|
+
configKey: "mcpServers",
|
|
382
|
+
format: "jsonc",
|
|
383
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
384
|
+
detectGlobalInstall: () => existsSync(traeConfigPath) || existsSync(join(home, ".trae", "mcp.json")) || existsSync(join(home, ".trae")) || existsSync(traePath) || existsSync(join(appSupport, "Trae")),
|
|
385
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".trae", "mcp.json")) || existsSync(join(cwd, ".trae")),
|
|
386
|
+
resolveConfigPath: ({ global: isGlobal, cwd }) => {
|
|
387
|
+
if (isGlobal) {
|
|
388
|
+
if (existsSync(join(home, ".trae", "mcp.json"))) {
|
|
389
|
+
return join(home, ".trae", "mcp.json");
|
|
390
|
+
}
|
|
391
|
+
return traeConfigPath;
|
|
392
|
+
}
|
|
393
|
+
return join(cwd, ".trae", "mcp.json");
|
|
394
|
+
},
|
|
395
|
+
transformDialect: "trae"
|
|
396
|
+
},
|
|
397
|
+
// https://code.visualstudio.com/raw/docs/agents/reference/mcp-configuration.md
|
|
398
|
+
vscode: {
|
|
399
|
+
name: "vscode",
|
|
400
|
+
displayName: "VS Code",
|
|
401
|
+
globalConfigPath: join(vscodePath, "mcp.json"),
|
|
402
|
+
projectConfigPath: ".vscode/mcp.json",
|
|
403
|
+
configKey: "servers",
|
|
404
|
+
format: "jsonc",
|
|
405
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
406
|
+
detectGlobalInstall: () => existsSync(join(vscodePath, "mcp.json")) || existsSync(vscodePath),
|
|
407
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".vscode", "mcp.json")),
|
|
408
|
+
transformDialect: "vscode"
|
|
409
|
+
},
|
|
410
|
+
// https://zed.dev/docs/ai/mcp.md
|
|
411
|
+
zed: {
|
|
412
|
+
name: "zed",
|
|
413
|
+
displayName: "Zed",
|
|
414
|
+
globalConfigPath: zedConfigPath,
|
|
415
|
+
projectConfigPath: ".zed/settings.json",
|
|
416
|
+
configKey: "context_servers",
|
|
417
|
+
format: "jsonc",
|
|
418
|
+
supportedTransports: ALL_TRANSPORTS,
|
|
419
|
+
detectGlobalInstall: () => existsSync(zedConfigPath) || existsSync(join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "zed")) || existsSync(join(appSupport, "Zed")),
|
|
420
|
+
detectProjectInstall: (cwd) => existsSync(join(cwd, ".zed", "settings.json")),
|
|
421
|
+
transformDialect: "zed"
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var mcpAgentAliases = {
|
|
425
|
+
agy: "antigravity-cli",
|
|
426
|
+
"amp-cli": "amp",
|
|
427
|
+
"amp-code": "amp",
|
|
428
|
+
ampcode: "amp",
|
|
429
|
+
auggie: "augment",
|
|
430
|
+
"augment-code": "augment",
|
|
431
|
+
augmentcode: "augment",
|
|
432
|
+
"cline-vscode": "cline",
|
|
433
|
+
gemini: "gemini-cli",
|
|
434
|
+
"github-copilot": "vscode",
|
|
435
|
+
"grok-cli": "grok",
|
|
436
|
+
kimi: "kimi-code-cli",
|
|
437
|
+
"kimi-cli": "kimi-code-cli",
|
|
438
|
+
"kimi-code": "kimi-code-cli",
|
|
439
|
+
"kiro-cli": "kiro",
|
|
440
|
+
"kiro-ide": "kiro",
|
|
441
|
+
"pi-agent": "pi",
|
|
442
|
+
"qoder-cli": "qoder",
|
|
443
|
+
qwen: "qwen-code",
|
|
444
|
+
"qwen-cli": "qwen-code",
|
|
445
|
+
qwencode: "qwen-code",
|
|
446
|
+
"trae-code": "trae",
|
|
447
|
+
traecode: "trae",
|
|
448
|
+
"trae-ide": "trae",
|
|
449
|
+
xai: "grok",
|
|
450
|
+
"xai-grok": "grok"
|
|
451
|
+
};
|
|
452
|
+
var getMcpAgentConfig = (agentType) => mcpAgents[agentType];
|
|
453
|
+
var getMcpAgentTypes = () => Object.values(mcpAgents).map((config) => config.name);
|
|
454
|
+
var isMcpAgentType = (value) => value in mcpAgents;
|
|
455
|
+
var resolveMcpAgentAlias = (input5) => {
|
|
456
|
+
if (isMcpAgentType(input5)) return input5;
|
|
457
|
+
return mcpAgentAliases[input5] ?? null;
|
|
458
|
+
};
|
|
459
|
+
var isMcpTransportSupported = (agent, transport) => agent.supportedTransports.includes(transport);
|
|
460
|
+
var detectProjectInstalledMcpAgents = (cwd) => getMcpAgentTypes().filter(
|
|
461
|
+
(type) => mcpAgents[type].detectProjectInstall ? mcpAgents[type].detectProjectInstall(cwd) : false
|
|
462
|
+
);
|
|
463
|
+
var detectGloballyInstalledMcpAgents = () => getMcpAgentTypes().filter((type) => mcpAgents[type].detectGlobalInstall());
|
|
464
|
+
var getMcpAgentsSupportingProjectScope = () => getMcpAgentTypes().filter((type) => Boolean(mcpAgents[type].projectConfigPath));
|
|
465
|
+
|
|
466
|
+
// src/constants.ts
|
|
467
|
+
var DEFAULT_REMOTE_TRANSPORT = "http";
|
|
468
|
+
var NPX_COMMAND = "npx";
|
|
469
|
+
var NPX_DASH_Y = "-y";
|
|
470
|
+
var GOOSE_TIMEOUT_SECONDS = 300;
|
|
471
|
+
var DEFAULT_JSON_INDENT_SPACES = 2;
|
|
472
|
+
var MCP_DEFAULT_SERVER_NAME = "mcp-server";
|
|
473
|
+
var GENERIC_HOST_PREFIXES = /* @__PURE__ */ new Set([
|
|
474
|
+
"mcp",
|
|
475
|
+
"api",
|
|
476
|
+
"app",
|
|
477
|
+
"www",
|
|
478
|
+
"server",
|
|
479
|
+
"servers",
|
|
480
|
+
"remote"
|
|
481
|
+
]);
|
|
482
|
+
var COMMON_TLD_LABELS = /* @__PURE__ */ new Set([
|
|
483
|
+
"com",
|
|
484
|
+
"org",
|
|
485
|
+
"net",
|
|
486
|
+
"io",
|
|
487
|
+
"dev",
|
|
488
|
+
"ai",
|
|
489
|
+
"tech",
|
|
490
|
+
"co",
|
|
491
|
+
"app",
|
|
492
|
+
"cloud",
|
|
493
|
+
"sh",
|
|
494
|
+
"run"
|
|
495
|
+
]);
|
|
496
|
+
var PACKAGE_NAME_PREFIX_STRIP = ["mcp-server-", "server-"];
|
|
497
|
+
var PACKAGE_NAME_SUFFIX_STRIP = ["-mcp-server", "-mcp"];
|
|
498
|
+
var KNOWN_COMMAND_RUNNERS = /* @__PURE__ */ new Set([
|
|
499
|
+
"npx",
|
|
500
|
+
"node",
|
|
501
|
+
"python",
|
|
502
|
+
"python3",
|
|
503
|
+
"uvx",
|
|
504
|
+
"bunx",
|
|
505
|
+
"deno"
|
|
506
|
+
]);
|
|
507
|
+
var SCRIPT_EXTENSION_REGEX = /\.(?:js|ts|mjs|cjs|py|sh|rb|go)$/i;
|
|
508
|
+
|
|
509
|
+
// src/build-server-config.ts
|
|
510
|
+
var buildMcpServerConfig = (parsed, options = {}) => {
|
|
511
|
+
if (parsed.type === "remote") {
|
|
512
|
+
const config2 = {
|
|
513
|
+
type: options.transport ?? DEFAULT_REMOTE_TRANSPORT,
|
|
514
|
+
url: parsed.value
|
|
515
|
+
};
|
|
516
|
+
if (options.headers && Object.keys(options.headers).length > 0) {
|
|
517
|
+
config2.headers = options.headers;
|
|
518
|
+
}
|
|
519
|
+
return config2;
|
|
520
|
+
}
|
|
521
|
+
if (parsed.type === "command") {
|
|
522
|
+
const parts = parsed.value.split(/\s+/);
|
|
523
|
+
const command = parts[0] ?? "";
|
|
524
|
+
const args = parts.slice(1);
|
|
525
|
+
if (options.args && options.args.length > 0) {
|
|
526
|
+
args.push(...options.args);
|
|
527
|
+
}
|
|
528
|
+
const config2 = { command, args };
|
|
529
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
530
|
+
config2.env = options.env;
|
|
531
|
+
}
|
|
532
|
+
return config2;
|
|
533
|
+
}
|
|
534
|
+
const packageArgs = [NPX_DASH_Y, parsed.value];
|
|
535
|
+
if (options.args && options.args.length > 0) {
|
|
536
|
+
packageArgs.push(...options.args);
|
|
537
|
+
}
|
|
538
|
+
const config = {
|
|
539
|
+
command: NPX_COMMAND,
|
|
540
|
+
args: packageArgs
|
|
541
|
+
};
|
|
542
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
543
|
+
config.env = options.env;
|
|
544
|
+
}
|
|
545
|
+
return config;
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// src/parse-server-config.ts
|
|
549
|
+
var isRemoteServerConfig = (config) => typeof config.url === "string" && config.url.length > 0;
|
|
550
|
+
var isStdioServerConfig = (config) => typeof config.command === "string" && config.command.length > 0;
|
|
551
|
+
var parseServerConfig = (raw) => {
|
|
552
|
+
if (!raw || typeof raw !== "object") return {};
|
|
553
|
+
const data = raw;
|
|
554
|
+
const rawUrl = typeof data.url === "string" && data.url.trim().length > 0 ? data.url.trim() : void 0;
|
|
555
|
+
const rawHttpUrl = typeof data.httpUrl === "string" && data.httpUrl.trim().length > 0 ? data.httpUrl.trim() : void 0;
|
|
556
|
+
const remoteUrl = rawHttpUrl ?? rawUrl;
|
|
557
|
+
if (remoteUrl) {
|
|
558
|
+
const transport = data.type === "sse" || data.transport === "sse" ? "sse" : "http";
|
|
559
|
+
const headers = data.headers && typeof data.headers === "object" ? data.headers : void 0;
|
|
560
|
+
return {
|
|
561
|
+
type: transport,
|
|
562
|
+
url: remoteUrl,
|
|
563
|
+
headers
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (typeof data.command === "string" && data.command.trim().length > 0) {
|
|
567
|
+
const args = Array.isArray(data.args) ? data.args.filter((item) => typeof item === "string") : void 0;
|
|
568
|
+
const env = data.env && typeof data.env === "object" ? data.env : void 0;
|
|
569
|
+
return {
|
|
570
|
+
command: data.command.trim(),
|
|
571
|
+
args,
|
|
572
|
+
env
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
return {};
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// src/utils/is-plain-object.ts
|
|
579
|
+
var isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
580
|
+
|
|
581
|
+
// src/utils/get-nested-value.ts
|
|
582
|
+
var getNestedValue = (source, dottedKey) => {
|
|
583
|
+
if (!source) return void 0;
|
|
584
|
+
if (dottedKey in source) return source[dottedKey];
|
|
585
|
+
const segments = dottedKey.split(".");
|
|
586
|
+
let cursor = source;
|
|
587
|
+
for (const segment of segments) {
|
|
588
|
+
if (!isPlainObject(cursor)) return void 0;
|
|
589
|
+
cursor = cursor[segment];
|
|
590
|
+
}
|
|
591
|
+
return cursor;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// src/formats/json.ts
|
|
595
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
596
|
+
import { applyEdits, modify, parse as parseJsonc } from "jsonc-parser";
|
|
597
|
+
|
|
598
|
+
// src/utils/ensure-parent-dir.ts
|
|
599
|
+
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
600
|
+
import { dirname } from "path";
|
|
601
|
+
var ensureParentDir = (filePath) => {
|
|
602
|
+
const parentDir = dirname(filePath);
|
|
603
|
+
if (!existsSync2(parentDir)) mkdirSync(parentDir, { recursive: true });
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// src/utils/set-nested-value.ts
|
|
607
|
+
var DANGEROUS_KEY_SEGMENTS = /* @__PURE__ */ new Set([
|
|
608
|
+
"__proto__",
|
|
609
|
+
"prototype",
|
|
610
|
+
"constructor"
|
|
611
|
+
]);
|
|
612
|
+
var assertSafeSegment = (segment) => {
|
|
613
|
+
if (DANGEROUS_KEY_SEGMENTS.has(segment)) {
|
|
614
|
+
throw new Error(`Refusing to write to unsafe key segment "${segment}"`);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
var setNestedValue = (target, dottedKey, value) => {
|
|
618
|
+
if (dottedKey in target) {
|
|
619
|
+
assertSafeSegment(dottedKey);
|
|
620
|
+
target[dottedKey] = value;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const segments = dottedKey.split(".");
|
|
624
|
+
let cursor = target;
|
|
625
|
+
for (let segmentIndex = 0; segmentIndex < segments.length - 1; segmentIndex += 1) {
|
|
626
|
+
const segment = segments[segmentIndex];
|
|
627
|
+
assertSafeSegment(segment);
|
|
628
|
+
const existing = cursor[segment];
|
|
629
|
+
if (isPlainObject(existing)) {
|
|
630
|
+
cursor = existing;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
const next = {};
|
|
634
|
+
cursor[segment] = next;
|
|
635
|
+
cursor = next;
|
|
636
|
+
}
|
|
637
|
+
const finalSegment = segments[segments.length - 1];
|
|
638
|
+
assertSafeSegment(finalSegment);
|
|
639
|
+
cursor[finalSegment] = value;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/utils/walk-nested-object.ts
|
|
643
|
+
var walkNestedObject = (root, segments) => {
|
|
644
|
+
let cursor = root;
|
|
645
|
+
for (const segment of segments) {
|
|
646
|
+
if (!isPlainObject(cursor)) return void 0;
|
|
647
|
+
cursor = cursor[segment];
|
|
648
|
+
}
|
|
649
|
+
return isPlainObject(cursor) ? cursor : void 0;
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/formats/json.ts
|
|
653
|
+
var JSONC_FORMATTING = {
|
|
654
|
+
insertSpaces: true,
|
|
655
|
+
tabSize: DEFAULT_JSON_INDENT_SPACES,
|
|
656
|
+
eol: "\n"
|
|
657
|
+
};
|
|
658
|
+
var readFileOrEmpty = (filePath) => existsSync3(filePath) ? readFileSync(filePath, "utf-8") : "";
|
|
659
|
+
var writeWithTrailingNewline = (filePath, contents) => {
|
|
660
|
+
writeFileSync(filePath, contents.endsWith("\n") ? contents : `${contents}
|
|
661
|
+
`, "utf-8");
|
|
662
|
+
};
|
|
663
|
+
var readJsoncConfig = (filePath) => {
|
|
664
|
+
const raw = readFileOrEmpty(filePath);
|
|
665
|
+
if (!raw.trim()) return {};
|
|
666
|
+
const parsed = parseJsonc(raw);
|
|
667
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
668
|
+
};
|
|
669
|
+
var resolvePathPrefix = (root, dottedKey) => {
|
|
670
|
+
if (dottedKey in root) return [dottedKey];
|
|
671
|
+
const segments = dottedKey.split(".");
|
|
672
|
+
if (segments.length > 1 && walkNestedObject(root, segments)) {
|
|
673
|
+
return segments;
|
|
674
|
+
}
|
|
675
|
+
return [dottedKey];
|
|
676
|
+
};
|
|
677
|
+
var setJsoncNestedValue = (filePath, dottedKey, serverName, serverConfig) => {
|
|
678
|
+
ensureParentDir(filePath);
|
|
679
|
+
const existingText = readFileOrEmpty(filePath);
|
|
680
|
+
const sourceText = existingText.trim() ? existingText : "{}";
|
|
681
|
+
const existing = readJsoncConfig(filePath);
|
|
682
|
+
const pathPrefix = resolvePathPrefix(existing, dottedKey);
|
|
683
|
+
const path = [...pathPrefix, serverName];
|
|
684
|
+
const edits = modify(sourceText, path, serverConfig, {
|
|
685
|
+
formattingOptions: JSONC_FORMATTING
|
|
686
|
+
});
|
|
687
|
+
writeWithTrailingNewline(filePath, applyEdits(sourceText, edits));
|
|
688
|
+
};
|
|
689
|
+
var writeJsonConfigAtKey = (filePath, dottedKey, serverName, serverConfig) => {
|
|
690
|
+
ensureParentDir(filePath);
|
|
691
|
+
const existing = readJsoncConfig(filePath);
|
|
692
|
+
const pathPrefix = resolvePathPrefix(existing, dottedKey);
|
|
693
|
+
const existingServers = walkNestedObject(existing, pathPrefix);
|
|
694
|
+
const servers = existingServers ? { ...existingServers } : {};
|
|
695
|
+
servers[serverName] = serverConfig;
|
|
696
|
+
if (pathPrefix.length === 1) {
|
|
697
|
+
existing[pathPrefix[0]] = servers;
|
|
698
|
+
} else {
|
|
699
|
+
setNestedValue(existing, dottedKey, servers);
|
|
700
|
+
}
|
|
701
|
+
writeFileSync(
|
|
702
|
+
filePath,
|
|
703
|
+
`${JSON.stringify(existing, null, DEFAULT_JSON_INDENT_SPACES)}
|
|
704
|
+
`,
|
|
705
|
+
"utf-8"
|
|
706
|
+
);
|
|
707
|
+
};
|
|
708
|
+
var removeJsoncConfigKey = (filePath, dottedKey, serverName) => {
|
|
709
|
+
if (!existsSync3(filePath)) return false;
|
|
710
|
+
const sourceText = readFileSync(filePath, "utf-8");
|
|
711
|
+
if (!sourceText.trim()) return false;
|
|
712
|
+
const existing = readJsoncConfig(filePath);
|
|
713
|
+
const pathPrefix = resolvePathPrefix(existing, dottedKey);
|
|
714
|
+
const parentObject = walkNestedObject(existing, pathPrefix);
|
|
715
|
+
if (!parentObject || !(serverName in parentObject)) return false;
|
|
716
|
+
const path = [...pathPrefix, serverName];
|
|
717
|
+
const edits = modify(sourceText, path, void 0, {
|
|
718
|
+
formattingOptions: JSONC_FORMATTING
|
|
719
|
+
});
|
|
720
|
+
if (edits.length === 0) return false;
|
|
721
|
+
writeWithTrailingNewline(filePath, applyEdits(sourceText, edits));
|
|
722
|
+
return true;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// src/formats/toml.ts
|
|
726
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
727
|
+
import TOML from "@iarna/toml";
|
|
728
|
+
|
|
729
|
+
// src/utils/delete-nested-value.ts
|
|
730
|
+
var DANGEROUS_KEY_SEGMENTS2 = /* @__PURE__ */ new Set([
|
|
731
|
+
"__proto__",
|
|
732
|
+
"prototype",
|
|
733
|
+
"constructor"
|
|
734
|
+
]);
|
|
735
|
+
var deleteNestedValue = (target, dottedKey) => {
|
|
736
|
+
if (!target) return false;
|
|
737
|
+
if (dottedKey in target) {
|
|
738
|
+
if (DANGEROUS_KEY_SEGMENTS2.has(dottedKey)) return false;
|
|
739
|
+
delete target[dottedKey];
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
const segments = dottedKey.split(".");
|
|
743
|
+
if (segments.some((segment) => DANGEROUS_KEY_SEGMENTS2.has(segment))) return false;
|
|
744
|
+
let cursor = target;
|
|
745
|
+
for (let segmentIndex = 0; segmentIndex < segments.length - 1; segmentIndex += 1) {
|
|
746
|
+
const segment = segments[segmentIndex];
|
|
747
|
+
const existing = cursor[segment];
|
|
748
|
+
if (!isPlainObject(existing)) return false;
|
|
749
|
+
cursor = existing;
|
|
750
|
+
}
|
|
751
|
+
const lastSegment = segments[segments.length - 1];
|
|
752
|
+
if (!(lastSegment in cursor)) return false;
|
|
753
|
+
delete cursor[lastSegment];
|
|
754
|
+
return true;
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/formats/toml.ts
|
|
758
|
+
var toTomlJsonMap = (value) => JSON.parse(JSON.stringify(value));
|
|
759
|
+
var readTomlConfig = (filePath) => {
|
|
760
|
+
if (!existsSync4(filePath)) return {};
|
|
761
|
+
const raw = readFileSync2(filePath, "utf-8");
|
|
762
|
+
if (!raw.trim()) return {};
|
|
763
|
+
const parsed = TOML.parse(raw);
|
|
764
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
765
|
+
};
|
|
766
|
+
var writeTomlConfigAtKey = (filePath, dottedKey, serverName, serverConfig) => {
|
|
767
|
+
ensureParentDir(filePath);
|
|
768
|
+
const existing = readTomlConfig(filePath);
|
|
769
|
+
const existingServers = walkNestedObject(existing, dottedKey.split("."));
|
|
770
|
+
const servers = existingServers ? { ...existingServers } : {};
|
|
771
|
+
servers[serverName] = serverConfig;
|
|
772
|
+
setNestedValue(existing, dottedKey, servers);
|
|
773
|
+
writeFileSync2(filePath, TOML.stringify(toTomlJsonMap(existing)), "utf-8");
|
|
774
|
+
};
|
|
775
|
+
var removeTomlConfigKey = (filePath, dottedKey, serverName) => {
|
|
776
|
+
if (!existsSync4(filePath)) return false;
|
|
777
|
+
const existing = readTomlConfig(filePath);
|
|
778
|
+
const didRemove = deleteNestedValue(existing, `${dottedKey}.${serverName}`);
|
|
779
|
+
if (didRemove) writeFileSync2(filePath, TOML.stringify(toTomlJsonMap(existing)), "utf-8");
|
|
780
|
+
return didRemove;
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// src/formats/yaml.ts
|
|
784
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
785
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
786
|
+
var readYamlConfig = (filePath) => {
|
|
787
|
+
if (!existsSync5(filePath)) return {};
|
|
788
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
789
|
+
if (!raw.trim()) return {};
|
|
790
|
+
const parsed = parseYaml(raw);
|
|
791
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
792
|
+
};
|
|
793
|
+
var writeYamlConfigAtKey = (filePath, dottedKey, serverName, serverConfig) => {
|
|
794
|
+
ensureParentDir(filePath);
|
|
795
|
+
const existing = readYamlConfig(filePath);
|
|
796
|
+
const existingServers = walkNestedObject(existing, dottedKey.split("."));
|
|
797
|
+
const servers = existingServers ? { ...existingServers } : {};
|
|
798
|
+
servers[serverName] = serverConfig;
|
|
799
|
+
setNestedValue(existing, dottedKey, servers);
|
|
800
|
+
writeFileSync3(filePath, stringifyYaml(existing), "utf-8");
|
|
801
|
+
};
|
|
802
|
+
var removeYamlConfigKey = (filePath, dottedKey, serverName) => {
|
|
803
|
+
if (!existsSync5(filePath)) return false;
|
|
804
|
+
const existing = readYamlConfig(filePath);
|
|
805
|
+
const didRemove = deleteNestedValue(existing, `${dottedKey}.${serverName}`);
|
|
806
|
+
if (didRemove) writeFileSync3(filePath, stringifyYaml(existing), "utf-8");
|
|
807
|
+
return didRemove;
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/formats/index.ts
|
|
811
|
+
var readConfigFile = (filePath, format) => {
|
|
812
|
+
switch (format) {
|
|
813
|
+
case "json":
|
|
814
|
+
case "jsonc":
|
|
815
|
+
return readJsoncConfig(filePath);
|
|
816
|
+
case "yaml":
|
|
817
|
+
return readYamlConfig(filePath);
|
|
818
|
+
case "toml":
|
|
819
|
+
return readTomlConfig(filePath);
|
|
820
|
+
default:
|
|
821
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
var writeServerToConfigFile = (filePath, format, dottedKey, serverName, serverConfig) => {
|
|
825
|
+
switch (format) {
|
|
826
|
+
case "jsonc":
|
|
827
|
+
setJsoncNestedValue(filePath, dottedKey, serverName, serverConfig);
|
|
828
|
+
return;
|
|
829
|
+
case "json":
|
|
830
|
+
writeJsonConfigAtKey(filePath, dottedKey, serverName, serverConfig);
|
|
831
|
+
return;
|
|
832
|
+
case "yaml":
|
|
833
|
+
writeYamlConfigAtKey(filePath, dottedKey, serverName, serverConfig);
|
|
834
|
+
return;
|
|
835
|
+
case "toml":
|
|
836
|
+
writeTomlConfigAtKey(filePath, dottedKey, serverName, serverConfig);
|
|
837
|
+
return;
|
|
838
|
+
default:
|
|
839
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var removeServerFromConfigFile = (filePath, format, dottedKey, serverName) => {
|
|
843
|
+
switch (format) {
|
|
844
|
+
case "json":
|
|
845
|
+
case "jsonc":
|
|
846
|
+
return removeJsoncConfigKey(filePath, dottedKey, serverName);
|
|
847
|
+
case "yaml":
|
|
848
|
+
return removeYamlConfigKey(filePath, dottedKey, serverName);
|
|
849
|
+
case "toml":
|
|
850
|
+
return removeTomlConfigKey(filePath, dottedKey, serverName);
|
|
851
|
+
default:
|
|
852
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
var listServersInConfigFile = (filePath, format, dottedKey) => {
|
|
856
|
+
const config = readConfigFile(filePath, format);
|
|
857
|
+
const entries = getNestedValue(config, dottedKey);
|
|
858
|
+
return isPlainObject(entries) ? entries : {};
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// src/config-store.ts
|
|
862
|
+
import { existsSync as existsSync6 } from "fs";
|
|
863
|
+
|
|
864
|
+
// src/resolve-config-target.ts
|
|
865
|
+
import { join as join2 } from "path";
|
|
866
|
+
var resolveMcpConfigTarget = (agent, options = {}) => {
|
|
867
|
+
const isGlobal = options.global ?? false;
|
|
868
|
+
const cwd = options.cwd ?? process.cwd();
|
|
869
|
+
const configPath = agent.resolveConfigPath ? agent.resolveConfigPath({ global: isGlobal, cwd }) : !isGlobal && agent.projectConfigPath ? join2(cwd, agent.projectConfigPath) : agent.globalConfigPath;
|
|
870
|
+
const configKey = !isGlobal && agent.projectConfigKey ? agent.projectConfigKey : agent.configKey;
|
|
871
|
+
return { configPath, configKey };
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// src/config-store.ts
|
|
875
|
+
var FsConfigStoreAdapter = class {
|
|
876
|
+
exists(filePath) {
|
|
877
|
+
return existsSync6(filePath);
|
|
878
|
+
}
|
|
879
|
+
read(target) {
|
|
880
|
+
return readConfigFile(target.filePath, target.format);
|
|
881
|
+
}
|
|
882
|
+
writeServer(target, serverName, serverConfig) {
|
|
883
|
+
if (!target.dottedKey) {
|
|
884
|
+
throw new Error(`Cannot write server: missing dottedKey for ${target.filePath}`);
|
|
885
|
+
}
|
|
886
|
+
writeServerToConfigFile(
|
|
887
|
+
target.filePath,
|
|
888
|
+
target.format,
|
|
889
|
+
target.dottedKey,
|
|
890
|
+
serverName,
|
|
891
|
+
serverConfig
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
removeServer(target, serverName) {
|
|
895
|
+
if (!target.dottedKey) return false;
|
|
896
|
+
return removeServerFromConfigFile(
|
|
897
|
+
target.filePath,
|
|
898
|
+
target.format,
|
|
899
|
+
target.dottedKey,
|
|
900
|
+
serverName
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
listServers(target) {
|
|
904
|
+
if (!target.dottedKey) return {};
|
|
905
|
+
return listServersInConfigFile(target.filePath, target.format, target.dottedKey);
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
var AgentConfigStore = class {
|
|
909
|
+
constructor(adapter = new FsConfigStoreAdapter()) {
|
|
910
|
+
this.adapter = adapter;
|
|
911
|
+
}
|
|
912
|
+
adapter;
|
|
913
|
+
getAdapter() {
|
|
914
|
+
return this.adapter;
|
|
915
|
+
}
|
|
916
|
+
resolveTarget(agent, options = {}) {
|
|
917
|
+
const agentConfig = typeof agent === "string" ? getMcpAgentConfig(agent) : agent;
|
|
918
|
+
const target = resolveMcpConfigTarget(agentConfig, options);
|
|
919
|
+
return { agent: agentConfig, target };
|
|
920
|
+
}
|
|
921
|
+
resolveDescriptor(agent, options = {}) {
|
|
922
|
+
const { agent: agentConfig, target } = this.resolveTarget(agent, options);
|
|
923
|
+
return {
|
|
924
|
+
filePath: target.configPath,
|
|
925
|
+
format: agentConfig.format,
|
|
926
|
+
dottedKey: target.configKey
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
writeServer(agent, serverName, serverConfig, options = {}) {
|
|
930
|
+
const descriptor = this.resolveDescriptor(agent, options);
|
|
931
|
+
this.adapter.writeServer(descriptor, serverName, serverConfig);
|
|
932
|
+
return { path: descriptor.filePath };
|
|
933
|
+
}
|
|
934
|
+
removeServer(agent, serverName, options = {}) {
|
|
935
|
+
const descriptor = this.resolveDescriptor(agent, options);
|
|
936
|
+
if (!this.adapter.exists(descriptor.filePath)) {
|
|
937
|
+
return { path: descriptor.filePath, removed: false };
|
|
938
|
+
}
|
|
939
|
+
const removed = this.adapter.removeServer(descriptor, serverName);
|
|
940
|
+
return { path: descriptor.filePath, removed };
|
|
941
|
+
}
|
|
942
|
+
listServers(agent, options = {}) {
|
|
943
|
+
const descriptor = this.resolveDescriptor(agent, options);
|
|
944
|
+
if (!this.adapter.exists(descriptor.filePath)) {
|
|
945
|
+
return { path: descriptor.filePath, exists: false, servers: {} };
|
|
946
|
+
}
|
|
947
|
+
const servers = this.adapter.listServers(descriptor);
|
|
948
|
+
return { path: descriptor.filePath, exists: true, servers };
|
|
949
|
+
}
|
|
950
|
+
read(agent, options = {}) {
|
|
951
|
+
const descriptor = this.resolveDescriptor(agent, options);
|
|
952
|
+
if (!this.adapter.exists(descriptor.filePath)) {
|
|
953
|
+
return {};
|
|
954
|
+
}
|
|
955
|
+
return this.adapter.read(descriptor);
|
|
956
|
+
}
|
|
957
|
+
readServer(agent, serverName, options = {}) {
|
|
958
|
+
const { exists, servers } = this.listServers(agent, options);
|
|
959
|
+
if (!exists) return void 0;
|
|
960
|
+
return servers[serverName];
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
var agentConfigStore = new AgentConfigStore();
|
|
964
|
+
|
|
965
|
+
// src/utils/to-error-message.ts
|
|
966
|
+
var toErrorMessage = (error, fallback = "Unknown error") => error instanceof Error ? error.message : fallback;
|
|
967
|
+
|
|
968
|
+
// src/transforms/index.ts
|
|
969
|
+
var DIALECT_PRESETS = {
|
|
970
|
+
vscode: {
|
|
971
|
+
stdioTransport: "type-stdio",
|
|
972
|
+
remoteTransport: "type-http-sse"
|
|
973
|
+
},
|
|
974
|
+
augment: {
|
|
975
|
+
stdioTransport: "none",
|
|
976
|
+
remoteTransport: "type-http-sse"
|
|
977
|
+
},
|
|
978
|
+
amp: {
|
|
979
|
+
stdioTransport: "none",
|
|
980
|
+
remoteTransport: "none"
|
|
981
|
+
},
|
|
982
|
+
trae: {
|
|
983
|
+
stdioTransport: "none",
|
|
984
|
+
remoteTransport: "sse-only-type"
|
|
985
|
+
},
|
|
986
|
+
grok: {
|
|
987
|
+
stdioTransport: "none",
|
|
988
|
+
remoteTransport: "sse-only-type"
|
|
989
|
+
},
|
|
990
|
+
cline: {
|
|
991
|
+
stdioTransport: "none",
|
|
992
|
+
remoteTransport: "streamableHttp"
|
|
993
|
+
},
|
|
994
|
+
goose: {
|
|
995
|
+
stdioTransport: "type-stdio",
|
|
996
|
+
remoteTransport: "streamable_http",
|
|
997
|
+
commandField: "cmd",
|
|
998
|
+
envField: "envs",
|
|
999
|
+
urlField: "uri",
|
|
1000
|
+
defaultEnvEmpty: true,
|
|
1001
|
+
defaultHeadersEmpty: true,
|
|
1002
|
+
includeServerName: true,
|
|
1003
|
+
timeoutSeconds: GOOSE_TIMEOUT_SECONDS,
|
|
1004
|
+
extraFields: {
|
|
1005
|
+
description: "",
|
|
1006
|
+
enabled: true
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
"kimi-code": {
|
|
1010
|
+
stdioTransport: "none",
|
|
1011
|
+
remoteTransport: "sse-only-transport"
|
|
1012
|
+
},
|
|
1013
|
+
kiro: {
|
|
1014
|
+
stdioTransport: "none",
|
|
1015
|
+
remoteTransport: "none"
|
|
1016
|
+
},
|
|
1017
|
+
opencode: {
|
|
1018
|
+
stdioTransport: "type-local",
|
|
1019
|
+
remoteTransport: "remote-type",
|
|
1020
|
+
commandArray: true,
|
|
1021
|
+
envField: "environment",
|
|
1022
|
+
defaultEnvEmpty: true,
|
|
1023
|
+
extraFields: {
|
|
1024
|
+
enabled: true
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
pi: {
|
|
1028
|
+
stdioTransport: "transport-stdio",
|
|
1029
|
+
remoteTransport: "streamable-http"
|
|
1030
|
+
},
|
|
1031
|
+
"qwen-code": {
|
|
1032
|
+
stdioTransport: "none",
|
|
1033
|
+
remoteTransport: "qwen"
|
|
1034
|
+
},
|
|
1035
|
+
zed: {
|
|
1036
|
+
stdioTransport: "none",
|
|
1037
|
+
remoteTransport: "none",
|
|
1038
|
+
defaultEnvEmpty: true
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
var resolveDialectOptions = (dialect) => {
|
|
1042
|
+
if (typeof dialect === "string") {
|
|
1043
|
+
const preset = DIALECT_PRESETS[dialect];
|
|
1044
|
+
if (!preset) {
|
|
1045
|
+
throw new Error(`Unknown server config dialect: "${dialect}"`);
|
|
1046
|
+
}
|
|
1047
|
+
return preset;
|
|
1048
|
+
}
|
|
1049
|
+
return dialect;
|
|
1050
|
+
};
|
|
1051
|
+
var transformRemoteConfig = (serverName, config, options) => {
|
|
1052
|
+
const result = {};
|
|
1053
|
+
if (options.includeServerName) {
|
|
1054
|
+
result.name = serverName;
|
|
1055
|
+
}
|
|
1056
|
+
if (options.extraFields) {
|
|
1057
|
+
Object.assign(result, options.extraFields);
|
|
1058
|
+
}
|
|
1059
|
+
const remoteTransport = options.remoteTransport ?? "type-http-sse";
|
|
1060
|
+
const urlField = options.urlField ?? "url";
|
|
1061
|
+
switch (remoteTransport) {
|
|
1062
|
+
case "type-http-sse":
|
|
1063
|
+
result.type = config.type || "http";
|
|
1064
|
+
result[urlField] = config.url;
|
|
1065
|
+
break;
|
|
1066
|
+
case "sse-only-type":
|
|
1067
|
+
result[urlField] = config.url;
|
|
1068
|
+
if (config.type === "sse") {
|
|
1069
|
+
result.type = "sse";
|
|
1070
|
+
}
|
|
1071
|
+
break;
|
|
1072
|
+
case "sse-only-transport":
|
|
1073
|
+
result[urlField] = config.url;
|
|
1074
|
+
if (config.type === "sse") {
|
|
1075
|
+
result.transport = "sse";
|
|
1076
|
+
}
|
|
1077
|
+
break;
|
|
1078
|
+
case "none":
|
|
1079
|
+
result[urlField] = config.url;
|
|
1080
|
+
break;
|
|
1081
|
+
case "streamableHttp":
|
|
1082
|
+
result.type = config.type === "sse" ? "sse" : "streamableHttp";
|
|
1083
|
+
result[urlField] = config.url;
|
|
1084
|
+
break;
|
|
1085
|
+
case "streamable-http":
|
|
1086
|
+
result.transport = config.type === "sse" ? "sse" : "streamable-http";
|
|
1087
|
+
result[urlField] = config.url;
|
|
1088
|
+
break;
|
|
1089
|
+
case "streamable_http":
|
|
1090
|
+
result.type = config.type === "sse" ? "sse" : "streamable_http";
|
|
1091
|
+
result[urlField] = config.url;
|
|
1092
|
+
break;
|
|
1093
|
+
case "remote-type":
|
|
1094
|
+
result.type = "remote";
|
|
1095
|
+
result[urlField] = config.url;
|
|
1096
|
+
break;
|
|
1097
|
+
case "qwen":
|
|
1098
|
+
if (config.type === "sse") {
|
|
1099
|
+
result.url = config.url;
|
|
1100
|
+
} else {
|
|
1101
|
+
result.httpUrl = config.url;
|
|
1102
|
+
}
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
const hasHeaders = config.headers && Object.keys(config.headers).length > 0;
|
|
1106
|
+
if (hasHeaders) {
|
|
1107
|
+
result.headers = config.headers;
|
|
1108
|
+
} else if (options.defaultHeadersEmpty) {
|
|
1109
|
+
result.headers = {};
|
|
1110
|
+
}
|
|
1111
|
+
if (options.timeoutSeconds !== void 0) {
|
|
1112
|
+
result.timeout = options.timeoutSeconds;
|
|
1113
|
+
}
|
|
1114
|
+
return result;
|
|
1115
|
+
};
|
|
1116
|
+
var transformStdioConfig = (serverName, config, options) => {
|
|
1117
|
+
const result = {};
|
|
1118
|
+
if (options.includeServerName) {
|
|
1119
|
+
result.name = serverName;
|
|
1120
|
+
}
|
|
1121
|
+
if (options.extraFields) {
|
|
1122
|
+
Object.assign(result, options.extraFields);
|
|
1123
|
+
}
|
|
1124
|
+
const stdioTransport = options.stdioTransport ?? "none";
|
|
1125
|
+
const commandField = options.commandField ?? "command";
|
|
1126
|
+
const argsField = options.argsField ?? "args";
|
|
1127
|
+
const envField = options.envField ?? "env";
|
|
1128
|
+
switch (stdioTransport) {
|
|
1129
|
+
case "type-stdio":
|
|
1130
|
+
result.type = "stdio";
|
|
1131
|
+
break;
|
|
1132
|
+
case "transport-stdio":
|
|
1133
|
+
result.transport = "stdio";
|
|
1134
|
+
break;
|
|
1135
|
+
case "type-local":
|
|
1136
|
+
result.type = "local";
|
|
1137
|
+
break;
|
|
1138
|
+
case "none":
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
if (options.commandArray) {
|
|
1142
|
+
result[commandField] = [config.command, ...config.args || []];
|
|
1143
|
+
} else {
|
|
1144
|
+
result[commandField] = config.command;
|
|
1145
|
+
result[argsField] = config.args || [];
|
|
1146
|
+
}
|
|
1147
|
+
const hasEnv = config.env && Object.keys(config.env).length > 0;
|
|
1148
|
+
if (hasEnv) {
|
|
1149
|
+
result[envField] = config.env;
|
|
1150
|
+
} else if (options.defaultEnvEmpty) {
|
|
1151
|
+
result[envField] = {};
|
|
1152
|
+
}
|
|
1153
|
+
if (options.timeoutSeconds !== void 0) {
|
|
1154
|
+
result.timeout = options.timeoutSeconds;
|
|
1155
|
+
}
|
|
1156
|
+
return result;
|
|
1157
|
+
};
|
|
1158
|
+
var transformServerConfig = (serverName, config, dialect, _context) => {
|
|
1159
|
+
const options = resolveDialectOptions(dialect);
|
|
1160
|
+
if (config.url) {
|
|
1161
|
+
return transformRemoteConfig(serverName, config, options);
|
|
1162
|
+
}
|
|
1163
|
+
return transformStdioConfig(serverName, config, options);
|
|
1164
|
+
};
|
|
1165
|
+
var createAgentTransform = (dialect) => {
|
|
1166
|
+
return (serverName, config, context) => transformServerConfig(serverName, config, dialect, context);
|
|
1167
|
+
};
|
|
1168
|
+
var transformServerConfigForAgent = (agent, serverName, config, context = { global: false }) => {
|
|
1169
|
+
if (agent.transformConfig) {
|
|
1170
|
+
return agent.transformConfig(serverName, config, context);
|
|
1171
|
+
}
|
|
1172
|
+
if (agent.transformDialect) {
|
|
1173
|
+
return transformServerConfig(serverName, config, agent.transformDialect, context);
|
|
1174
|
+
}
|
|
1175
|
+
return config;
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
// src/installer.ts
|
|
1179
|
+
var installMcpServerForAgent = (serverName, serverConfig, agentType, options = {}) => {
|
|
1180
|
+
const agent = getMcpAgentConfig(agentType);
|
|
1181
|
+
const isGlobal = options.global ?? false;
|
|
1182
|
+
const { target } = agentConfigStore.resolveTarget(agent, options);
|
|
1183
|
+
try {
|
|
1184
|
+
const transformed = transformServerConfigForAgent(agent, serverName, serverConfig, {
|
|
1185
|
+
global: isGlobal
|
|
1186
|
+
});
|
|
1187
|
+
agentConfigStore.writeServer(agent, serverName, transformed, options);
|
|
1188
|
+
return { agent: agentType, success: true, path: target.configPath };
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
return {
|
|
1191
|
+
agent: agentType,
|
|
1192
|
+
success: false,
|
|
1193
|
+
path: target.configPath,
|
|
1194
|
+
error: toErrorMessage(error)
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
var installMcpServerForAgents = (serverName, serverConfig, agentTypes, options = {}) => agentTypes.map(
|
|
1199
|
+
(agentType) => installMcpServerForAgent(serverName, serverConfig, agentType, options)
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
// src/utils/parse-mcp-agent-list.ts
|
|
1203
|
+
var parseMcpAgentList = (input5) => {
|
|
1204
|
+
if (!input5 || input5.length === 0) return void 0;
|
|
1205
|
+
if (input5.includes("*")) return getMcpAgentTypes();
|
|
1206
|
+
const resolved = [];
|
|
1207
|
+
for (const value of input5) {
|
|
1208
|
+
const agentType = resolveMcpAgentAlias(value);
|
|
1209
|
+
if (!agentType) throw new Error(`Unknown MCP agent "${value}"`);
|
|
1210
|
+
resolved.push(agentType);
|
|
1211
|
+
}
|
|
1212
|
+
return resolved;
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// src/resolve-target-agents.ts
|
|
1216
|
+
var normalizeRequestedAgents = (input5) => {
|
|
1217
|
+
if (!input5 || input5.length === 0) return void 0;
|
|
1218
|
+
const rawList = [...input5];
|
|
1219
|
+
if (rawList.every((item) => isMcpAgentType(item))) {
|
|
1220
|
+
return rawList;
|
|
1221
|
+
}
|
|
1222
|
+
return parseMcpAgentList(rawList);
|
|
1223
|
+
};
|
|
1224
|
+
var resolveTargetAgents = (query = {}) => {
|
|
1225
|
+
const cwd = query.cwd ?? process.cwd();
|
|
1226
|
+
const isGlobal = query.global ?? false;
|
|
1227
|
+
let explicitAgents = normalizeRequestedAgents(query.requested);
|
|
1228
|
+
if (query.all) {
|
|
1229
|
+
explicitAgents = getMcpAgentTypes();
|
|
1230
|
+
}
|
|
1231
|
+
const isDetected = !explicitAgents || explicitAgents.length === 0;
|
|
1232
|
+
const detected = isGlobal ? detectGloballyInstalledMcpAgents() : detectProjectInstalledMcpAgents(cwd);
|
|
1233
|
+
const candidateAgents = isDetected ? detected : explicitAgents ?? [];
|
|
1234
|
+
const allAgents = candidateAgents.filter(
|
|
1235
|
+
(type, index) => candidateAgents.indexOf(type) === index
|
|
1236
|
+
);
|
|
1237
|
+
const incompatible = [];
|
|
1238
|
+
const compatibleAgents = [];
|
|
1239
|
+
for (const agentType of allAgents) {
|
|
1240
|
+
const config = getMcpAgentConfig(agentType);
|
|
1241
|
+
if (query.transport && !isMcpTransportSupported(config, query.transport)) {
|
|
1242
|
+
incompatible.push({
|
|
1243
|
+
agent: agentType,
|
|
1244
|
+
reason: config.unsupportedTransportMessage ?? `agent ${agentType} only supports ${config.supportedTransports.join(", ")} transport (attempted ${query.transport})`
|
|
1245
|
+
});
|
|
1246
|
+
} else {
|
|
1247
|
+
compatibleAgents.push(agentType);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
let diagnostic;
|
|
1251
|
+
if (compatibleAgents.length === 0) {
|
|
1252
|
+
if (isDetected) {
|
|
1253
|
+
diagnostic = `No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass -a <agent> (e.g. -a cursor) or --all to install.`;
|
|
1254
|
+
} else if (allAgents.length > 0 && incompatible.length > 0 && query.transport) {
|
|
1255
|
+
const list = incompatible.map((item) => `${item.agent} (${item.reason})`).join(", ");
|
|
1256
|
+
diagnostic = `None of the selected agents support ${query.transport} transport: ${list}`;
|
|
1257
|
+
} else {
|
|
1258
|
+
diagnostic = "No valid target agents specified.";
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return {
|
|
1262
|
+
agents: compatibleAgents,
|
|
1263
|
+
compatibleAgents,
|
|
1264
|
+
allAgents,
|
|
1265
|
+
candidateAgents: allAgents,
|
|
1266
|
+
detected,
|
|
1267
|
+
isDetected,
|
|
1268
|
+
incompatible,
|
|
1269
|
+
diagnostic
|
|
1270
|
+
};
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1273
|
+
// src/source-parser.ts
|
|
1274
|
+
var REMOTE_URL_REGEX = /^https?:\/\//i;
|
|
1275
|
+
var HAS_WHITESPACE_REGEX = /\s/;
|
|
1276
|
+
var PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(?:@[^\s]+)?$/;
|
|
1277
|
+
var PATH_SEPARATOR_REGEX = /[/\\]/;
|
|
1278
|
+
var stripVersionSuffix = (input5) => {
|
|
1279
|
+
if (input5.startsWith("@")) {
|
|
1280
|
+
const secondAtIndex = input5.indexOf("@", 1);
|
|
1281
|
+
if (secondAtIndex > 0) return input5.slice(0, secondAtIndex);
|
|
1282
|
+
return input5;
|
|
1283
|
+
}
|
|
1284
|
+
const atIndex = input5.lastIndexOf("@");
|
|
1285
|
+
if (atIndex > 0) return input5.slice(0, atIndex);
|
|
1286
|
+
return input5;
|
|
1287
|
+
};
|
|
1288
|
+
var stripScopePrefix = (input5) => {
|
|
1289
|
+
if (!input5.startsWith("@") || !input5.includes("/")) return input5;
|
|
1290
|
+
const parts = input5.split("/");
|
|
1291
|
+
return parts[1] || input5;
|
|
1292
|
+
};
|
|
1293
|
+
var stripPathPrefix = (input5) => {
|
|
1294
|
+
if (!PATH_SEPARATOR_REGEX.test(input5)) return input5;
|
|
1295
|
+
const segments = input5.split(PATH_SEPARATOR_REGEX);
|
|
1296
|
+
const basename = segments[segments.length - 1];
|
|
1297
|
+
return basename || input5;
|
|
1298
|
+
};
|
|
1299
|
+
var extractPackageName = (input5) => {
|
|
1300
|
+
let name = stripVersionSuffix(input5);
|
|
1301
|
+
name = stripScopePrefix(name);
|
|
1302
|
+
name = stripPathPrefix(name);
|
|
1303
|
+
name = name.replace(SCRIPT_EXTENSION_REGEX, "");
|
|
1304
|
+
for (const prefix of PACKAGE_NAME_PREFIX_STRIP) {
|
|
1305
|
+
if (name.startsWith(prefix)) {
|
|
1306
|
+
name = name.slice(prefix.length);
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
for (const suffix of PACKAGE_NAME_SUFFIX_STRIP) {
|
|
1311
|
+
if (name.endsWith(suffix)) {
|
|
1312
|
+
name = name.slice(0, -suffix.length);
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return name || MCP_DEFAULT_SERVER_NAME;
|
|
1317
|
+
};
|
|
1318
|
+
var inferNameFromUrl = (input5) => {
|
|
1319
|
+
try {
|
|
1320
|
+
const url = new URL(input5);
|
|
1321
|
+
const host = url.hostname;
|
|
1322
|
+
const labels = host.split(".").filter((segment) => segment.length > 0);
|
|
1323
|
+
if (labels.length === 0) return MCP_DEFAULT_SERVER_NAME;
|
|
1324
|
+
const meaningfulLabels = labels.filter((label) => {
|
|
1325
|
+
const lower = label.toLowerCase();
|
|
1326
|
+
if (COMMON_TLD_LABELS.has(lower)) return false;
|
|
1327
|
+
if (GENERIC_HOST_PREFIXES.has(lower)) return false;
|
|
1328
|
+
return true;
|
|
1329
|
+
});
|
|
1330
|
+
if (meaningfulLabels.length > 0) return meaningfulLabels[0];
|
|
1331
|
+
if (labels.length >= 2) return labels[labels.length - 2];
|
|
1332
|
+
return labels[labels.length - 1] || MCP_DEFAULT_SERVER_NAME;
|
|
1333
|
+
} catch {
|
|
1334
|
+
return MCP_DEFAULT_SERVER_NAME;
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
var inferNameFromCommand = (command) => {
|
|
1338
|
+
const tokens = command.trim().split(/\s+/);
|
|
1339
|
+
const runnerBase = tokens[0]?.split(PATH_SEPARATOR_REGEX).pop() ?? "";
|
|
1340
|
+
const startIndex = KNOWN_COMMAND_RUNNERS.has(runnerBase) ? 1 : 0;
|
|
1341
|
+
for (let tokenIndex = startIndex; tokenIndex < tokens.length; tokenIndex += 1) {
|
|
1342
|
+
const token = tokens[tokenIndex];
|
|
1343
|
+
if (!token || token.startsWith("-")) continue;
|
|
1344
|
+
return extractPackageName(token);
|
|
1345
|
+
}
|
|
1346
|
+
const firstNonFlag = tokens.find((token) => !token.startsWith("-"));
|
|
1347
|
+
return firstNonFlag ? extractPackageName(firstNonFlag) : MCP_DEFAULT_SERVER_NAME;
|
|
1348
|
+
};
|
|
1349
|
+
var parseMcpSource = (input5) => {
|
|
1350
|
+
const trimmed = input5.trim();
|
|
1351
|
+
if (trimmed.length === 0) {
|
|
1352
|
+
throw new Error(
|
|
1353
|
+
"Invalid MCP source: input is empty. Expected a remote URL, an npm package, or a command line."
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
if (REMOTE_URL_REGEX.test(trimmed)) {
|
|
1357
|
+
return {
|
|
1358
|
+
type: "remote",
|
|
1359
|
+
value: trimmed,
|
|
1360
|
+
inferredName: inferNameFromUrl(trimmed)
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
if (HAS_WHITESPACE_REGEX.test(trimmed)) {
|
|
1364
|
+
return {
|
|
1365
|
+
type: "command",
|
|
1366
|
+
value: trimmed,
|
|
1367
|
+
inferredName: inferNameFromCommand(trimmed)
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
if (PACKAGE_NAME_REGEX.test(trimmed)) {
|
|
1371
|
+
return {
|
|
1372
|
+
type: "package",
|
|
1373
|
+
value: trimmed,
|
|
1374
|
+
inferredName: extractPackageName(trimmed)
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
type: "command",
|
|
1379
|
+
value: trimmed,
|
|
1380
|
+
inferredName: inferNameFromCommand(trimmed)
|
|
1381
|
+
};
|
|
1382
|
+
};
|
|
1383
|
+
var isRemoteMcpSource = (parsed) => parsed.type === "remote";
|
|
1384
|
+
|
|
1385
|
+
// src/install-mcp-server.ts
|
|
1386
|
+
var installMcpServer = (options) => {
|
|
1387
|
+
const parsed = parseMcpSource(options.source);
|
|
1388
|
+
const isGlobal = options.global ?? false;
|
|
1389
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1390
|
+
const serverName = options.name ?? parsed.inferredName;
|
|
1391
|
+
const serverConfig = buildMcpServerConfig(parsed, {
|
|
1392
|
+
transport: options.transport,
|
|
1393
|
+
headers: options.headers,
|
|
1394
|
+
env: options.env,
|
|
1395
|
+
args: options.args
|
|
1396
|
+
});
|
|
1397
|
+
const requestedTransport = parsed.type === "remote" ? serverConfig.type ?? "http" : "stdio";
|
|
1398
|
+
const { allAgents, incompatible } = resolveTargetAgents({
|
|
1399
|
+
requested: options.agents,
|
|
1400
|
+
global: isGlobal,
|
|
1401
|
+
cwd,
|
|
1402
|
+
transport: requestedTransport
|
|
1403
|
+
});
|
|
1404
|
+
const incompatibleMap = new Map(incompatible.map((item) => [item.agent, item.reason]));
|
|
1405
|
+
const results = allAgents.map((agentType) => {
|
|
1406
|
+
const incompatibleReason = incompatibleMap.get(agentType);
|
|
1407
|
+
if (incompatibleReason) {
|
|
1408
|
+
return {
|
|
1409
|
+
agent: agentType,
|
|
1410
|
+
success: false,
|
|
1411
|
+
path: "",
|
|
1412
|
+
error: incompatibleReason
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
return installMcpServerForAgent(serverName, serverConfig, agentType, { global: isGlobal, cwd });
|
|
1416
|
+
});
|
|
1417
|
+
return { serverName, config: serverConfig, results };
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/list.ts
|
|
1421
|
+
var listInstalledMcpServers = (options = {}) => {
|
|
1422
|
+
const agentTypes = options.agents ?? getMcpAgentTypes();
|
|
1423
|
+
const collected = [];
|
|
1424
|
+
for (const agentType of agentTypes) {
|
|
1425
|
+
const agent = getMcpAgentConfig(agentType);
|
|
1426
|
+
const { path, exists, servers } = agentConfigStore.listServers(agent, options);
|
|
1427
|
+
if (!exists) continue;
|
|
1428
|
+
for (const [serverName, rawConfig] of Object.entries(servers)) {
|
|
1429
|
+
collected.push({
|
|
1430
|
+
serverName,
|
|
1431
|
+
agent: agentType,
|
|
1432
|
+
path,
|
|
1433
|
+
config: rawConfig,
|
|
1434
|
+
serverConfig: parseServerConfig(rawConfig)
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return collected;
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
// src/remove.ts
|
|
1442
|
+
var removeMcpServerFromAgent = (serverName, agentType, options = {}) => {
|
|
1443
|
+
const agent = getMcpAgentConfig(agentType);
|
|
1444
|
+
const { target } = agentConfigStore.resolveTarget(agent, options);
|
|
1445
|
+
try {
|
|
1446
|
+
const { removed } = agentConfigStore.removeServer(agent, serverName, options);
|
|
1447
|
+
return { agent: agentType, path: target.configPath, removed };
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
return {
|
|
1450
|
+
agent: agentType,
|
|
1451
|
+
path: target.configPath,
|
|
1452
|
+
removed: false,
|
|
1453
|
+
error: toErrorMessage(error)
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
var removeMcpServer = (options) => {
|
|
1458
|
+
const { allAgents } = resolveTargetAgents({
|
|
1459
|
+
requested: options.agents,
|
|
1460
|
+
all: !options.agents,
|
|
1461
|
+
global: options.global,
|
|
1462
|
+
cwd: options.cwd
|
|
1463
|
+
});
|
|
1464
|
+
const results = [];
|
|
1465
|
+
for (const agentType of allAgents) {
|
|
1466
|
+
const result = removeMcpServerFromAgent(options.name, agentType, {
|
|
1467
|
+
global: options.global,
|
|
1468
|
+
cwd: options.cwd
|
|
1469
|
+
});
|
|
1470
|
+
if (result.removed || result.error) results.push(result);
|
|
1471
|
+
}
|
|
1472
|
+
return results;
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
// src/interactive/main-menu.ts
|
|
1476
|
+
import { select as select8 } from "@inquirer/prompts";
|
|
1477
|
+
import pc10 from "picocolors";
|
|
1478
|
+
|
|
1479
|
+
// src/interactive/wizard-add.ts
|
|
1480
|
+
import { confirm as confirm2, input as input4, select as select5 } from "@inquirer/prompts";
|
|
1481
|
+
import pc7 from "picocolors";
|
|
1482
|
+
|
|
1483
|
+
// src/utils/logger.ts
|
|
1484
|
+
import pc from "picocolors";
|
|
1485
|
+
var logger = {
|
|
1486
|
+
info: (message) => {
|
|
1487
|
+
console.log(pc.cyan("i"), message);
|
|
1488
|
+
},
|
|
1489
|
+
success: (message) => {
|
|
1490
|
+
console.log(pc.green("\u221A"), message);
|
|
1491
|
+
},
|
|
1492
|
+
warn: (message) => {
|
|
1493
|
+
console.log(pc.yellow("!"), message);
|
|
1494
|
+
},
|
|
1495
|
+
error: (message) => {
|
|
1496
|
+
console.error(pc.red("x"), message);
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
// src/interactive/prompts/agents.ts
|
|
1501
|
+
import { checkbox } from "@inquirer/prompts";
|
|
1502
|
+
import pc3 from "picocolors";
|
|
1503
|
+
|
|
1504
|
+
// src/interactive/prompts/scope.ts
|
|
1505
|
+
import { select } from "@inquirer/prompts";
|
|
1506
|
+
import pc2 from "picocolors";
|
|
1507
|
+
var promptScope = async (options = {}) => {
|
|
1508
|
+
const initialGlobal = options.defaultGlobal ?? options.global;
|
|
1509
|
+
if (initialGlobal !== void 0) {
|
|
1510
|
+
return initialGlobal;
|
|
1511
|
+
}
|
|
1512
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1513
|
+
return select({
|
|
1514
|
+
message: options.message ?? "Select MCP scope:",
|
|
1515
|
+
choices: [
|
|
1516
|
+
{
|
|
1517
|
+
name: `Current Project - ${pc2.dim(cwd)}`,
|
|
1518
|
+
value: false
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
name: `Global User Config - ${pc2.dim("applies across all projects")}`,
|
|
1522
|
+
value: true
|
|
1523
|
+
}
|
|
1524
|
+
]
|
|
1525
|
+
});
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
// src/interactive/prompts/agents.ts
|
|
1529
|
+
var promptScopeAndAgents = async (options = {}) => {
|
|
1530
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1531
|
+
const isGlobal = await promptScope({
|
|
1532
|
+
cwd,
|
|
1533
|
+
defaultGlobal: options.defaultGlobal,
|
|
1534
|
+
message: "Select MCP installation scope:"
|
|
1535
|
+
});
|
|
1536
|
+
const resolution = resolveTargetAgents({
|
|
1537
|
+
global: isGlobal,
|
|
1538
|
+
cwd
|
|
1539
|
+
});
|
|
1540
|
+
const detected = resolution.detected;
|
|
1541
|
+
const availableAgentTypes = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
|
|
1542
|
+
if (detected.length > 0) {
|
|
1543
|
+
logger.info(
|
|
1544
|
+
`Detected configured agents: ${pc3.cyan(detected.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
|
|
1545
|
+
);
|
|
1546
|
+
} else {
|
|
1547
|
+
logger.warn(`No active ${isGlobal ? "global" : "project"} agents detected`);
|
|
1548
|
+
}
|
|
1549
|
+
const defaultChecked = options.defaultAgents && options.defaultAgents.length > 0 ? options.defaultAgents : detected;
|
|
1550
|
+
const choices = availableAgentTypes.map((agentType) => {
|
|
1551
|
+
const config = getMcpAgentConfig(agentType);
|
|
1552
|
+
const isDetected = detected.includes(agentType);
|
|
1553
|
+
const label = `${config.displayName} ${pc3.dim(`(${agentType})`)}${isDetected ? pc3.green(" [detected]") : ""}`;
|
|
1554
|
+
return {
|
|
1555
|
+
name: label,
|
|
1556
|
+
value: agentType,
|
|
1557
|
+
checked: defaultChecked.includes(agentType)
|
|
1558
|
+
};
|
|
1559
|
+
});
|
|
1560
|
+
const selectedAgents = await checkbox({
|
|
1561
|
+
message: "Select target agents (Space to select, Enter to confirm):",
|
|
1562
|
+
choices,
|
|
1563
|
+
validate: (chosen) => {
|
|
1564
|
+
if (chosen.length === 0) {
|
|
1565
|
+
return "Please select at least one agent";
|
|
1566
|
+
}
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
return {
|
|
1571
|
+
global: isGlobal,
|
|
1572
|
+
agents: selectedAgents
|
|
1573
|
+
};
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
// src/interactive/prompts/args.ts
|
|
1577
|
+
import { confirm, input } from "@inquirer/prompts";
|
|
1578
|
+
var parseArgsString = (rawText) => {
|
|
1579
|
+
const matches = rawText.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
|
1580
|
+
if (!matches) return [];
|
|
1581
|
+
return matches.map((arg) => {
|
|
1582
|
+
if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
|
|
1583
|
+
return arg.slice(1, -1);
|
|
1584
|
+
}
|
|
1585
|
+
return arg;
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
var promptArgsConfig = async (initialArgs = []) => {
|
|
1589
|
+
if (initialArgs.length > 0) {
|
|
1590
|
+
return initialArgs;
|
|
1591
|
+
}
|
|
1592
|
+
const needArgs = await confirm({
|
|
1593
|
+
message: "Configure command arguments (e.g. file paths, connection strings)?",
|
|
1594
|
+
default: false
|
|
1595
|
+
});
|
|
1596
|
+
if (!needArgs) {
|
|
1597
|
+
return [];
|
|
1598
|
+
}
|
|
1599
|
+
const raw = await input({
|
|
1600
|
+
message: "Enter command arguments (space-separated, wrap paths with spaces in quotes):",
|
|
1601
|
+
validate: (val) => val.trim() ? true : "Arguments cannot be empty"
|
|
1602
|
+
});
|
|
1603
|
+
return parseArgsString(raw.trim());
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
// src/interactive/prompts/env.ts
|
|
1607
|
+
import { input as input2, password, select as select3 } from "@inquirer/prompts";
|
|
1608
|
+
import pc5 from "picocolors";
|
|
1609
|
+
|
|
1610
|
+
// src/interactive/prompts/multiline.ts
|
|
1611
|
+
import { createInterface } from "readline";
|
|
1612
|
+
import { editor } from "@inquirer/prompts";
|
|
1613
|
+
import pc4 from "picocolors";
|
|
1614
|
+
var readMultilineTextFromTerminal = async (message, endHint = "When done pasting, enter END on a new line or press Enter twice to finish") => {
|
|
1615
|
+
console.log(pc4.cyan(`
|
|
1616
|
+
${message}`));
|
|
1617
|
+
console.log(pc4.dim(` (Hint: ${endHint})
|
|
1618
|
+
`));
|
|
1619
|
+
return new Promise((resolve) => {
|
|
1620
|
+
const rl = createInterface({
|
|
1621
|
+
input: process.stdin,
|
|
1622
|
+
output: process.stdout
|
|
1623
|
+
});
|
|
1624
|
+
const lines = [];
|
|
1625
|
+
let consecutiveEmpty = 0;
|
|
1626
|
+
const cleanup = () => {
|
|
1627
|
+
rl.removeAllListeners();
|
|
1628
|
+
rl.close();
|
|
1629
|
+
};
|
|
1630
|
+
rl.on("line", (line) => {
|
|
1631
|
+
const trimmed = line.trim();
|
|
1632
|
+
if (trimmed === "END") {
|
|
1633
|
+
cleanup();
|
|
1634
|
+
resolve(lines.join("\n"));
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
if (line === "") {
|
|
1638
|
+
consecutiveEmpty++;
|
|
1639
|
+
if (lines.length > 0) {
|
|
1640
|
+
cleanup();
|
|
1641
|
+
resolve(lines.join("\n"));
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
if (consecutiveEmpty >= 2) {
|
|
1645
|
+
cleanup();
|
|
1646
|
+
resolve("");
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
} else {
|
|
1650
|
+
consecutiveEmpty = 0;
|
|
1651
|
+
lines.push(line);
|
|
1652
|
+
}
|
|
1653
|
+
});
|
|
1654
|
+
rl.on("close", () => {
|
|
1655
|
+
resolve(lines.join("\n"));
|
|
1656
|
+
});
|
|
1657
|
+
});
|
|
1658
|
+
};
|
|
1659
|
+
var promptEditorText = async (options) => {
|
|
1660
|
+
try {
|
|
1661
|
+
return await editor({
|
|
1662
|
+
message: options.message,
|
|
1663
|
+
default: options.defaultText ?? "",
|
|
1664
|
+
postfix: options.postfix
|
|
1665
|
+
});
|
|
1666
|
+
} catch {
|
|
1667
|
+
return readMultilineTextFromTerminal(options.message);
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
|
|
1671
|
+
// src/interactive/prompts/env.ts
|
|
1672
|
+
var SECRET_KEY_PATTERN = /(token|key|secret|password|passwd|auth|credential)/i;
|
|
1673
|
+
var parseEnvText = (rawText) => {
|
|
1674
|
+
const result = {};
|
|
1675
|
+
const lines = rawText.split(/\r?\n/);
|
|
1676
|
+
for (const rawLine of lines) {
|
|
1677
|
+
let line = rawLine.trim();
|
|
1678
|
+
if (!line || line.startsWith("#")) continue;
|
|
1679
|
+
if (line.startsWith("export ")) {
|
|
1680
|
+
line = line.slice(7).trim();
|
|
1681
|
+
}
|
|
1682
|
+
const eqIndex = line.indexOf("=");
|
|
1683
|
+
if (eqIndex === -1) continue;
|
|
1684
|
+
const key = line.slice(0, eqIndex).trim();
|
|
1685
|
+
let value = line.slice(eqIndex + 1).trim();
|
|
1686
|
+
if (!key) continue;
|
|
1687
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1688
|
+
value = value.slice(1, -1);
|
|
1689
|
+
}
|
|
1690
|
+
result[key] = value;
|
|
1691
|
+
}
|
|
1692
|
+
return result;
|
|
1693
|
+
};
|
|
1694
|
+
var promptEnvConfig = async (initialEnv = {}) => {
|
|
1695
|
+
const env = { ...initialEnv };
|
|
1696
|
+
const initialCount = Object.keys(env).length;
|
|
1697
|
+
if (initialCount > 0) {
|
|
1698
|
+
logger.info(`Includes ${pc5.cyan(String(initialCount))} preset environment variables`);
|
|
1699
|
+
}
|
|
1700
|
+
const mode = await select3({
|
|
1701
|
+
message: "Configure environment variables?",
|
|
1702
|
+
choices: [
|
|
1703
|
+
{
|
|
1704
|
+
name: "Skip / None",
|
|
1705
|
+
value: "skip"
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
name: "Paste multiline .env text into terminal",
|
|
1709
|
+
value: "paste"
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
name: "Open in system default editor ($EDITOR)",
|
|
1713
|
+
value: "editor"
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
name: "Enter key-value pairs one by one",
|
|
1717
|
+
value: "manual"
|
|
1718
|
+
}
|
|
1719
|
+
]
|
|
1720
|
+
});
|
|
1721
|
+
if (mode === "skip") {
|
|
1722
|
+
return env;
|
|
1723
|
+
}
|
|
1724
|
+
if (mode === "paste" || mode === "editor") {
|
|
1725
|
+
const pasted = mode === "editor" ? await promptEditorText({
|
|
1726
|
+
message: "Paste or edit environment variables in editor, then save and exit:",
|
|
1727
|
+
postfix: ".env"
|
|
1728
|
+
}) : await readMultilineTextFromTerminal("Paste .env formatted content (multiline supported):");
|
|
1729
|
+
const parsed = parseEnvText(pasted);
|
|
1730
|
+
const count = Object.keys(parsed).length;
|
|
1731
|
+
if (count === 0) {
|
|
1732
|
+
logger.warn("No valid KEY=VALUE pairs recognized");
|
|
1733
|
+
} else {
|
|
1734
|
+
Object.assign(env, parsed);
|
|
1735
|
+
logger.success(`Successfully parsed ${pc5.cyan(String(count))} environment variables:`);
|
|
1736
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
1737
|
+
const masked = SECRET_KEY_PATTERN.test(k) && v.length > 4 ? `${v.slice(0, 2)}***${v.slice(-2)}` : v;
|
|
1738
|
+
console.log(` ${pc5.bold(k)}=${pc5.dim(masked)}`);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
return env;
|
|
1742
|
+
}
|
|
1743
|
+
logger.info("Entering environment variables (leave key empty and press enter to finish):");
|
|
1744
|
+
while (true) {
|
|
1745
|
+
const key = await input2({
|
|
1746
|
+
message: "Variable name (Key, leave empty to finish):",
|
|
1747
|
+
validate: (val2) => {
|
|
1748
|
+
const trimmed = val2.trim();
|
|
1749
|
+
if (!trimmed) return true;
|
|
1750
|
+
if (/\s/.test(trimmed)) return "Variable name cannot contain spaces";
|
|
1751
|
+
return true;
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
const trimmedKey = key.trim();
|
|
1755
|
+
if (!trimmedKey) break;
|
|
1756
|
+
const isSecret = SECRET_KEY_PATTERN.test(trimmedKey);
|
|
1757
|
+
let val;
|
|
1758
|
+
if (isSecret) {
|
|
1759
|
+
val = await password({
|
|
1760
|
+
message: `Value for (${trimmedKey}) [secret masked]:`,
|
|
1761
|
+
mask: "*"
|
|
1762
|
+
});
|
|
1763
|
+
} else {
|
|
1764
|
+
val = await input2({
|
|
1765
|
+
message: `Value for (${trimmedKey}):`
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
env[trimmedKey] = val;
|
|
1769
|
+
logger.success(`Added: ${pc5.cyan(trimmedKey)}`);
|
|
1770
|
+
}
|
|
1771
|
+
return env;
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
// src/interactive/prompts/headers.ts
|
|
1775
|
+
import { input as input3, password as password2, select as select4 } from "@inquirer/prompts";
|
|
1776
|
+
import pc6 from "picocolors";
|
|
1777
|
+
var SECRET_HEADER_PATTERN = /(authorization|token|key|secret|auth)/i;
|
|
1778
|
+
var parseHeadersText = (rawText) => {
|
|
1779
|
+
const result = {};
|
|
1780
|
+
const lines = rawText.split(/\r?\n/);
|
|
1781
|
+
for (const rawLine of lines) {
|
|
1782
|
+
const line = rawLine.trim();
|
|
1783
|
+
if (!line || line.startsWith("#")) continue;
|
|
1784
|
+
const colonIndex = line.indexOf(":");
|
|
1785
|
+
const equalIndex = line.indexOf("=");
|
|
1786
|
+
let splitIndex = -1;
|
|
1787
|
+
if (colonIndex !== -1 && equalIndex !== -1) {
|
|
1788
|
+
splitIndex = Math.min(colonIndex, equalIndex);
|
|
1789
|
+
} else if (colonIndex !== -1) {
|
|
1790
|
+
splitIndex = colonIndex;
|
|
1791
|
+
} else {
|
|
1792
|
+
splitIndex = equalIndex;
|
|
1793
|
+
}
|
|
1794
|
+
if (splitIndex === -1) continue;
|
|
1795
|
+
const key = line.slice(0, splitIndex).trim();
|
|
1796
|
+
let value = line.slice(splitIndex + 1).trim();
|
|
1797
|
+
if (!key) continue;
|
|
1798
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1799
|
+
value = value.slice(1, -1);
|
|
1800
|
+
}
|
|
1801
|
+
result[key] = value;
|
|
1802
|
+
}
|
|
1803
|
+
return result;
|
|
1804
|
+
};
|
|
1805
|
+
var promptHeadersConfig = async (initialHeaders = {}) => {
|
|
1806
|
+
const headers = { ...initialHeaders };
|
|
1807
|
+
const mode = await select4({
|
|
1808
|
+
message: "Select HTTP headers configuration method:",
|
|
1809
|
+
choices: [
|
|
1810
|
+
{
|
|
1811
|
+
name: "Paste multiline headers into terminal (Key: Value format)",
|
|
1812
|
+
value: "paste"
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
name: "Open in system default editor ($EDITOR)",
|
|
1816
|
+
value: "editor"
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
name: "Enter headers one by one (e.g. Authorization: Bearer ...)",
|
|
1820
|
+
value: "manual"
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
name: "Skip / None",
|
|
1824
|
+
value: "skip"
|
|
1825
|
+
}
|
|
1826
|
+
]
|
|
1827
|
+
});
|
|
1828
|
+
if (mode === "skip") {
|
|
1829
|
+
return headers;
|
|
1830
|
+
}
|
|
1831
|
+
if (mode === "paste" || mode === "editor") {
|
|
1832
|
+
const pasted = mode === "editor" ? await promptEditorText({
|
|
1833
|
+
message: "Paste or edit HTTP headers in editor, then save and exit:"
|
|
1834
|
+
}) : await readMultilineTextFromTerminal(
|
|
1835
|
+
"Paste HTTP headers content (multiline supported, e.g. Authorization: Bearer ...):"
|
|
1836
|
+
);
|
|
1837
|
+
const parsed = parseHeadersText(pasted);
|
|
1838
|
+
const count = Object.keys(parsed).length;
|
|
1839
|
+
if (count === 0) {
|
|
1840
|
+
logger.warn("No valid Key: Value pairs recognized");
|
|
1841
|
+
} else {
|
|
1842
|
+
Object.assign(headers, parsed);
|
|
1843
|
+
logger.success(`Successfully parsed ${pc6.cyan(String(count))} headers:`);
|
|
1844
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
1845
|
+
const masked = SECRET_HEADER_PATTERN.test(k) && v.length > 8 ? `${v.slice(0, 4)}***${v.slice(-3)}` : v;
|
|
1846
|
+
console.log(` ${pc6.bold(k)}: ${pc6.dim(masked)}`);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return headers;
|
|
1850
|
+
}
|
|
1851
|
+
logger.info("Entering HTTP headers (leave header name empty and press enter to finish):");
|
|
1852
|
+
while (true) {
|
|
1853
|
+
const name = await input3({
|
|
1854
|
+
message: "Header name (e.g. Authorization, leave empty to finish):",
|
|
1855
|
+
validate: (val2) => {
|
|
1856
|
+
const trimmed = val2.trim();
|
|
1857
|
+
if (!trimmed) return true;
|
|
1858
|
+
if (/\s/.test(trimmed)) return "Header name cannot contain spaces";
|
|
1859
|
+
return true;
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
const trimmedName = name.trim();
|
|
1863
|
+
if (!trimmedName) break;
|
|
1864
|
+
const isSecret = SECRET_HEADER_PATTERN.test(trimmedName);
|
|
1865
|
+
let val;
|
|
1866
|
+
if (isSecret) {
|
|
1867
|
+
val = await password2({
|
|
1868
|
+
message: `Header value for (${trimmedName}) [sensitive content masked]:`,
|
|
1869
|
+
mask: "*"
|
|
1870
|
+
});
|
|
1871
|
+
} else {
|
|
1872
|
+
val = await input3({
|
|
1873
|
+
message: `Header value for (${trimmedName}):`
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
headers[trimmedName] = val;
|
|
1877
|
+
logger.success(`Added: ${pc6.cyan(trimmedName)}`);
|
|
1878
|
+
}
|
|
1879
|
+
return headers;
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
// src/interactive/wizard-add.ts
|
|
1883
|
+
var wizardAdd = async (initial = {}) => {
|
|
1884
|
+
const cwd = initial.cwd ?? process.cwd();
|
|
1885
|
+
logger.info(pc7.bold("Welcome to the MCP interactive add wizard"));
|
|
1886
|
+
let source = initial.source;
|
|
1887
|
+
if (!source) {
|
|
1888
|
+
const sourceType = await select5({
|
|
1889
|
+
message: "Select MCP server type:",
|
|
1890
|
+
choices: [
|
|
1891
|
+
{
|
|
1892
|
+
name: "npm package (run via npx)",
|
|
1893
|
+
value: "npm"
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
name: "Remote MCP server (via HTTP / SSE URL)",
|
|
1897
|
+
value: "remote"
|
|
1898
|
+
},
|
|
1899
|
+
{
|
|
1900
|
+
name: "Local command / script / Docker (stdio)",
|
|
1901
|
+
value: "command"
|
|
1902
|
+
}
|
|
1903
|
+
]
|
|
1904
|
+
});
|
|
1905
|
+
if (sourceType === "npm") {
|
|
1906
|
+
source = await input4({
|
|
1907
|
+
message: "Enter npm package name (e.g. @modelcontextprotocol/server-postgres or mcp-server-git):",
|
|
1908
|
+
validate: (val) => val.trim() ? true : "Package name cannot be empty"
|
|
1909
|
+
});
|
|
1910
|
+
} else if (sourceType === "remote") {
|
|
1911
|
+
source = await input4({
|
|
1912
|
+
message: "Enter remote server URL (e.g. https://mcp.example.com/sse):",
|
|
1913
|
+
validate: (val) => {
|
|
1914
|
+
const trimmed = val.trim();
|
|
1915
|
+
if (!trimmed) return "URL cannot be empty";
|
|
1916
|
+
if (!/^https?:\/\//i.test(trimmed)) return "Please enter a valid URL starting with http:// or https://";
|
|
1917
|
+
return true;
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
} else {
|
|
1921
|
+
source = await input4({
|
|
1922
|
+
message: "Enter command and arguments (e.g. python -m my_mcp_server or docker run ...):",
|
|
1923
|
+
validate: (val) => val.trim() ? true : "Command cannot be empty"
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
source = source.trim();
|
|
1928
|
+
const parsed = parseMcpSource(source);
|
|
1929
|
+
let serverName = initial.name;
|
|
1930
|
+
if (!serverName) {
|
|
1931
|
+
serverName = await input4({
|
|
1932
|
+
message: "MCP server name:",
|
|
1933
|
+
default: parsed.inferredName,
|
|
1934
|
+
validate: (val) => val.trim() ? true : "Server name cannot be empty"
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
serverName = serverName.trim();
|
|
1938
|
+
let transport = initial.transport;
|
|
1939
|
+
let headers = initial.headers ?? {};
|
|
1940
|
+
if (parsed.type === "remote") {
|
|
1941
|
+
if (!transport) {
|
|
1942
|
+
const isSseUrl = /\/sse\b/i.test(parsed.value);
|
|
1943
|
+
transport = await select5({
|
|
1944
|
+
message: "Select remote transport protocol:",
|
|
1945
|
+
choices: [
|
|
1946
|
+
{ name: "HTTP", value: "http" },
|
|
1947
|
+
{ name: "SSE (Server-Sent Events)", value: "sse" }
|
|
1948
|
+
],
|
|
1949
|
+
default: isSseUrl ? "sse" : "http"
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
if (Object.keys(headers).length === 0) {
|
|
1953
|
+
const needHeader = await confirm2({
|
|
1954
|
+
message: "Configure HTTP headers (e.g. Authorization Bearer token)?",
|
|
1955
|
+
default: false
|
|
1956
|
+
});
|
|
1957
|
+
if (needHeader) {
|
|
1958
|
+
headers = await promptHeadersConfig();
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
const { global: isGlobal, agents: selectedAgents } = await promptScopeAndAgents({
|
|
1963
|
+
cwd,
|
|
1964
|
+
defaultGlobal: initial.global,
|
|
1965
|
+
defaultAgents: initial.agents
|
|
1966
|
+
});
|
|
1967
|
+
let args = initial.args ?? [];
|
|
1968
|
+
if (parsed.type !== "remote") {
|
|
1969
|
+
args = await promptArgsConfig(args);
|
|
1970
|
+
}
|
|
1971
|
+
let env = initial.env ?? {};
|
|
1972
|
+
if (parsed.type !== "remote") {
|
|
1973
|
+
env = await promptEnvConfig(env);
|
|
1974
|
+
}
|
|
1975
|
+
console.log("\n" + pc7.cyan(pc7.bold("Configuration Preview:")));
|
|
1976
|
+
console.log(` ${pc7.bold("Server Name:")} ${pc7.green(serverName)}`);
|
|
1977
|
+
console.log(` ${pc7.bold("Server Type:")} ${pc7.magenta(parsed.type)}`);
|
|
1978
|
+
console.log(` ${pc7.bold("Source/Command:")} ${pc7.dim(source)}`);
|
|
1979
|
+
console.log(` ${pc7.bold("Scope:")} ${isGlobal ? pc7.yellow("Global") : pc7.blue("Project")}`);
|
|
1980
|
+
console.log(` ${pc7.bold("Target Agents:")} ${pc7.cyan(selectedAgents.join(", "))}`);
|
|
1981
|
+
if (args.length > 0) {
|
|
1982
|
+
console.log(` ${pc7.bold("Arguments:")} ${pc7.dim(args.join(" "))}`);
|
|
1983
|
+
}
|
|
1984
|
+
if (transport) {
|
|
1985
|
+
console.log(` ${pc7.bold("Transport:")} ${pc7.magenta(transport)}`);
|
|
1986
|
+
}
|
|
1987
|
+
const envKeys = Object.keys(env);
|
|
1988
|
+
if (envKeys.length > 0) {
|
|
1989
|
+
console.log(` ${pc7.bold("Environment Variables:")} ${pc7.dim(envKeys.join(", "))} (${envKeys.length})`);
|
|
1990
|
+
}
|
|
1991
|
+
const headerKeys = Object.keys(headers);
|
|
1992
|
+
if (headerKeys.length > 0) {
|
|
1993
|
+
console.log(` ${pc7.bold("Headers:")} ${pc7.dim(headerKeys.join(", "))} (${headerKeys.length})`);
|
|
1994
|
+
}
|
|
1995
|
+
console.log();
|
|
1996
|
+
const proceed = await confirm2({
|
|
1997
|
+
message: "Confirm installation with this configuration?",
|
|
1998
|
+
default: true
|
|
1999
|
+
});
|
|
2000
|
+
if (!proceed) {
|
|
2001
|
+
logger.warn("Operation cancelled");
|
|
2002
|
+
return false;
|
|
2003
|
+
}
|
|
2004
|
+
const result = installMcpServer({
|
|
2005
|
+
source,
|
|
2006
|
+
name: serverName,
|
|
2007
|
+
agents: selectedAgents,
|
|
2008
|
+
args,
|
|
2009
|
+
global: isGlobal,
|
|
2010
|
+
cwd,
|
|
2011
|
+
transport,
|
|
2012
|
+
headers,
|
|
2013
|
+
env
|
|
2014
|
+
});
|
|
2015
|
+
logger.info(
|
|
2016
|
+
`Writing ${pc7.bold(result.serverName)} to ${pc7.cyan(String(result.results.length))} agent config files...`
|
|
2017
|
+
);
|
|
2018
|
+
let allSuccess = true;
|
|
2019
|
+
for (const record of result.results) {
|
|
2020
|
+
if (record.success) {
|
|
2021
|
+
logger.success(`${pc7.cyan(record.agent)}: Successfully written to ${pc7.dim(record.path)}`);
|
|
2022
|
+
} else {
|
|
2023
|
+
allSuccess = false;
|
|
2024
|
+
logger.error(`${pc7.cyan(record.agent)}: Failed to write - ${record.error}`);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (allSuccess) {
|
|
2028
|
+
logger.success(pc7.bold(`MCP server "${serverName}" configured successfully!`));
|
|
2029
|
+
}
|
|
2030
|
+
return allSuccess;
|
|
2031
|
+
};
|
|
2032
|
+
|
|
2033
|
+
// src/interactive/wizard-manage.ts
|
|
2034
|
+
import { checkbox as checkbox2, confirm as confirm3, select as select6 } from "@inquirer/prompts";
|
|
2035
|
+
import pc8 from "picocolors";
|
|
2036
|
+
|
|
2037
|
+
// src/interactive/utils/group-installed-servers.ts
|
|
2038
|
+
var normalizeServerConfig = parseServerConfig;
|
|
2039
|
+
var groupInstalledServersByName = (installed) => {
|
|
2040
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2041
|
+
for (const item of installed) {
|
|
2042
|
+
let entry = grouped.get(item.serverName);
|
|
2043
|
+
if (!entry) {
|
|
2044
|
+
entry = {
|
|
2045
|
+
serverName: item.serverName,
|
|
2046
|
+
agents: [],
|
|
2047
|
+
paths: [],
|
|
2048
|
+
config: normalizeServerConfig(item.config)
|
|
2049
|
+
};
|
|
2050
|
+
grouped.set(item.serverName, entry);
|
|
2051
|
+
}
|
|
2052
|
+
if (!entry.agents.includes(item.agent)) {
|
|
2053
|
+
entry.agents.push(item.agent);
|
|
2054
|
+
}
|
|
2055
|
+
if (!entry.paths.includes(item.path)) {
|
|
2056
|
+
entry.paths.push(item.path);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return grouped;
|
|
2060
|
+
};
|
|
2061
|
+
|
|
2062
|
+
// src/interactive/wizard-manage.ts
|
|
2063
|
+
var wizardManage = async (options = {}) => {
|
|
2064
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2065
|
+
const isGlobal = await promptScope({
|
|
2066
|
+
cwd,
|
|
2067
|
+
defaultGlobal: options.global,
|
|
2068
|
+
message: "Select MCP scope to inspect and manage:"
|
|
2069
|
+
});
|
|
2070
|
+
const installed = listInstalledMcpServers({ global: isGlobal, cwd });
|
|
2071
|
+
if (installed.length === 0) {
|
|
2072
|
+
logger.warn(`No configured MCP servers found in ${isGlobal ? "global" : "project"} scope`);
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
const grouped = groupInstalledServersByName(installed);
|
|
2076
|
+
while (true) {
|
|
2077
|
+
const choices = Array.from(grouped.values()).map((g) => {
|
|
2078
|
+
const agentNames = g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
|
|
2079
|
+
return {
|
|
2080
|
+
name: `${pc8.bold(g.serverName)} ${pc8.dim(`(configured in: ${agentNames})`)}`,
|
|
2081
|
+
value: g.serverName
|
|
2082
|
+
};
|
|
2083
|
+
});
|
|
2084
|
+
choices.push({
|
|
2085
|
+
name: `Back`,
|
|
2086
|
+
value: "__back__"
|
|
2087
|
+
});
|
|
2088
|
+
const chosenServerName = await select6({
|
|
2089
|
+
message: "Select MCP server to manage or sync:",
|
|
2090
|
+
choices
|
|
2091
|
+
});
|
|
2092
|
+
if (chosenServerName === "__back__") {
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
const targetGroup = grouped.get(chosenServerName);
|
|
2096
|
+
if (!targetGroup) continue;
|
|
2097
|
+
console.log("\n" + pc8.cyan(pc8.bold(`MCP Server Details: [${chosenServerName}]`)));
|
|
2098
|
+
console.log(` ${pc8.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
|
|
2099
|
+
console.log(
|
|
2100
|
+
` ${pc8.bold("Configured Agents:")} ${pc8.green(targetGroup.agents.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
|
|
2101
|
+
);
|
|
2102
|
+
const cfg = targetGroup.config;
|
|
2103
|
+
if (isRemoteServerConfig(cfg)) {
|
|
2104
|
+
console.log(` ${pc8.bold("URL:")} ${pc8.dim(cfg.url)} (${cfg.type})`);
|
|
2105
|
+
if (cfg.headers && Object.keys(cfg.headers).length > 0) {
|
|
2106
|
+
console.log(` ${pc8.bold("Headers:")} ${Object.keys(cfg.headers).join(", ")}`);
|
|
2107
|
+
}
|
|
2108
|
+
} else if (isStdioServerConfig(cfg)) {
|
|
2109
|
+
console.log(` ${pc8.bold("Command:")} ${pc8.magenta(cfg.command)}`);
|
|
2110
|
+
if (cfg.args && cfg.args.length > 0) {
|
|
2111
|
+
console.log(` ${pc8.bold("Arguments:")} ${pc8.dim(cfg.args.join(" "))}`);
|
|
2112
|
+
}
|
|
2113
|
+
if (cfg.env && Object.keys(cfg.env).length > 0) {
|
|
2114
|
+
console.log(` ${pc8.bold("Environment Variables:")} ${pc8.dim(Object.keys(cfg.env).join(", "))}`);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
console.log();
|
|
2118
|
+
const action = await select6({
|
|
2119
|
+
message: `What would you like to do with [${chosenServerName}]?`,
|
|
2120
|
+
choices: [
|
|
2121
|
+
{
|
|
2122
|
+
name: "Sync / clone to other agents",
|
|
2123
|
+
value: "sync"
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
name: "Back to list",
|
|
2127
|
+
value: "back"
|
|
2128
|
+
}
|
|
2129
|
+
]
|
|
2130
|
+
});
|
|
2131
|
+
if (action === "back") continue;
|
|
2132
|
+
if (action === "sync") {
|
|
2133
|
+
const allAllowedAgents = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
|
|
2134
|
+
const candidateAgents = allAllowedAgents.filter((a) => !targetGroup.agents.includes(a));
|
|
2135
|
+
if (candidateAgents.length === 0) {
|
|
2136
|
+
logger.info("All supported agents in this scope already have this MCP server configured; no sync needed");
|
|
2137
|
+
continue;
|
|
2138
|
+
}
|
|
2139
|
+
const selectedToSync = await checkbox2({
|
|
2140
|
+
message: "Select target agents to sync to (Space to select):",
|
|
2141
|
+
choices: candidateAgents.map((a) => ({
|
|
2142
|
+
name: `${getMcpAgentConfig(a).displayName} (${a})`,
|
|
2143
|
+
value: a,
|
|
2144
|
+
checked: false
|
|
2145
|
+
})),
|
|
2146
|
+
validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
|
|
2147
|
+
});
|
|
2148
|
+
const confirmed = await confirm3({
|
|
2149
|
+
message: `Confirm syncing configuration of [${chosenServerName}] to: ${selectedToSync.join(", ")}?`,
|
|
2150
|
+
default: true
|
|
2151
|
+
});
|
|
2152
|
+
if (!confirmed) {
|
|
2153
|
+
logger.warn("Sync cancelled");
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
for (const targetAgent of selectedToSync) {
|
|
2157
|
+
const res = installMcpServerForAgent(chosenServerName, targetGroup.config, targetAgent, {
|
|
2158
|
+
global: isGlobal,
|
|
2159
|
+
cwd
|
|
2160
|
+
});
|
|
2161
|
+
if (res.success) {
|
|
2162
|
+
logger.success(`${pc8.cyan(targetAgent)}: Successfully synced to ${pc8.dim(res.path)}`);
|
|
2163
|
+
targetGroup.agents.push(targetAgent);
|
|
2164
|
+
} else {
|
|
2165
|
+
logger.error(`${pc8.cyan(targetAgent)}: Sync failed - ${res.error}`);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
};
|
|
2171
|
+
|
|
2172
|
+
// src/interactive/wizard-remove.ts
|
|
2173
|
+
import { checkbox as checkbox3, confirm as confirm4, select as select7 } from "@inquirer/prompts";
|
|
2174
|
+
import pc9 from "picocolors";
|
|
2175
|
+
var wizardRemove = async (options = {}) => {
|
|
2176
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2177
|
+
const isGlobal = await promptScope({
|
|
2178
|
+
cwd,
|
|
2179
|
+
defaultGlobal: options.global,
|
|
2180
|
+
message: "Select scope to remove MCP server from:"
|
|
2181
|
+
});
|
|
2182
|
+
const installed = listInstalledMcpServers({ global: isGlobal, cwd });
|
|
2183
|
+
if (installed.length === 0) {
|
|
2184
|
+
logger.warn(`No installed MCP servers found in ${isGlobal ? "global" : "project"} scope`);
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
const serverMap = groupInstalledServersByName(installed);
|
|
2188
|
+
let serverName = options.name;
|
|
2189
|
+
if (!serverName) {
|
|
2190
|
+
const choices = Array.from(serverMap.values()).map((g) => ({
|
|
2191
|
+
name: `${pc9.bold(g.serverName)} ${pc9.dim(`(installed in: ${g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ")})`)}`,
|
|
2192
|
+
value: g.serverName
|
|
2193
|
+
}));
|
|
2194
|
+
serverName = await select7({
|
|
2195
|
+
message: "Select MCP server to remove:",
|
|
2196
|
+
choices
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
const installedAgents = serverMap.get(serverName)?.agents || [];
|
|
2200
|
+
if (installedAgents.length === 0) {
|
|
2201
|
+
logger.warn(`No agents found with [${serverName}] installed`);
|
|
2202
|
+
return false;
|
|
2203
|
+
}
|
|
2204
|
+
let targetAgents = options.agents;
|
|
2205
|
+
if (!targetAgents || targetAgents.length === 0) {
|
|
2206
|
+
targetAgents = await checkbox3({
|
|
2207
|
+
message: `Select agents to remove [${serverName}] from:`,
|
|
2208
|
+
choices: installedAgents.map((agent) => ({
|
|
2209
|
+
name: `${getMcpAgentConfig(agent)?.displayName ?? agent} (${agent})`,
|
|
2210
|
+
value: agent,
|
|
2211
|
+
checked: true
|
|
2212
|
+
})),
|
|
2213
|
+
validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
|
|
2214
|
+
});
|
|
2215
|
+
} else {
|
|
2216
|
+
const validAgents = targetAgents.filter((agent) => installedAgents.includes(agent));
|
|
2217
|
+
if (validAgents.length === 0) {
|
|
2218
|
+
logger.warn(`None of the specified agents (${targetAgents.join(", ")}) have [${serverName}] installed`);
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2221
|
+
targetAgents = validAgents;
|
|
2222
|
+
}
|
|
2223
|
+
const confirmed = await confirm4({
|
|
2224
|
+
message: `Confirm removing MCP server [${serverName}] from ${targetAgents.join(", ")}?`,
|
|
2225
|
+
default: true
|
|
2226
|
+
});
|
|
2227
|
+
if (!confirmed) {
|
|
2228
|
+
logger.warn("Operation cancelled");
|
|
2229
|
+
return false;
|
|
2230
|
+
}
|
|
2231
|
+
const results = removeMcpServer({
|
|
2232
|
+
name: serverName,
|
|
2233
|
+
agents: targetAgents,
|
|
2234
|
+
global: isGlobal,
|
|
2235
|
+
cwd
|
|
2236
|
+
});
|
|
2237
|
+
let removedCount = 0;
|
|
2238
|
+
for (const res of results) {
|
|
2239
|
+
if (res.removed) {
|
|
2240
|
+
logger.success(`${pc9.cyan(res.agent)}: Successfully removed from ${pc9.dim(res.path)}`);
|
|
2241
|
+
removedCount++;
|
|
2242
|
+
} else if (res.error) {
|
|
2243
|
+
logger.error(`${pc9.cyan(res.agent)}: Failed to remove - ${res.error}`);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
if (removedCount > 0) {
|
|
2247
|
+
logger.success(`Successfully removed [${serverName}] from ${removedCount} agent(s)`);
|
|
2248
|
+
return true;
|
|
2249
|
+
}
|
|
2250
|
+
logger.warn(`Failed to remove [${serverName}] from specified agents`);
|
|
2251
|
+
return false;
|
|
2252
|
+
};
|
|
2253
|
+
|
|
2254
|
+
// src/interactive/main-menu.ts
|
|
2255
|
+
var mainMenu = async () => {
|
|
2256
|
+
console.log();
|
|
2257
|
+
console.log(pc10.bold(pc10.cyan("mcps - Cross-Platform MCP Manager for AI Coding Agents")));
|
|
2258
|
+
console.log(pc10.dim("Cross-platform MCP server configuration & synchronization tool"));
|
|
2259
|
+
console.log();
|
|
2260
|
+
while (true) {
|
|
2261
|
+
try {
|
|
2262
|
+
const action = await select8({
|
|
2263
|
+
message: "Select an action:",
|
|
2264
|
+
choices: [
|
|
2265
|
+
{
|
|
2266
|
+
name: "Add MCP Server",
|
|
2267
|
+
value: "add"
|
|
2268
|
+
},
|
|
2269
|
+
{
|
|
2270
|
+
name: "Manage & Sync Installed MCP Servers",
|
|
2271
|
+
value: "manage"
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
name: "Remove MCP Server",
|
|
2275
|
+
value: "remove"
|
|
2276
|
+
},
|
|
2277
|
+
{
|
|
2278
|
+
name: "Exit",
|
|
2279
|
+
value: "exit"
|
|
2280
|
+
}
|
|
2281
|
+
]
|
|
2282
|
+
});
|
|
2283
|
+
if (action === "exit") {
|
|
2284
|
+
console.log(pc10.dim("Goodbye!"));
|
|
2285
|
+
break;
|
|
2286
|
+
}
|
|
2287
|
+
if (action === "add") {
|
|
2288
|
+
await wizardAdd();
|
|
2289
|
+
} else if (action === "manage") {
|
|
2290
|
+
await wizardManage();
|
|
2291
|
+
} else if (action === "remove") {
|
|
2292
|
+
await wizardRemove();
|
|
2293
|
+
}
|
|
2294
|
+
console.log();
|
|
2295
|
+
} catch (error) {
|
|
2296
|
+
if (error?.name === "ExitPromptError") {
|
|
2297
|
+
console.log("\n" + pc10.dim("Exited."));
|
|
2298
|
+
break;
|
|
2299
|
+
}
|
|
2300
|
+
throw error;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
|
|
2305
|
+
export {
|
|
2306
|
+
mcpAgents,
|
|
2307
|
+
mcpAgentAliases,
|
|
2308
|
+
getMcpAgentConfig,
|
|
2309
|
+
getMcpAgentTypes,
|
|
2310
|
+
isMcpAgentType,
|
|
2311
|
+
resolveMcpAgentAlias,
|
|
2312
|
+
isMcpTransportSupported,
|
|
2313
|
+
detectProjectInstalledMcpAgents,
|
|
2314
|
+
detectGloballyInstalledMcpAgents,
|
|
2315
|
+
getMcpAgentsSupportingProjectScope,
|
|
2316
|
+
DEFAULT_REMOTE_TRANSPORT,
|
|
2317
|
+
NPX_COMMAND,
|
|
2318
|
+
NPX_DASH_Y,
|
|
2319
|
+
buildMcpServerConfig,
|
|
2320
|
+
isRemoteServerConfig,
|
|
2321
|
+
isStdioServerConfig,
|
|
2322
|
+
parseServerConfig,
|
|
2323
|
+
readConfigFile,
|
|
2324
|
+
writeServerToConfigFile,
|
|
2325
|
+
removeServerFromConfigFile,
|
|
2326
|
+
listServersInConfigFile,
|
|
2327
|
+
resolveMcpConfigTarget,
|
|
2328
|
+
AgentConfigStore,
|
|
2329
|
+
agentConfigStore,
|
|
2330
|
+
toErrorMessage,
|
|
2331
|
+
transformServerConfig,
|
|
2332
|
+
createAgentTransform,
|
|
2333
|
+
transformServerConfigForAgent,
|
|
2334
|
+
installMcpServerForAgent,
|
|
2335
|
+
installMcpServerForAgents,
|
|
2336
|
+
parseMcpAgentList,
|
|
2337
|
+
resolveTargetAgents,
|
|
2338
|
+
extractPackageName,
|
|
2339
|
+
parseMcpSource,
|
|
2340
|
+
isRemoteMcpSource,
|
|
2341
|
+
installMcpServer,
|
|
2342
|
+
listInstalledMcpServers,
|
|
2343
|
+
removeMcpServerFromAgent,
|
|
2344
|
+
removeMcpServer,
|
|
2345
|
+
logger,
|
|
2346
|
+
promptScope,
|
|
2347
|
+
promptScopeAndAgents,
|
|
2348
|
+
parseArgsString,
|
|
2349
|
+
promptArgsConfig,
|
|
2350
|
+
parseEnvText,
|
|
2351
|
+
promptEnvConfig,
|
|
2352
|
+
parseHeadersText,
|
|
2353
|
+
promptHeadersConfig,
|
|
2354
|
+
wizardAdd,
|
|
2355
|
+
normalizeServerConfig,
|
|
2356
|
+
groupInstalledServersByName,
|
|
2357
|
+
wizardManage,
|
|
2358
|
+
wizardRemove,
|
|
2359
|
+
mainMenu
|
|
2360
|
+
};
|