@wuyax/mcps 0.1.0-beta.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1624 @@
1
+ import {
2
+ SECRET_HEADER_PATTERN,
3
+ SECRET_KEY_PATTERN,
4
+ agentConfigStore,
5
+ getMcpAgentConfig,
6
+ getMcpAgentTypes,
7
+ getMcpAgentsSupportingProjectScope,
8
+ installMcpServer,
9
+ maskSecretHeader,
10
+ maskSecretValue,
11
+ parseMcpSource,
12
+ queryGroupedInstalledServers,
13
+ removeMcpServer,
14
+ resolveTargetAgents,
15
+ toRemoteServerConfig,
16
+ toStdioServerConfig,
17
+ updateMcpServer
18
+ } from "./chunk-O3BRJFEC.js";
19
+
20
+ // src/interactive/utils/build-linked-agent-choices.ts
21
+ import pc from "picocolors";
22
+ var buildLinkedAgentChoices = (options) => {
23
+ const { agents, checkedAgents, detectedAgents = [], scopeOptions = {} } = options;
24
+ const alignedCheckedSet = new Set(checkedAgents);
25
+ for (const agent of checkedAgents) {
26
+ const coHosted = agentConfigStore.getCoHostedAgents(agent, scopeOptions);
27
+ for (const co of coHosted) {
28
+ if (agents.includes(co)) {
29
+ alignedCheckedSet.add(co);
30
+ }
31
+ }
32
+ }
33
+ return agents.map((agent) => {
34
+ const config = getMcpAgentConfig(agent);
35
+ const displayName = config?.displayName ?? agent;
36
+ const isDetected = detectedAgents.includes(agent);
37
+ const coHosted = agentConfigStore.getCoHostedAgents(agent, scopeOptions).filter(
38
+ (co) => agents.includes(co)
39
+ );
40
+ const detectedBadge = isDetected ? pc.green(" [detected]") : "";
41
+ const sharedBadge = coHosted.length > 0 ? pc.dim(` [shared: ${coHosted.join(", ")}]`) : "";
42
+ const label = `${displayName} ${pc.dim(`(${agent})`)}${detectedBadge}${sharedBadge}`;
43
+ return {
44
+ name: label,
45
+ value: agent,
46
+ checked: alignedCheckedSet.has(agent),
47
+ linkedValues: coHosted,
48
+ description: coHosted.length > 0 ? `Linked with ${coHosted.map((a) => getMcpAgentConfig(a).displayName).join(", ")} (shared configuration)` : void 0
49
+ };
50
+ });
51
+ };
52
+
53
+ // src/interactive/prompts/linked-checkbox.ts
54
+ import {
55
+ Separator,
56
+ ValidationError,
57
+ createPrompt,
58
+ isDownKey,
59
+ isEnterKey,
60
+ isNumberKey,
61
+ isSpaceKey,
62
+ isUpKey,
63
+ makeTheme,
64
+ useKeypress,
65
+ useMemo,
66
+ usePagination,
67
+ usePrefix,
68
+ useState
69
+ } from "@inquirer/core";
70
+ import pc2 from "picocolors";
71
+ var defaultTheme = {
72
+ icon: {
73
+ checked: pc2.green("[x]"),
74
+ unchecked: pc2.dim("[ ]"),
75
+ cursor: pc2.cyan(">"),
76
+ disabledChecked: pc2.dim("[x]"),
77
+ disabledUnchecked: pc2.dim("[-]")
78
+ },
79
+ style: {
80
+ disabled: (text) => pc2.dim(text),
81
+ renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", "),
82
+ description: (text) => pc2.cyan(text),
83
+ keysHelpTip: (keys) => keys.map(([key, action]) => `${pc2.bold(key)} ${pc2.dim(action)}`).join(pc2.dim(" | ")),
84
+ highlight: (text) => pc2.cyan(text)
85
+ },
86
+ i18n: {
87
+ disabledError: "This option is disabled and cannot be toggled."
88
+ }
89
+ };
90
+ function isSelectable(item) {
91
+ return !Separator.isSeparator(item) && !item.disabled;
92
+ }
93
+ function isNavigable(item) {
94
+ return !Separator.isSeparator(item);
95
+ }
96
+ function isChecked(item) {
97
+ return !Separator.isSeparator(item) && item.checked;
98
+ }
99
+ function normalizeChoices(choices) {
100
+ return choices.map((choice) => {
101
+ if (Separator.isSeparator(choice)) {
102
+ return choice;
103
+ }
104
+ if (typeof choice !== "object" || choice === null || !("value" in choice)) {
105
+ const name2 = String(choice);
106
+ return {
107
+ value: choice,
108
+ name: name2,
109
+ short: name2,
110
+ checkedName: name2,
111
+ disabled: false,
112
+ checked: false,
113
+ linkedValues: []
114
+ };
115
+ }
116
+ const name = choice.name ?? String(choice.value);
117
+ return {
118
+ value: choice.value,
119
+ name,
120
+ short: choice.short ?? name,
121
+ checkedName: choice.checkedName ?? name,
122
+ description: choice.description,
123
+ disabled: choice.disabled ?? false,
124
+ checked: choice.checked ?? false,
125
+ linkedValues: choice.linkedValues ?? []
126
+ };
127
+ });
128
+ }
129
+ var linkedCheckbox = createPrompt(
130
+ (config, done) => {
131
+ const { pageSize = 10, loop = true, required, validate = () => true } = config;
132
+ const theme = makeTheme(defaultTheme, config.theme);
133
+ const [status, setStatus] = useState("idle");
134
+ const prefix = usePrefix({ status, theme });
135
+ const [items, setItems] = useState(() => normalizeChoices(config.choices));
136
+ const bounds = useMemo(() => {
137
+ const first = items.findIndex(isNavigable);
138
+ let last = -1;
139
+ for (let i = items.length - 1; i >= 0; i--) {
140
+ if (isNavigable(items[i])) {
141
+ last = i;
142
+ break;
143
+ }
144
+ }
145
+ if (first === -1 || last === -1) {
146
+ throw new ValidationError("[linkedCheckbox prompt] No selectable choices.");
147
+ }
148
+ return { first, last };
149
+ }, [items]);
150
+ const [active, setActive] = useState(bounds.first);
151
+ const [errorMsg, setError] = useState();
152
+ const toggleWithLinked = (targetIndex) => {
153
+ const targetItem = items[targetIndex];
154
+ if (!targetItem || Separator.isSeparator(targetItem) || targetItem.disabled) {
155
+ return;
156
+ }
157
+ const nextChecked = !targetItem.checked;
158
+ const targetValue = targetItem.value;
159
+ const linked = new Set(targetItem.linkedValues);
160
+ setItems(
161
+ (prevItems) => prevItems.map((item) => {
162
+ if (Separator.isSeparator(item) || item.disabled) {
163
+ return item;
164
+ }
165
+ const isTargetOrLinked = item.value === targetValue || linked.has(item.value) || item.linkedValues.includes(targetValue);
166
+ if (isTargetOrLinked) {
167
+ return { ...item, checked: nextChecked };
168
+ }
169
+ return item;
170
+ })
171
+ );
172
+ };
173
+ useKeypress(async (key) => {
174
+ if (isEnterKey(key)) {
175
+ const selection = items.filter(isChecked);
176
+ const isValid = await validate([...selection]);
177
+ if (required && selection.length === 0) {
178
+ setError("At least one choice must be selected");
179
+ } else if (isValid === true) {
180
+ setStatus("done");
181
+ done(selection.map((choice) => choice.value));
182
+ } else {
183
+ setError(typeof isValid === "string" ? isValid : "You must select a valid value");
184
+ }
185
+ } else if (isUpKey(key) || isDownKey(key)) {
186
+ if (errorMsg) setError(void 0);
187
+ if (loop || isUpKey(key) && active !== bounds.first || isDownKey(key) && active !== bounds.last) {
188
+ const offset = isUpKey(key) ? -1 : 1;
189
+ let next = active;
190
+ do {
191
+ next = (next + offset + items.length) % items.length;
192
+ } while (!isNavigable(items[next]));
193
+ setActive(next);
194
+ }
195
+ } else if (isSpaceKey(key)) {
196
+ const activeItem = items[active];
197
+ if (activeItem && !Separator.isSeparator(activeItem)) {
198
+ if (activeItem.disabled) {
199
+ setError(theme.i18n.disabledError);
200
+ } else {
201
+ setError(void 0);
202
+ toggleWithLinked(active);
203
+ }
204
+ }
205
+ } else if (key.name === "a") {
206
+ const hasUnchecked = items.some((choice) => isSelectable(choice) && !choice.checked);
207
+ setItems(
208
+ (prevItems) => prevItems.map((item) => isSelectable(item) ? { ...item, checked: hasUnchecked } : item)
209
+ );
210
+ } else if (isNumberKey(key)) {
211
+ const selectedIndex = Number(key.name) - 1;
212
+ let selectableIndex = -1;
213
+ const position = items.findIndex((item) => {
214
+ if (Separator.isSeparator(item)) return false;
215
+ selectableIndex++;
216
+ return selectableIndex === selectedIndex;
217
+ });
218
+ const selectedItem = items[position];
219
+ if (selectedItem && isSelectable(selectedItem)) {
220
+ setActive(position);
221
+ setError(void 0);
222
+ toggleWithLinked(position);
223
+ }
224
+ }
225
+ });
226
+ const message = theme.style.message(config.message, status);
227
+ let description;
228
+ const page = usePagination({
229
+ items,
230
+ active,
231
+ renderItem({ item, isActive }) {
232
+ if (Separator.isSeparator(item)) {
233
+ return ` ${item.separator}`;
234
+ }
235
+ const cursor = isActive ? theme.icon.cursor : " ";
236
+ if (item.disabled) {
237
+ const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
238
+ const checkbox2 = item.checked ? theme.icon.disabledChecked : theme.icon.disabledUnchecked;
239
+ return theme.style.disabled(`${cursor} ${checkbox2} ${item.name} ${disabledLabel}`);
240
+ }
241
+ if (isActive) {
242
+ description = item.description;
243
+ }
244
+ const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
245
+ const name = item.checked ? item.checkedName : item.name;
246
+ const color = isActive ? theme.style.highlight : (x) => x;
247
+ return color(`${cursor} ${checkbox} ${name}`);
248
+ },
249
+ pageSize,
250
+ loop
251
+ });
252
+ if (status === "done") {
253
+ const selection = items.filter(isChecked);
254
+ const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
255
+ return [prefix, message, answer].filter(Boolean).join(" ");
256
+ }
257
+ const helpLine = theme.style.keysHelpTip([
258
+ ["up/down", "navigate"],
259
+ ["space", "toggle"],
260
+ ["a", "all"],
261
+ ["enter", "submit"]
262
+ ]);
263
+ const lines = [
264
+ [prefix, message].filter(Boolean).join(" "),
265
+ page,
266
+ helpLine
267
+ ];
268
+ if (description) {
269
+ lines.push(theme.style.description(description));
270
+ }
271
+ if (errorMsg) {
272
+ lines.push(theme.style.error(errorMsg));
273
+ }
274
+ return lines.join("\n");
275
+ }
276
+ );
277
+
278
+ // src/interactive/prompts/scope.ts
279
+ import { select } from "@inquirer/prompts";
280
+ import pc3 from "picocolors";
281
+ var promptScope = async (options = {}) => {
282
+ const initialGlobal = options.defaultGlobal ?? options.global;
283
+ if (initialGlobal !== void 0) {
284
+ return initialGlobal;
285
+ }
286
+ const cwd = options.cwd ?? process.cwd();
287
+ return select({
288
+ message: options.message ?? "Select MCP scope:",
289
+ choices: [
290
+ {
291
+ name: `Current Project - ${pc3.dim(cwd)}`,
292
+ value: false
293
+ },
294
+ {
295
+ name: `Global User Config - ${pc3.dim("applies across all projects")}`,
296
+ value: true
297
+ }
298
+ ]
299
+ });
300
+ };
301
+
302
+ // src/interactive/prompts/agents.ts
303
+ import pc5 from "picocolors";
304
+
305
+ // src/utils/logger.ts
306
+ import pc4 from "picocolors";
307
+ var logger = {
308
+ info: (message) => {
309
+ console.log(pc4.cyan("i"), message);
310
+ },
311
+ success: (message) => {
312
+ console.log(pc4.green("\u221A"), message);
313
+ },
314
+ warn: (message) => {
315
+ console.log(pc4.yellow("!"), message);
316
+ },
317
+ error: (message) => {
318
+ console.error(pc4.red("x"), message);
319
+ }
320
+ };
321
+
322
+ // src/interactive/prompts/agents.ts
323
+ var promptScopeAndAgents = async (options = {}) => {
324
+ const cwd = options.cwd ?? process.cwd();
325
+ const isGlobal = await promptScope({
326
+ cwd,
327
+ defaultGlobal: options.defaultGlobal,
328
+ message: "Select MCP installation scope:"
329
+ });
330
+ const resolution = resolveTargetAgents({
331
+ global: isGlobal,
332
+ cwd
333
+ });
334
+ const detected = resolution.detected;
335
+ const rawAvailable = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
336
+ const availableAgentTypes = agentConfigStore.sortAgentsByClusters(rawAvailable, {
337
+ global: isGlobal,
338
+ cwd
339
+ });
340
+ if (detected.length > 0) {
341
+ logger.info(
342
+ `Detected configured agents: ${pc5.cyan(detected.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
343
+ );
344
+ } else {
345
+ logger.warn(`No active ${isGlobal ? "global" : "project"} agents detected`);
346
+ }
347
+ const defaultChecked = options.defaultAgents && options.defaultAgents.length > 0 ? options.defaultAgents : detected;
348
+ const choices = buildLinkedAgentChoices({
349
+ agents: availableAgentTypes,
350
+ checkedAgents: defaultChecked,
351
+ detectedAgents: detected,
352
+ scopeOptions: { global: isGlobal, cwd }
353
+ });
354
+ const selectedAgents = await linkedCheckbox({
355
+ message: "Select target agents (Space to select, Enter to confirm):",
356
+ choices,
357
+ validate: (chosen) => {
358
+ if (chosen.length === 0) {
359
+ return "Please select at least one agent";
360
+ }
361
+ return true;
362
+ }
363
+ });
364
+ return {
365
+ global: isGlobal,
366
+ agents: selectedAgents
367
+ };
368
+ };
369
+
370
+ // src/interactive/prompts/args.ts
371
+ import { confirm, input } from "@inquirer/prompts";
372
+ var parseArgsString = (rawText) => {
373
+ const matches = rawText.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
374
+ if (!matches) return [];
375
+ return matches.map((arg) => {
376
+ if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
377
+ return arg.slice(1, -1);
378
+ }
379
+ return arg;
380
+ });
381
+ };
382
+ var promptArgsConfig = async (initialArgs = []) => {
383
+ if (initialArgs.length > 0) {
384
+ return initialArgs;
385
+ }
386
+ const needArgs = await confirm({
387
+ message: "Configure command arguments (e.g. file paths, connection strings)?",
388
+ default: false
389
+ });
390
+ if (!needArgs) {
391
+ return [];
392
+ }
393
+ const raw = await input({
394
+ message: "Enter command arguments (space-separated, wrap paths with spaces in quotes):",
395
+ validate: (val) => val.trim() ? true : "Arguments cannot be empty"
396
+ });
397
+ return parseArgsString(raw.trim());
398
+ };
399
+ var formatArgsString = (args) => {
400
+ return args.map((arg) => arg.includes(" ") || arg.includes('"') ? `"${arg.replace(/"/g, '\\"')}"` : arg).join(" ");
401
+ };
402
+ var promptEditArgs = async (currentArgs = []) => {
403
+ const defaultStr = formatArgsString(currentArgs);
404
+ const raw = await input({
405
+ message: "Edit command arguments (space-separated, wrap paths with spaces in quotes, leave empty to clear):",
406
+ default: defaultStr
407
+ });
408
+ const trimmed = raw.trim();
409
+ if (!trimmed) {
410
+ return [];
411
+ }
412
+ return parseArgsString(trimmed);
413
+ };
414
+
415
+ // src/interactive/prompts/multiline.ts
416
+ import { createInterface } from "readline";
417
+ import { editor } from "@inquirer/prompts";
418
+ import pc6 from "picocolors";
419
+ var readMultilineTextFromTerminal = async (message, endHint = "When done pasting, enter END on a new line or press Enter twice to finish") => {
420
+ console.log(pc6.cyan(`
421
+ ${message}`));
422
+ console.log(pc6.dim(` (Hint: ${endHint})
423
+ `));
424
+ return new Promise((resolve) => {
425
+ const rl = createInterface({
426
+ input: process.stdin,
427
+ output: process.stdout
428
+ });
429
+ const lines = [];
430
+ let consecutiveEmpty = 0;
431
+ const cleanup = () => {
432
+ rl.removeAllListeners();
433
+ rl.close();
434
+ };
435
+ rl.on("line", (line) => {
436
+ const trimmed = line.trim();
437
+ if (trimmed === "END") {
438
+ cleanup();
439
+ resolve(lines.join("\n"));
440
+ return;
441
+ }
442
+ if (line === "") {
443
+ consecutiveEmpty++;
444
+ if (lines.length > 0) {
445
+ cleanup();
446
+ resolve(lines.join("\n"));
447
+ return;
448
+ }
449
+ if (consecutiveEmpty >= 2) {
450
+ cleanup();
451
+ resolve("");
452
+ return;
453
+ }
454
+ } else {
455
+ consecutiveEmpty = 0;
456
+ lines.push(line);
457
+ }
458
+ });
459
+ rl.on("close", () => {
460
+ resolve(lines.join("\n"));
461
+ });
462
+ });
463
+ };
464
+ var promptEditorText = async (options) => {
465
+ try {
466
+ return await editor({
467
+ message: options.message,
468
+ default: options.defaultText ?? "",
469
+ postfix: options.postfix
470
+ });
471
+ } catch {
472
+ return readMultilineTextFromTerminal(options.message);
473
+ }
474
+ };
475
+
476
+ // src/interactive/prompts/kv.ts
477
+ import { confirm as confirm2, input as input2, password, select as select2 } from "@inquirer/prompts";
478
+ import pc7 from "picocolors";
479
+ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
480
+ let items = { ...currentItems };
481
+ while (true) {
482
+ const keys = Object.keys(items);
483
+ console.log();
484
+ if (keys.length === 0) {
485
+ console.log(pc7.dim(` No ${options.itemsNoun} configured.`));
486
+ } else {
487
+ console.log(pc7.cyan(pc7.bold(` Configured ${options.title} (${keys.length}):`)));
488
+ for (const [k, v] of Object.entries(items)) {
489
+ const sep = options.separator === "=" ? "=" : ": ";
490
+ console.log(` ${pc7.bold(k)}${sep}${pc7.dim(options.maskValue(k, v))}`);
491
+ }
492
+ }
493
+ console.log();
494
+ const choice = await select2({
495
+ message: `Manage ${options.itemsNoun}:`,
496
+ choices: [
497
+ {
498
+ name: "Open in system default editor ($EDITOR)",
499
+ value: "editor"
500
+ },
501
+ {
502
+ name: `Add or modify a ${options.itemNoun}`,
503
+ value: "upsert"
504
+ },
505
+ ...keys.length > 0 ? [
506
+ {
507
+ name: `Delete a ${options.itemNoun}`,
508
+ value: "delete"
509
+ }
510
+ ] : [],
511
+ {
512
+ name: `Paste multiline ${options.itemsNoun} into terminal`,
513
+ value: "paste"
514
+ },
515
+ ...keys.length > 0 ? [
516
+ {
517
+ name: `Clear all ${options.itemsNoun}`,
518
+ value: "clear"
519
+ }
520
+ ] : [],
521
+ {
522
+ name: `Done (finish editing ${options.itemsNoun})`,
523
+ value: "done"
524
+ }
525
+ ]
526
+ });
527
+ if (choice === "done") {
528
+ return items;
529
+ }
530
+ if (choice === "editor") {
531
+ const defaultText = options.formatText(items);
532
+ const text = await promptEditorText({
533
+ message: options.editorMessage,
534
+ postfix: options.editorPostfix,
535
+ defaultText
536
+ });
537
+ const parsed = options.parseText(text);
538
+ items = parsed;
539
+ logger.success(`${options.title} updated (${Object.keys(items).length} total)`);
540
+ } else if (choice === "upsert") {
541
+ const key = await input2({
542
+ message: options.keyPromptMessage,
543
+ validate: (val) => {
544
+ const trimmed = val.trim();
545
+ if (!trimmed) return `${options.itemNoun} name cannot be empty`;
546
+ if (/\s/.test(trimmed)) return `${options.itemNoun} name cannot contain spaces`;
547
+ return true;
548
+ }
549
+ });
550
+ const trimmedKey = key.trim();
551
+ const existingVal = items[trimmedKey];
552
+ const isSecret = options.isSecretKey(trimmedKey);
553
+ let newVal;
554
+ if (isSecret) {
555
+ newVal = await password({
556
+ message: existingVal !== void 0 ? `New value for (${trimmedKey}) [leave empty to keep current]:` : `${options.valuePromptMessage} for (${trimmedKey}) [sensitive content masked]:`,
557
+ mask: "*"
558
+ });
559
+ if (existingVal !== void 0 && newVal === "") {
560
+ newVal = existingVal;
561
+ }
562
+ } else {
563
+ newVal = await input2({
564
+ message: `${options.valuePromptMessage} for (${trimmedKey}):`,
565
+ default: existingVal
566
+ });
567
+ }
568
+ items[trimmedKey] = newVal;
569
+ logger.success(`${existingVal !== void 0 ? "Updated" : "Added"}: ${pc7.cyan(trimmedKey)}`);
570
+ } else if (choice === "delete") {
571
+ const toDelete = await select2({
572
+ message: `Select ${options.itemNoun} to delete:`,
573
+ choices: [
574
+ ...keys.map((k) => ({ name: k, value: k })),
575
+ { name: "Cancel", value: "__cancel__" }
576
+ ]
577
+ });
578
+ if (toDelete !== "__cancel__") {
579
+ delete items[toDelete];
580
+ logger.success(`Deleted: ${pc7.cyan(toDelete)}`);
581
+ }
582
+ } else if (choice === "paste") {
583
+ const pasted = await readMultilineTextFromTerminal(options.pasteMessage);
584
+ const parsed = options.parseText(pasted);
585
+ const count = Object.keys(parsed).length;
586
+ if (count === 0) {
587
+ logger.warn(`No valid ${options.itemsNoun} recognized`);
588
+ } else {
589
+ if (keys.length > 0) {
590
+ const pasteMode = await select2({
591
+ message: `How to apply pasted ${options.itemsNoun}?`,
592
+ choices: [
593
+ { name: `Merge with existing ${options.itemsNoun}`, value: "merge" },
594
+ { name: `Replace all existing ${options.itemsNoun}`, value: "replace" }
595
+ ]
596
+ });
597
+ if (pasteMode === "replace") {
598
+ items = parsed;
599
+ } else {
600
+ Object.assign(items, parsed);
601
+ }
602
+ } else {
603
+ items = parsed;
604
+ }
605
+ logger.success(`Successfully applied ${pc7.cyan(String(count))} ${options.itemsNoun}`);
606
+ }
607
+ } else if (choice === "clear") {
608
+ const confirmClear = await confirm2({
609
+ message: `Are you sure you want to clear all ${options.itemsNoun}?`,
610
+ default: false
611
+ });
612
+ if (confirmClear) {
613
+ items = {};
614
+ logger.success(`Cleared all ${options.itemsNoun}`);
615
+ }
616
+ }
617
+ }
618
+ };
619
+
620
+ // src/interactive/prompts/env.ts
621
+ import { input as input3, password as password2, select as select3 } from "@inquirer/prompts";
622
+ import pc8 from "picocolors";
623
+ var formatEnvText = (env) => {
624
+ return Object.entries(env).map(([key, value]) => {
625
+ if (/[\s"']/.test(value)) {
626
+ return `${key}="${value.replace(/"/g, '\\"')}"`;
627
+ }
628
+ return `${key}=${value}`;
629
+ }).join("\n");
630
+ };
631
+ var parseEnvText = (rawText) => {
632
+ const result = {};
633
+ const lines = rawText.split(/\r?\n/);
634
+ for (const rawLine of lines) {
635
+ let line = rawLine.trim();
636
+ if (!line || line.startsWith("#")) continue;
637
+ if (line.startsWith("export ")) {
638
+ line = line.slice(7).trim();
639
+ }
640
+ const eqIndex = line.indexOf("=");
641
+ if (eqIndex === -1) continue;
642
+ const key = line.slice(0, eqIndex).trim();
643
+ let value = line.slice(eqIndex + 1).trim();
644
+ if (!key) continue;
645
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
646
+ value = value.slice(1, -1);
647
+ }
648
+ result[key] = value;
649
+ }
650
+ return result;
651
+ };
652
+ var promptEnvConfig = async (initialEnv = {}) => {
653
+ const env = { ...initialEnv };
654
+ const initialCount = Object.keys(env).length;
655
+ if (initialCount > 0) {
656
+ logger.info(`Includes ${pc8.cyan(String(initialCount))} preset environment variables`);
657
+ }
658
+ const mode = await select3({
659
+ message: "Configure environment variables?",
660
+ choices: [
661
+ {
662
+ name: "Skip / None",
663
+ value: "skip"
664
+ },
665
+ {
666
+ name: "Paste multiline .env text into terminal",
667
+ value: "paste"
668
+ },
669
+ {
670
+ name: "Open in system default editor ($EDITOR)",
671
+ value: "editor"
672
+ },
673
+ {
674
+ name: "Enter key-value pairs one by one",
675
+ value: "manual"
676
+ }
677
+ ]
678
+ });
679
+ if (mode === "skip") {
680
+ return env;
681
+ }
682
+ if (mode === "paste" || mode === "editor") {
683
+ const pasted = mode === "editor" ? await promptEditorText({
684
+ message: "Paste or edit environment variables in editor, then save and exit:",
685
+ postfix: ".env",
686
+ defaultText: formatEnvText(env)
687
+ }) : await readMultilineTextFromTerminal("Paste .env formatted content (multiline supported):");
688
+ const parsed = parseEnvText(pasted);
689
+ const count = Object.keys(parsed).length;
690
+ if (count === 0) {
691
+ logger.warn("No valid KEY=VALUE pairs recognized");
692
+ } else {
693
+ Object.assign(env, parsed);
694
+ logger.success(`Successfully parsed ${pc8.cyan(String(count))} environment variables:`);
695
+ for (const [k, v] of Object.entries(parsed)) {
696
+ console.log(` ${pc8.bold(k)}=${pc8.dim(maskSecretValue(k, v))}`);
697
+ }
698
+ }
699
+ return env;
700
+ }
701
+ logger.info("Entering environment variables (leave key empty and press enter to finish):");
702
+ while (true) {
703
+ const key = await input3({
704
+ message: "Variable name (Key, leave empty to finish):",
705
+ validate: (val2) => {
706
+ const trimmed = val2.trim();
707
+ if (!trimmed) return true;
708
+ if (/\s/.test(trimmed)) return "Variable name cannot contain spaces";
709
+ return true;
710
+ }
711
+ });
712
+ const trimmedKey = key.trim();
713
+ if (!trimmedKey) break;
714
+ const isSecret = SECRET_KEY_PATTERN.test(trimmedKey);
715
+ let val;
716
+ if (isSecret) {
717
+ val = await password2({
718
+ message: `Value for (${trimmedKey}) [secret masked]:`,
719
+ mask: "*"
720
+ });
721
+ } else {
722
+ val = await input3({
723
+ message: `Value for (${trimmedKey}):`
724
+ });
725
+ }
726
+ env[trimmedKey] = val;
727
+ logger.success(`Added: ${pc8.cyan(trimmedKey)}`);
728
+ }
729
+ return env;
730
+ };
731
+ var promptEditEnvConfig = async (currentEnv = {}) => promptEditKeyValueConfig(currentEnv, {
732
+ title: "Environment Variables",
733
+ itemNoun: "variable",
734
+ itemsNoun: "environment variables",
735
+ separator: "=",
736
+ editorPostfix: ".env",
737
+ editorMessage: "Edit environment variables in editor, then save and exit:",
738
+ pasteMessage: "Paste .env formatted content (multiline supported):",
739
+ keyPromptMessage: "Variable name (Key):",
740
+ valuePromptMessage: "Value",
741
+ isSecretKey: (k) => SECRET_KEY_PATTERN.test(k),
742
+ maskValue: maskSecretValue,
743
+ formatText: formatEnvText,
744
+ parseText: parseEnvText
745
+ });
746
+
747
+ // src/interactive/prompts/headers.ts
748
+ import { input as input4, password as password3, select as select4 } from "@inquirer/prompts";
749
+ import pc9 from "picocolors";
750
+ var formatHeadersText = (headers) => {
751
+ return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
752
+ };
753
+ var parseHeadersText = (rawText) => {
754
+ const result = {};
755
+ const lines = rawText.split(/\r?\n/);
756
+ for (const rawLine of lines) {
757
+ const line = rawLine.trim();
758
+ if (!line || line.startsWith("#")) continue;
759
+ const colonIndex = line.indexOf(":");
760
+ const equalIndex = line.indexOf("=");
761
+ let splitIndex = -1;
762
+ if (colonIndex !== -1 && equalIndex !== -1) {
763
+ splitIndex = Math.min(colonIndex, equalIndex);
764
+ } else if (colonIndex !== -1) {
765
+ splitIndex = colonIndex;
766
+ } else {
767
+ splitIndex = equalIndex;
768
+ }
769
+ if (splitIndex === -1) continue;
770
+ const key = line.slice(0, splitIndex).trim();
771
+ let value = line.slice(splitIndex + 1).trim();
772
+ if (!key) continue;
773
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
774
+ value = value.slice(1, -1);
775
+ }
776
+ result[key] = value;
777
+ }
778
+ return result;
779
+ };
780
+ var promptHeadersConfig = async (initialHeaders = {}) => {
781
+ const headers = { ...initialHeaders };
782
+ const mode = await select4({
783
+ message: "Select HTTP headers configuration method:",
784
+ choices: [
785
+ {
786
+ name: "Paste multiline headers into terminal (Key: Value format)",
787
+ value: "paste"
788
+ },
789
+ {
790
+ name: "Open in system default editor ($EDITOR)",
791
+ value: "editor"
792
+ },
793
+ {
794
+ name: "Enter headers one by one (e.g. Authorization: Bearer ...)",
795
+ value: "manual"
796
+ },
797
+ {
798
+ name: "Skip / None",
799
+ value: "skip"
800
+ }
801
+ ]
802
+ });
803
+ if (mode === "skip") {
804
+ return headers;
805
+ }
806
+ if (mode === "paste" || mode === "editor") {
807
+ const pasted = mode === "editor" ? await promptEditorText({
808
+ message: "Paste or edit HTTP headers in editor, then save and exit:",
809
+ defaultText: formatHeadersText(headers)
810
+ }) : await readMultilineTextFromTerminal(
811
+ "Paste HTTP headers content (multiline supported, e.g. Authorization: Bearer ...):"
812
+ );
813
+ const parsed = parseHeadersText(pasted);
814
+ const count = Object.keys(parsed).length;
815
+ if (count === 0) {
816
+ logger.warn("No valid Key: Value pairs recognized");
817
+ } else {
818
+ Object.assign(headers, parsed);
819
+ logger.success(`Successfully parsed ${pc9.cyan(String(count))} headers:`);
820
+ for (const [k, v] of Object.entries(parsed)) {
821
+ console.log(` ${pc9.bold(k)}: ${pc9.dim(maskSecretHeader(k, v))}`);
822
+ }
823
+ }
824
+ return headers;
825
+ }
826
+ logger.info("Entering HTTP headers (leave header name empty and press enter to finish):");
827
+ while (true) {
828
+ const name = await input4({
829
+ message: "Header name (e.g. Authorization, leave empty to finish):",
830
+ validate: (val2) => {
831
+ const trimmed = val2.trim();
832
+ if (!trimmed) return true;
833
+ if (/\s/.test(trimmed)) return "Header name cannot contain spaces";
834
+ return true;
835
+ }
836
+ });
837
+ const trimmedName = name.trim();
838
+ if (!trimmedName) break;
839
+ const isSecret = SECRET_HEADER_PATTERN.test(trimmedName);
840
+ let val;
841
+ if (isSecret) {
842
+ val = await password3({
843
+ message: `Header value for (${trimmedName}) [sensitive content masked]:`,
844
+ mask: "*"
845
+ });
846
+ } else {
847
+ val = await input4({
848
+ message: `Header value for (${trimmedName}):`
849
+ });
850
+ }
851
+ headers[trimmedName] = val;
852
+ logger.success(`Added: ${pc9.cyan(trimmedName)}`);
853
+ }
854
+ return headers;
855
+ };
856
+ var promptEditHeadersConfig = async (currentHeaders = {}) => promptEditKeyValueConfig(currentHeaders, {
857
+ title: "HTTP Headers",
858
+ itemNoun: "header",
859
+ itemsNoun: "HTTP headers",
860
+ separator: ":",
861
+ editorMessage: "Edit HTTP headers in editor, then save and exit:",
862
+ pasteMessage: "Paste HTTP headers content (multiline supported, e.g. Authorization: Bearer ...):",
863
+ keyPromptMessage: "Header name (e.g. Authorization):",
864
+ valuePromptMessage: "Header value",
865
+ isSecretKey: (k) => SECRET_HEADER_PATTERN.test(k),
866
+ maskValue: maskSecretHeader,
867
+ formatText: formatHeadersText,
868
+ parseText: parseHeadersText
869
+ });
870
+
871
+ // src/interactive/wizard-add.ts
872
+ import { confirm as confirm5, input as input5, select as select5 } from "@inquirer/prompts";
873
+ import pc11 from "picocolors";
874
+
875
+ // src/utils/co-hosted-feedback.ts
876
+ import pc10 from "picocolors";
877
+ var formatCoHostedBadge = (kind, agents) => {
878
+ if (!agents || agents.length === 0) return "";
879
+ const label = kind === "configured" ? "co-configured" : "co-affected";
880
+ return ` ${pc10.yellow(`(${label}: ${agents.join(", ")})`)}`;
881
+ };
882
+ var logCoHostedNotice = (kind, agents) => {
883
+ if (!agents || agents.length === 0) return;
884
+ const actionText = kind === "configured" ? "Also configured for" : "Also affects";
885
+ logger.info(
886
+ ` ${pc10.dim("Note:")} ${actionText} co-hosted agent(s): ${pc10.yellow(agents.join(", "))}`
887
+ );
888
+ };
889
+
890
+ // src/interactive/wizard-add.ts
891
+ var wizardAdd = async (initial = {}) => {
892
+ const cwd = initial.cwd ?? process.cwd();
893
+ logger.info(pc11.bold("Welcome to the MCP interactive add wizard"));
894
+ let source = initial.source;
895
+ if (!source) {
896
+ const sourceType = await select5({
897
+ message: "Select MCP server type:",
898
+ choices: [
899
+ {
900
+ name: "npm package (run via npx)",
901
+ value: "npm"
902
+ },
903
+ {
904
+ name: "Remote MCP server (via HTTP / SSE URL)",
905
+ value: "remote"
906
+ },
907
+ {
908
+ name: "Local command / script / Docker (stdio)",
909
+ value: "command"
910
+ }
911
+ ]
912
+ });
913
+ if (sourceType === "npm") {
914
+ source = await input5({
915
+ message: "Enter npm package name (e.g. @modelcontextprotocol/server-postgres):",
916
+ validate: (val) => val.trim() ? true : "Package name cannot be empty"
917
+ });
918
+ } else if (sourceType === "remote") {
919
+ source = await input5({
920
+ message: "Enter remote server URL (e.g. https://mcp.example.com/sse):",
921
+ validate: (val) => {
922
+ const trimmed = val.trim();
923
+ if (!trimmed) return "URL cannot be empty";
924
+ if (!/^https?:\/\//i.test(trimmed)) return "Please enter a valid URL starting with http:// or https://";
925
+ return true;
926
+ }
927
+ });
928
+ } else {
929
+ source = await input5({
930
+ message: "Enter command and arguments (e.g. python -m my_mcp_server or docker run ...):",
931
+ validate: (val) => val.trim() ? true : "Command cannot be empty"
932
+ });
933
+ }
934
+ }
935
+ source = source.trim();
936
+ const parsed = parseMcpSource(source);
937
+ let serverName = initial.name;
938
+ if (!serverName) {
939
+ serverName = await input5({
940
+ message: "MCP server name:",
941
+ default: parsed.inferredName,
942
+ validate: (val) => val.trim() ? true : "Server name cannot be empty"
943
+ });
944
+ }
945
+ serverName = serverName.trim();
946
+ let transport = initial.transport;
947
+ let headers = initial.headers ?? {};
948
+ if (parsed.type === "remote") {
949
+ if (!transport) {
950
+ const isSseUrl = /\/sse\b/i.test(parsed.value);
951
+ transport = await select5({
952
+ message: "Select remote transport protocol:",
953
+ choices: [
954
+ { name: "HTTP", value: "http" },
955
+ { name: "SSE (Server-Sent Events)", value: "sse" }
956
+ ],
957
+ default: isSseUrl ? "sse" : "http"
958
+ });
959
+ }
960
+ if (Object.keys(headers).length === 0) {
961
+ const needHeader = await confirm5({
962
+ message: "Configure HTTP headers (e.g. Authorization Bearer token)?",
963
+ default: false
964
+ });
965
+ if (needHeader) {
966
+ headers = await promptHeadersConfig();
967
+ }
968
+ }
969
+ }
970
+ const { global: isGlobal, agents: selectedAgents } = await promptScopeAndAgents({
971
+ cwd,
972
+ defaultGlobal: initial.global,
973
+ defaultAgents: initial.agents
974
+ });
975
+ let args = initial.args ?? [];
976
+ if (parsed.type !== "remote") {
977
+ args = await promptArgsConfig(args);
978
+ }
979
+ let env = initial.env ?? {};
980
+ if (parsed.type !== "remote") {
981
+ env = await promptEnvConfig(env);
982
+ }
983
+ console.log("\n" + pc11.cyan(pc11.bold("Configuration Preview:")));
984
+ console.log(` ${pc11.bold("Server Name:")} ${pc11.green(serverName)}`);
985
+ console.log(` ${pc11.bold("Server Type:")} ${pc11.magenta(parsed.type)}`);
986
+ console.log(` ${pc11.bold("Source/Command:")} ${pc11.dim(source)}`);
987
+ console.log(` ${pc11.bold("Scope:")} ${isGlobal ? pc11.yellow("Global") : pc11.blue("Project")}`);
988
+ console.log(` ${pc11.bold("Target Agents:")} ${pc11.cyan(selectedAgents.join(", "))}`);
989
+ if (args.length > 0) {
990
+ console.log(` ${pc11.bold("Arguments:")} ${pc11.dim(args.join(" "))}`);
991
+ }
992
+ if (transport) {
993
+ console.log(` ${pc11.bold("Transport:")} ${pc11.magenta(transport)}`);
994
+ }
995
+ const envKeys = Object.keys(env);
996
+ if (envKeys.length > 0) {
997
+ console.log(` ${pc11.bold("Environment Variables:")} ${pc11.dim(envKeys.join(", "))} (${envKeys.length})`);
998
+ }
999
+ const headerKeys = Object.keys(headers);
1000
+ if (headerKeys.length > 0) {
1001
+ console.log(` ${pc11.bold("Headers:")} ${pc11.dim(headerKeys.join(", "))} (${headerKeys.length})`);
1002
+ }
1003
+ console.log();
1004
+ const proceed = await confirm5({
1005
+ message: "Confirm installation with this configuration?",
1006
+ default: true
1007
+ });
1008
+ if (!proceed) {
1009
+ logger.warn("Operation cancelled");
1010
+ return false;
1011
+ }
1012
+ const result = installMcpServer({
1013
+ source,
1014
+ name: serverName,
1015
+ agents: selectedAgents,
1016
+ args,
1017
+ global: isGlobal,
1018
+ cwd,
1019
+ transport,
1020
+ headers,
1021
+ env
1022
+ });
1023
+ logger.info(
1024
+ `Writing ${pc11.bold(result.serverName)} to ${pc11.cyan(String(result.results.length))} agent config files...`
1025
+ );
1026
+ let allSuccess = true;
1027
+ for (const record of result.results) {
1028
+ if (record.success) {
1029
+ logger.success(
1030
+ `${pc11.cyan(record.agent)}: Successfully written to ${pc11.dim(record.path)}${formatCoHostedBadge("configured", record.coConfiguredAgents)}`
1031
+ );
1032
+ } else {
1033
+ allSuccess = false;
1034
+ logger.error(`${pc11.cyan(record.agent)}: Failed to write - ${record.error}`);
1035
+ }
1036
+ }
1037
+ if (allSuccess) {
1038
+ logger.success(pc11.bold(`MCP server "${serverName}" configured successfully!`));
1039
+ }
1040
+ return allSuccess;
1041
+ };
1042
+
1043
+ // src/interactive/wizard-manage.ts
1044
+ import { confirm as confirm6, input as input6, select as select6 } from "@inquirer/prompts";
1045
+ import pc13 from "picocolors";
1046
+
1047
+ // src/utils/display-server-details.ts
1048
+ import pc12 from "picocolors";
1049
+ var displayServerDetails = ({
1050
+ serverName,
1051
+ config,
1052
+ agents,
1053
+ hasDivergence,
1054
+ global: isGlobal,
1055
+ titlePrefix = "MCP Server Details"
1056
+ }) => {
1057
+ console.log("\n" + pc12.cyan(pc12.bold(`${titlePrefix}: [${serverName}]`)));
1058
+ if (isGlobal !== void 0) {
1059
+ console.log(` ${pc12.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
1060
+ }
1061
+ if (agents && agents.length > 0) {
1062
+ console.log(
1063
+ ` ${pc12.bold("Configured Agents:")} ${pc12.green(agents.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
1064
+ );
1065
+ }
1066
+ if (hasDivergence) {
1067
+ console.log(
1068
+ ` ${pc12.yellow(pc12.bold("Notice:"))} ${pc12.yellow("Configurations differ across installed agents. Showing configuration from the first agent.")}`
1069
+ );
1070
+ }
1071
+ const isRemote = Boolean(config.url && config.url.length > 0);
1072
+ if (isRemote) {
1073
+ console.log(` ${pc12.bold("Transport:")} ${pc12.magenta(config.type ?? "http")}`);
1074
+ console.log(` ${pc12.bold("URL:")} ${pc12.dim(config.url ?? "")}`);
1075
+ const headerKeys = Object.keys(config.headers ?? {});
1076
+ if (headerKeys.length > 0) {
1077
+ console.log(` ${pc12.bold("Headers:")} ${pc12.cyan(String(headerKeys.length))}`);
1078
+ for (const [k, v] of Object.entries(config.headers ?? {})) {
1079
+ console.log(` ${pc12.bold(k)}: ${pc12.dim(maskSecretHeader(k, v))}`);
1080
+ }
1081
+ } else {
1082
+ console.log(` ${pc12.bold("Headers:")} ${pc12.dim("(none)")}`);
1083
+ }
1084
+ } else {
1085
+ console.log(` ${pc12.bold("Command:")} ${pc12.magenta(config.command ?? "")}`);
1086
+ const argsStr = config.args && config.args.length > 0 ? config.args.join(" ") : "(none)";
1087
+ console.log(` ${pc12.bold("Arguments:")} ${pc12.dim(argsStr)}`);
1088
+ const envKeys = Object.keys(config.env ?? {});
1089
+ if (envKeys.length > 0) {
1090
+ console.log(` ${pc12.bold("Environment Variables:")} ${pc12.cyan(String(envKeys.length))}`);
1091
+ for (const [k, v] of Object.entries(config.env ?? {})) {
1092
+ console.log(` ${pc12.bold(k)}=${pc12.dim(maskSecretValue(k, v))}`);
1093
+ }
1094
+ } else {
1095
+ console.log(` ${pc12.bold("Environment Variables:")} ${pc12.dim("(none)")}`);
1096
+ }
1097
+ }
1098
+ console.log();
1099
+ };
1100
+
1101
+ // src/interactive/wizard-manage.ts
1102
+ var promptSwitchServerType = async (currentConfig, serverName) => {
1103
+ const isRemote = Boolean(currentConfig.url && currentConfig.url.length > 0);
1104
+ if (isRemote) {
1105
+ const newCmd = await input6({
1106
+ message: "Executable command (e.g. node, npx):",
1107
+ validate: (val) => val.trim() ? true : "Command cannot be empty"
1108
+ });
1109
+ const newArgs = await promptEditArgs([]);
1110
+ const newEnv = await promptEditEnvConfig({});
1111
+ logger.success(`Switched [${serverName}] configuration to stdio mode`);
1112
+ return toStdioServerConfig({
1113
+ command: newCmd.trim(),
1114
+ args: newArgs.length > 0 ? newArgs : void 0,
1115
+ env: Object.keys(newEnv).length > 0 ? newEnv : void 0
1116
+ });
1117
+ }
1118
+ const newUrl = await input6({
1119
+ message: "Remote server URL:",
1120
+ validate: (val) => {
1121
+ const trimmed = val.trim();
1122
+ if (!trimmed) return "URL cannot be empty";
1123
+ if (!/^https?:\/\//i.test(trimmed)) {
1124
+ return "Please enter a valid URL starting with http:// or https://";
1125
+ }
1126
+ return true;
1127
+ }
1128
+ });
1129
+ const transport = await select6({
1130
+ message: "Select remote transport protocol:",
1131
+ choices: [
1132
+ { name: "HTTP", value: "http" },
1133
+ { name: "SSE (Server-Sent Events)", value: "sse" }
1134
+ ],
1135
+ default: "http"
1136
+ });
1137
+ const newHeaders = await promptEditHeadersConfig({});
1138
+ logger.success(`Switched [${serverName}] configuration to remote mode`);
1139
+ return toRemoteServerConfig(
1140
+ {
1141
+ url: newUrl.trim(),
1142
+ headers: Object.keys(newHeaders).length > 0 ? newHeaders : void 0
1143
+ },
1144
+ transport
1145
+ );
1146
+ };
1147
+ var handleEditServerConfig = async (options) => {
1148
+ const { targetGroup } = options;
1149
+ const isGlobal = options.global ?? false;
1150
+ const cwd = options.cwd ?? process.cwd();
1151
+ const serverName = targetGroup.serverName;
1152
+ let workingConfig = {
1153
+ ...targetGroup.config,
1154
+ args: targetGroup.config.args ? [...targetGroup.config.args] : void 0,
1155
+ env: targetGroup.config.env ? { ...targetGroup.config.env } : void 0,
1156
+ headers: targetGroup.config.headers ? { ...targetGroup.config.headers } : void 0
1157
+ };
1158
+ while (true) {
1159
+ const isRemote = Boolean(workingConfig.url && workingConfig.url.length > 0);
1160
+ displayServerDetails({
1161
+ serverName,
1162
+ config: workingConfig,
1163
+ titlePrefix: "Edit Server Configuration"
1164
+ });
1165
+ const editChoices = isRemote ? [
1166
+ { name: "Edit HTTP Headers (headers)", value: "headers" },
1167
+ { name: "Edit Remote URL (url)", value: "url" },
1168
+ { name: "Edit Transport Protocol (type)", value: "transport" },
1169
+ { name: "Switch to local command (stdio)", value: "switch_type" },
1170
+ { name: "Reset changes to original", value: "reset" },
1171
+ { name: "Save and apply changes", value: "save" },
1172
+ { name: "Cancel (discard changes)", value: "cancel" }
1173
+ ] : [
1174
+ { name: "Edit Environment Variables (env)", value: "env" },
1175
+ { name: "Edit Command Arguments (args)", value: "args" },
1176
+ { name: "Edit Executable Command (command)", value: "command" },
1177
+ { name: "Switch to remote server (HTTP/SSE)", value: "switch_type" },
1178
+ { name: "Reset changes to original", value: "reset" },
1179
+ { name: "Save and apply changes", value: "save" },
1180
+ { name: "Cancel (discard changes)", value: "cancel" }
1181
+ ];
1182
+ const editAction = await select6({
1183
+ message: `What would you like to modify in [${serverName}]?`,
1184
+ choices: editChoices
1185
+ });
1186
+ if (editAction === "cancel") {
1187
+ logger.info("Modification cancelled; changes discarded");
1188
+ return;
1189
+ }
1190
+ if (editAction === "reset") {
1191
+ workingConfig = {
1192
+ ...targetGroup.config,
1193
+ args: targetGroup.config.args ? [...targetGroup.config.args] : void 0,
1194
+ env: targetGroup.config.env ? { ...targetGroup.config.env } : void 0,
1195
+ headers: targetGroup.config.headers ? { ...targetGroup.config.headers } : void 0
1196
+ };
1197
+ logger.info("Configuration reset to original");
1198
+ continue;
1199
+ }
1200
+ if (editAction === "switch_type") {
1201
+ workingConfig = await promptSwitchServerType(workingConfig, serverName);
1202
+ continue;
1203
+ }
1204
+ if (editAction === "env") {
1205
+ workingConfig.env = await promptEditEnvConfig(workingConfig.env ?? {});
1206
+ } else if (editAction === "args") {
1207
+ workingConfig.args = await promptEditArgs(workingConfig.args ?? []);
1208
+ } else if (editAction === "command") {
1209
+ const newCmd = await input6({
1210
+ message: "Executable command:",
1211
+ default: workingConfig.command,
1212
+ validate: (val) => val.trim() ? true : "Command cannot be empty"
1213
+ });
1214
+ workingConfig.command = newCmd.trim();
1215
+ } else if (editAction === "headers") {
1216
+ workingConfig.headers = await promptEditHeadersConfig(workingConfig.headers ?? {});
1217
+ } else if (editAction === "url") {
1218
+ const newUrl = await input6({
1219
+ message: "Remote server URL:",
1220
+ default: workingConfig.url,
1221
+ validate: (val) => {
1222
+ const trimmed = val.trim();
1223
+ if (!trimmed) return "URL cannot be empty";
1224
+ if (!/^https?:\/\//i.test(trimmed)) {
1225
+ return "Please enter a valid URL starting with http:// or https://";
1226
+ }
1227
+ return true;
1228
+ }
1229
+ });
1230
+ workingConfig.url = newUrl.trim();
1231
+ } else if (editAction === "transport") {
1232
+ workingConfig.type = await select6({
1233
+ message: "Select remote transport protocol:",
1234
+ choices: [
1235
+ { name: "HTTP", value: "http" },
1236
+ { name: "SSE (Server-Sent Events)", value: "sse" }
1237
+ ],
1238
+ default: workingConfig.type === "sse" ? "sse" : "http"
1239
+ });
1240
+ } else if (editAction === "save") {
1241
+ let targetAgents = targetGroup.agents;
1242
+ if (targetGroup.agents.length > 1) {
1243
+ const sortedAgents = agentConfigStore.sortAgentsByClusters(targetGroup.agents, { global: isGlobal, cwd });
1244
+ const choices = buildLinkedAgentChoices({
1245
+ agents: sortedAgents,
1246
+ checkedAgents: sortedAgents,
1247
+ scopeOptions: { global: isGlobal, cwd }
1248
+ });
1249
+ targetAgents = await linkedCheckbox({
1250
+ message: "Select agents to update configuration (Space to toggle):",
1251
+ choices,
1252
+ loop: false,
1253
+ validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
1254
+ });
1255
+ if (targetAgents.length < targetGroup.agents.length) {
1256
+ const unselected = targetGroup.agents.filter((a) => !targetAgents.includes(a));
1257
+ const unselectedNames = unselected.map((a) => getMcpAgentConfig(a).displayName).join(", ");
1258
+ logger.info(
1259
+ `Note: Updating only a subset of agents. Server configurations will diverge from: ${unselectedNames}.`
1260
+ );
1261
+ }
1262
+ }
1263
+ const requestedTransport = workingConfig.url ? workingConfig.type ?? "http" : "stdio";
1264
+ const resolution = resolveTargetAgents({
1265
+ requested: targetAgents,
1266
+ global: isGlobal,
1267
+ cwd,
1268
+ transport: requestedTransport
1269
+ });
1270
+ if (resolution.incompatible.length > 0) {
1271
+ for (const item of resolution.incompatible) {
1272
+ logger.warn(`Skipping ${pc13.cyan(item.agent)}: ${item.reason}`);
1273
+ }
1274
+ }
1275
+ if (resolution.compatibleAgents.length === 0) {
1276
+ logger.error(
1277
+ `None of the selected agents support ${requestedTransport} transport. Cannot update.`
1278
+ );
1279
+ continue;
1280
+ }
1281
+ const agentNames = resolution.compatibleAgents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
1282
+ const confirmed = await confirm6({
1283
+ message: `Confirm updating configuration for [${serverName}] across: ${agentNames}?`,
1284
+ default: true
1285
+ });
1286
+ if (!confirmed) {
1287
+ logger.warn("Update cancelled");
1288
+ continue;
1289
+ }
1290
+ const updateResult = updateMcpServer({
1291
+ serverName,
1292
+ config: workingConfig,
1293
+ previousConfig: targetGroup.config,
1294
+ agents: resolution.compatibleAgents,
1295
+ global: isGlobal,
1296
+ cwd
1297
+ });
1298
+ let updatedAny = false;
1299
+ const succeededAgents = [];
1300
+ for (const res of updateResult.results) {
1301
+ if (res.success) {
1302
+ updatedAny = true;
1303
+ succeededAgents.push(res.agent);
1304
+ logger.success(
1305
+ `${pc13.cyan(res.agent)}: Successfully updated configuration in ${pc13.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
1306
+ );
1307
+ } else {
1308
+ logger.error(`${pc13.cyan(res.agent)}: Update failed - ${res.error}`);
1309
+ }
1310
+ }
1311
+ if (updatedAny) {
1312
+ targetGroup.config = updateResult.config;
1313
+ logger.success(`Configuration for [${serverName}] updated successfully!`);
1314
+ return;
1315
+ }
1316
+ }
1317
+ }
1318
+ };
1319
+ var wizardManage = async (options = {}) => {
1320
+ const cwd = options.cwd ?? process.cwd();
1321
+ const isGlobal = await promptScope({
1322
+ cwd,
1323
+ defaultGlobal: options.global,
1324
+ message: "Select MCP scope to inspect and manage:"
1325
+ });
1326
+ const grouped = queryGroupedInstalledServers({ global: isGlobal, cwd });
1327
+ if (grouped.size === 0) {
1328
+ logger.warn(`No configured MCP servers found in ${isGlobal ? "global" : "project"} scope`);
1329
+ return;
1330
+ }
1331
+ let pendingServerName = options.serverName;
1332
+ const refreshGroupedServers = () => {
1333
+ const freshGrouped = queryGroupedInstalledServers({ global: isGlobal, cwd });
1334
+ grouped.clear();
1335
+ for (const [name, grp] of freshGrouped) {
1336
+ grouped.set(name, grp);
1337
+ }
1338
+ };
1339
+ while (true) {
1340
+ let chosenServerName;
1341
+ if (pendingServerName && grouped.has(pendingServerName)) {
1342
+ chosenServerName = pendingServerName;
1343
+ pendingServerName = void 0;
1344
+ } else {
1345
+ pendingServerName = void 0;
1346
+ const choices = Array.from(grouped.values()).map((g) => {
1347
+ const agentNames = g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
1348
+ return {
1349
+ name: `${pc13.bold(g.serverName)} ${pc13.dim(`(configured in: ${agentNames})`)}`,
1350
+ value: g.serverName
1351
+ };
1352
+ });
1353
+ choices.push({
1354
+ name: `Back`,
1355
+ value: "__back__"
1356
+ });
1357
+ chosenServerName = await select6({
1358
+ message: "Select MCP server to manage or sync:",
1359
+ choices
1360
+ });
1361
+ if (chosenServerName === "__back__") {
1362
+ return;
1363
+ }
1364
+ }
1365
+ const targetGroup = grouped.get(chosenServerName);
1366
+ if (!targetGroup) continue;
1367
+ displayServerDetails({
1368
+ serverName: chosenServerName,
1369
+ config: targetGroup.config,
1370
+ agents: targetGroup.agents,
1371
+ global: isGlobal,
1372
+ hasDivergence: targetGroup.hasDivergence
1373
+ });
1374
+ const action = await select6({
1375
+ message: `What would you like to do with [${chosenServerName}]?`,
1376
+ choices: [
1377
+ {
1378
+ name: "Edit server configuration",
1379
+ value: "edit"
1380
+ },
1381
+ {
1382
+ name: "Sync / clone to other agents",
1383
+ value: "sync"
1384
+ },
1385
+ {
1386
+ name: "Back to list",
1387
+ value: "back"
1388
+ }
1389
+ ]
1390
+ });
1391
+ if (action === "back") continue;
1392
+ if (action === "edit") {
1393
+ await handleEditServerConfig({
1394
+ targetGroup,
1395
+ global: isGlobal,
1396
+ cwd
1397
+ });
1398
+ refreshGroupedServers();
1399
+ continue;
1400
+ }
1401
+ if (action === "sync") {
1402
+ const allAllowedAgents = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
1403
+ const rawCandidateAgents = allAllowedAgents.filter((a) => !targetGroup.agents.includes(a));
1404
+ if (rawCandidateAgents.length === 0) {
1405
+ logger.info(
1406
+ "All supported agents in this scope already have this MCP server configured; no sync needed"
1407
+ );
1408
+ continue;
1409
+ }
1410
+ const candidateAgents = agentConfigStore.sortAgentsByClusters(rawCandidateAgents, { global: isGlobal, cwd });
1411
+ const choices = buildLinkedAgentChoices({
1412
+ agents: candidateAgents,
1413
+ checkedAgents: [],
1414
+ scopeOptions: { global: isGlobal, cwd }
1415
+ });
1416
+ const selectedToSync = await linkedCheckbox({
1417
+ message: "Select target agents to sync to (Space to select):",
1418
+ choices,
1419
+ loop: false,
1420
+ validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
1421
+ });
1422
+ const confirmed = await confirm6({
1423
+ message: `Confirm syncing configuration of [${chosenServerName}] to: ${selectedToSync.join(", ")}?`,
1424
+ default: true
1425
+ });
1426
+ if (!confirmed) {
1427
+ logger.warn("Sync cancelled");
1428
+ continue;
1429
+ }
1430
+ const syncResult = updateMcpServer({
1431
+ serverName: chosenServerName,
1432
+ config: targetGroup.config,
1433
+ agents: selectedToSync,
1434
+ global: isGlobal,
1435
+ cwd
1436
+ });
1437
+ for (const item of syncResult.incompatible) {
1438
+ logger.warn(`Skipping ${pc13.cyan(item.agent)}: ${item.reason}`);
1439
+ }
1440
+ for (const res of syncResult.results) {
1441
+ if (syncResult.incompatible.some((i) => i.agent === res.agent)) {
1442
+ continue;
1443
+ }
1444
+ if (res.success) {
1445
+ logger.success(
1446
+ `${pc13.cyan(res.agent)}: Successfully synced to ${pc13.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
1447
+ );
1448
+ targetGroup.agents.push(res.agent);
1449
+ } else {
1450
+ logger.error(`${pc13.cyan(res.agent)}: Sync failed - ${res.error}`);
1451
+ }
1452
+ }
1453
+ refreshGroupedServers();
1454
+ }
1455
+ }
1456
+ };
1457
+
1458
+ // src/interactive/wizard-remove.ts
1459
+ import { confirm as confirm7, select as select7 } from "@inquirer/prompts";
1460
+ import pc14 from "picocolors";
1461
+ var wizardRemove = async (options = {}) => {
1462
+ const cwd = options.cwd ?? process.cwd();
1463
+ const isGlobal = await promptScope({
1464
+ cwd,
1465
+ defaultGlobal: options.global,
1466
+ message: "Select scope to remove MCP server from:"
1467
+ });
1468
+ const serverMap = queryGroupedInstalledServers({ global: isGlobal, cwd });
1469
+ if (serverMap.size === 0) {
1470
+ logger.warn(`No installed MCP servers found in ${isGlobal ? "global" : "project"} scope`);
1471
+ return false;
1472
+ }
1473
+ let serverName = options.name;
1474
+ if (!serverName) {
1475
+ const choices = Array.from(serverMap.values()).map((g) => ({
1476
+ name: `${pc14.bold(g.serverName)} ${pc14.dim(`(installed in: ${g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ")})`)}`,
1477
+ value: g.serverName
1478
+ }));
1479
+ serverName = await select7({
1480
+ message: "Select MCP server to remove:",
1481
+ choices
1482
+ });
1483
+ }
1484
+ const rawInstalledAgents = serverMap.get(serverName)?.agents || [];
1485
+ if (rawInstalledAgents.length === 0) {
1486
+ logger.warn(`No agents found with [${serverName}] installed`);
1487
+ return false;
1488
+ }
1489
+ const installedAgents = agentConfigStore.sortAgentsByClusters(rawInstalledAgents, { global: isGlobal, cwd });
1490
+ let targetAgents = options.agents;
1491
+ if (!targetAgents || targetAgents.length === 0) {
1492
+ const choices = buildLinkedAgentChoices({
1493
+ agents: installedAgents,
1494
+ checkedAgents: installedAgents,
1495
+ scopeOptions: { global: isGlobal, cwd }
1496
+ });
1497
+ targetAgents = await linkedCheckbox({
1498
+ message: `Select agents to remove [${serverName}] from:`,
1499
+ choices,
1500
+ validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
1501
+ });
1502
+ } else {
1503
+ const validAgents = targetAgents.filter((agent) => installedAgents.includes(agent));
1504
+ if (validAgents.length === 0) {
1505
+ logger.warn(`None of the specified agents (${targetAgents.join(", ")}) have [${serverName}] installed`);
1506
+ return false;
1507
+ }
1508
+ targetAgents = validAgents;
1509
+ }
1510
+ const confirmed = await confirm7({
1511
+ message: `Confirm removing MCP server [${serverName}] from ${targetAgents.join(", ")}?`,
1512
+ default: true
1513
+ });
1514
+ if (!confirmed) {
1515
+ logger.warn("Operation cancelled");
1516
+ return false;
1517
+ }
1518
+ const results = removeMcpServer({
1519
+ name: serverName,
1520
+ agents: targetAgents,
1521
+ global: isGlobal,
1522
+ cwd
1523
+ });
1524
+ let removedCount = 0;
1525
+ for (const res of results) {
1526
+ if (res.removed) {
1527
+ logger.success(
1528
+ `${pc14.cyan(res.agent)}: Successfully removed from ${pc14.dim(res.path)}${formatCoHostedBadge("affected", res.coAffectedAgents)}`
1529
+ );
1530
+ removedCount++;
1531
+ } else if (res.error) {
1532
+ logger.error(`${pc14.cyan(res.agent)}: Failed to remove - ${res.error}`);
1533
+ }
1534
+ }
1535
+ if (removedCount > 0) {
1536
+ logger.success(`Successfully removed [${serverName}] from ${removedCount} agent(s)`);
1537
+ return true;
1538
+ }
1539
+ logger.warn(`Failed to remove [${serverName}] from specified agents`);
1540
+ return false;
1541
+ };
1542
+
1543
+ // src/interactive/main-menu.ts
1544
+ import { select as select8 } from "@inquirer/prompts";
1545
+ import pc15 from "picocolors";
1546
+ var mainMenu = async () => {
1547
+ console.log();
1548
+ console.log(pc15.bold(pc15.cyan("mcps - Cross-Platform MCP Manager for AI Coding Agents")));
1549
+ console.log(pc15.dim("Cross-platform MCP server configuration & synchronization tool"));
1550
+ console.log();
1551
+ while (true) {
1552
+ try {
1553
+ const action = await select8({
1554
+ message: "Select an action:",
1555
+ choices: [
1556
+ {
1557
+ name: "Add MCP Server",
1558
+ value: "add"
1559
+ },
1560
+ {
1561
+ name: "Manage & Sync Installed MCP Servers",
1562
+ value: "manage"
1563
+ },
1564
+ {
1565
+ name: "Remove MCP Server",
1566
+ value: "remove"
1567
+ },
1568
+ {
1569
+ name: "Exit",
1570
+ value: "exit"
1571
+ }
1572
+ ]
1573
+ });
1574
+ if (action === "exit") {
1575
+ console.log(pc15.dim("Goodbye!"));
1576
+ break;
1577
+ }
1578
+ if (action === "add") {
1579
+ await wizardAdd();
1580
+ } else if (action === "manage") {
1581
+ await wizardManage();
1582
+ } else if (action === "remove") {
1583
+ await wizardRemove();
1584
+ }
1585
+ console.log();
1586
+ } catch (error) {
1587
+ if (error?.name === "ExitPromptError") {
1588
+ console.log("\n" + pc15.dim("Exited."));
1589
+ break;
1590
+ }
1591
+ throw error;
1592
+ }
1593
+ }
1594
+ };
1595
+
1596
+ export {
1597
+ logger,
1598
+ logCoHostedNotice,
1599
+ buildLinkedAgentChoices,
1600
+ linkedCheckbox,
1601
+ promptScope,
1602
+ promptScopeAndAgents,
1603
+ parseArgsString,
1604
+ promptArgsConfig,
1605
+ formatArgsString,
1606
+ promptEditArgs,
1607
+ readMultilineTextFromTerminal,
1608
+ promptEditorText,
1609
+ promptEditKeyValueConfig,
1610
+ formatEnvText,
1611
+ parseEnvText,
1612
+ promptEnvConfig,
1613
+ promptEditEnvConfig,
1614
+ formatHeadersText,
1615
+ parseHeadersText,
1616
+ promptHeadersConfig,
1617
+ promptEditHeadersConfig,
1618
+ wizardAdd,
1619
+ displayServerDetails,
1620
+ promptSwitchServerType,
1621
+ wizardManage,
1622
+ wizardRemove,
1623
+ mainMenu
1624
+ };