@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.
package/dist/cli.cjs CHANGED
@@ -26,8 +26,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  var import_commander5 = require("commander");
27
27
 
28
28
  // src/cli/add.ts
29
- var import_commander2 = require("commander");
30
- var import_picocolors13 = __toESM(require("picocolors"), 1);
29
+ var import_commander = require("commander");
30
+ var import_picocolors12 = __toESM(require("picocolors"), 1);
31
31
 
32
32
  // src/agents.ts
33
33
  var import_node_fs = require("fs");
@@ -537,7 +537,7 @@ var KNOWN_COMMAND_RUNNERS = /* @__PURE__ */ new Set([
537
537
  ]);
538
538
  var SCRIPT_EXTENSION_REGEX = /\.(?:js|ts|mjs|cjs|py|sh|rb|go)$/i;
539
539
 
540
- // src/build-server-config.ts
540
+ // src/server-config.ts
541
541
  var buildMcpServerConfig = (parsed, options = {}) => {
542
542
  if (parsed.type === "remote") {
543
543
  const config2 = {
@@ -575,8 +575,6 @@ var buildMcpServerConfig = (parsed, options = {}) => {
575
575
  }
576
576
  return config;
577
577
  };
578
-
579
- // src/parse-server-config.ts
580
578
  var parseServerConfig = (raw) => {
581
579
  if (!raw || typeof raw !== "object") return {};
582
580
  const data = raw;
@@ -603,6 +601,236 @@ var parseServerConfig = (raw) => {
603
601
  }
604
602
  return {};
605
603
  };
604
+ var toRemoteServerConfig = (config, defaultTransport = "http") => {
605
+ const {
606
+ command: _droppedCommand,
607
+ args: _droppedArgs,
608
+ env: _droppedEnv,
609
+ ...remoteConfig
610
+ } = config;
611
+ return {
612
+ ...remoteConfig,
613
+ type: remoteConfig.type ?? defaultTransport
614
+ };
615
+ };
616
+ var toStdioServerConfig = (config) => {
617
+ const {
618
+ url: _droppedUrl,
619
+ type: _droppedType,
620
+ headers: _droppedHeaders,
621
+ ...stdioConfig
622
+ } = config;
623
+ return stdioConfig;
624
+ };
625
+ var detectUpdateTransition = (incoming, previous) => {
626
+ if (!previous) {
627
+ return incoming.url ? "switch-to-remote" : "switch-to-stdio";
628
+ }
629
+ const previousIsRemote = Boolean(previous.url && previous.url.length > 0);
630
+ if (previousIsRemote) {
631
+ if (incoming.command && !incoming.url) {
632
+ return "switch-to-stdio";
633
+ }
634
+ return "merge-remote";
635
+ }
636
+ if (incoming.url && !incoming.command) {
637
+ return "switch-to-remote";
638
+ }
639
+ return "merge-stdio";
640
+ };
641
+ var sanitizeUpdatedServerConfig = (incoming, previous) => {
642
+ const transition = detectUpdateTransition(incoming, previous);
643
+ const targetTransport = incoming.type ?? previous?.type ?? "http";
644
+ switch (transition) {
645
+ case "switch-to-remote": {
646
+ const cleanBase = previous ? toRemoteServerConfig(previous, targetTransport) : {};
647
+ return toRemoteServerConfig({ ...cleanBase, ...incoming }, targetTransport);
648
+ }
649
+ case "switch-to-stdio": {
650
+ const cleanBase = previous ? toStdioServerConfig(previous) : {};
651
+ return toStdioServerConfig({ ...cleanBase, ...incoming });
652
+ }
653
+ case "merge-remote": {
654
+ return toRemoteServerConfig({ ...previous, ...incoming }, targetTransport);
655
+ }
656
+ case "merge-stdio":
657
+ default: {
658
+ return toStdioServerConfig({ ...previous, ...incoming });
659
+ }
660
+ }
661
+ };
662
+ var applyServerConfigDelta = (previousConfig, options) => {
663
+ if (options.url !== void 0 && options.command !== void 0) {
664
+ throw new Error('Cannot specify both "--url" (remote) and "--command" (stdio) simultaneously.');
665
+ }
666
+ const isCurrentRemote = Boolean(previousConfig?.url && previousConfig.url.length > 0);
667
+ const willBeRemote = options.url !== void 0 ? true : options.command !== void 0 ? false : isCurrentRemote;
668
+ const protocol = willBeRemote ? "remote" : "stdio";
669
+ const ignoredFlags = [];
670
+ if (willBeRemote) {
671
+ if (options.env !== void 0) ignoredFlags.push("--env");
672
+ if (options.clearEnv) ignoredFlags.push("--clear-env");
673
+ if (options.args !== void 0) ignoredFlags.push("--args");
674
+ if (options.clearArgs) ignoredFlags.push("--clear-args");
675
+ } else {
676
+ if (options.headers !== void 0) ignoredFlags.push("--header");
677
+ if (options.clearHeaders) ignoredFlags.push("--clear-headers");
678
+ if (options.transport !== void 0) ignoredFlags.push("--transport");
679
+ }
680
+ const incomingDelta = {};
681
+ if (options.command !== void 0) {
682
+ incomingDelta.command = options.command;
683
+ }
684
+ if (options.clearArgs) {
685
+ incomingDelta.args = void 0;
686
+ }
687
+ if (options.args !== void 0) {
688
+ incomingDelta.args = options.args;
689
+ }
690
+ if (options.url !== void 0) {
691
+ incomingDelta.url = options.url;
692
+ }
693
+ if (options.transport !== void 0) {
694
+ incomingDelta.type = options.transport;
695
+ }
696
+ if (options.clearEnv) {
697
+ incomingDelta.env = void 0;
698
+ }
699
+ if (options.env !== void 0) {
700
+ const baseEnv = options.clearEnv ? {} : previousConfig?.env ?? {};
701
+ incomingDelta.env = { ...baseEnv, ...options.env };
702
+ }
703
+ if (options.clearHeaders) {
704
+ incomingDelta.headers = void 0;
705
+ }
706
+ if (options.headers !== void 0) {
707
+ const baseHeaders = options.clearHeaders ? {} : previousConfig?.headers ?? {};
708
+ incomingDelta.headers = { ...baseHeaders, ...options.headers };
709
+ }
710
+ const transition = detectUpdateTransition(incomingDelta, previousConfig);
711
+ const cleanConfig = sanitizeUpdatedServerConfig(incomingDelta, previousConfig);
712
+ return {
713
+ config: cleanConfig,
714
+ transition,
715
+ protocol,
716
+ ignoredFlags
717
+ };
718
+ };
719
+
720
+ // src/source-parser.ts
721
+ var REMOTE_URL_REGEX = /^https?:\/\//i;
722
+ var HAS_WHITESPACE_REGEX = /\s/;
723
+ var PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(?:@[^\s]+)?$/;
724
+ var PATH_SEPARATOR_REGEX = /[/\\]/;
725
+ var stripVersionSuffix = (input7) => {
726
+ if (input7.startsWith("@")) {
727
+ const secondAtIndex = input7.indexOf("@", 1);
728
+ if (secondAtIndex > 0) return input7.slice(0, secondAtIndex);
729
+ return input7;
730
+ }
731
+ const atIndex = input7.lastIndexOf("@");
732
+ if (atIndex > 0) return input7.slice(0, atIndex);
733
+ return input7;
734
+ };
735
+ var stripScopePrefix = (input7) => {
736
+ if (!input7.startsWith("@") || !input7.includes("/")) return input7;
737
+ const parts = input7.split("/");
738
+ return parts[1] || input7;
739
+ };
740
+ var stripPathPrefix = (input7) => {
741
+ if (!PATH_SEPARATOR_REGEX.test(input7)) return input7;
742
+ const segments = input7.split(PATH_SEPARATOR_REGEX);
743
+ const basename = segments[segments.length - 1];
744
+ return basename || input7;
745
+ };
746
+ var extractPackageName = (input7) => {
747
+ let name = stripVersionSuffix(input7);
748
+ name = stripScopePrefix(name);
749
+ name = stripPathPrefix(name);
750
+ name = name.replace(SCRIPT_EXTENSION_REGEX, "");
751
+ for (const prefix of PACKAGE_NAME_PREFIX_STRIP) {
752
+ if (name.startsWith(prefix)) {
753
+ name = name.slice(prefix.length);
754
+ break;
755
+ }
756
+ }
757
+ for (const suffix of PACKAGE_NAME_SUFFIX_STRIP) {
758
+ if (name.endsWith(suffix)) {
759
+ name = name.slice(0, -suffix.length);
760
+ break;
761
+ }
762
+ }
763
+ return name || MCP_DEFAULT_SERVER_NAME;
764
+ };
765
+ var inferNameFromUrl = (input7) => {
766
+ try {
767
+ const url = new URL(input7);
768
+ const host = url.hostname;
769
+ const labels = host.split(".").filter((segment) => segment.length > 0);
770
+ if (labels.length === 0) return MCP_DEFAULT_SERVER_NAME;
771
+ const meaningfulLabels = labels.filter((label) => {
772
+ const lower = label.toLowerCase();
773
+ if (COMMON_TLD_LABELS.has(lower)) return false;
774
+ if (GENERIC_HOST_PREFIXES.has(lower)) return false;
775
+ return true;
776
+ });
777
+ if (meaningfulLabels.length > 0) return meaningfulLabels[0];
778
+ if (labels.length >= 2) return labels[labels.length - 2];
779
+ return labels[labels.length - 1] || MCP_DEFAULT_SERVER_NAME;
780
+ } catch {
781
+ return MCP_DEFAULT_SERVER_NAME;
782
+ }
783
+ };
784
+ var inferNameFromCommand = (command) => {
785
+ const tokens = command.trim().split(/\s+/);
786
+ const runnerBase = tokens[0]?.split(PATH_SEPARATOR_REGEX).pop() ?? "";
787
+ const startIndex = KNOWN_COMMAND_RUNNERS.has(runnerBase) ? 1 : 0;
788
+ for (let tokenIndex = startIndex; tokenIndex < tokens.length; tokenIndex += 1) {
789
+ const token = tokens[tokenIndex];
790
+ if (!token || token.startsWith("-")) continue;
791
+ return extractPackageName(token);
792
+ }
793
+ const firstNonFlag = tokens.find((token) => !token.startsWith("-"));
794
+ return firstNonFlag ? extractPackageName(firstNonFlag) : MCP_DEFAULT_SERVER_NAME;
795
+ };
796
+ var parseMcpSource = (input7) => {
797
+ const trimmed = input7.trim();
798
+ if (trimmed.length === 0) {
799
+ throw new Error(
800
+ "Invalid MCP source: input is empty. Expected a remote URL, an npm package, or a command line."
801
+ );
802
+ }
803
+ if (REMOTE_URL_REGEX.test(trimmed)) {
804
+ return {
805
+ type: "remote",
806
+ value: trimmed,
807
+ inferredName: inferNameFromUrl(trimmed)
808
+ };
809
+ }
810
+ if (HAS_WHITESPACE_REGEX.test(trimmed)) {
811
+ return {
812
+ type: "command",
813
+ value: trimmed,
814
+ inferredName: inferNameFromCommand(trimmed)
815
+ };
816
+ }
817
+ if (PACKAGE_NAME_REGEX.test(trimmed)) {
818
+ return {
819
+ type: "package",
820
+ value: trimmed,
821
+ inferredName: extractPackageName(trimmed)
822
+ };
823
+ }
824
+ return {
825
+ type: "command",
826
+ value: trimmed,
827
+ inferredName: inferNameFromCommand(trimmed)
828
+ };
829
+ };
830
+
831
+ // src/config-store.ts
832
+ var import_node_fs6 = require("fs");
833
+ var import_node_path3 = require("path");
606
834
 
607
835
  // src/utils/is-plain-object.ts
608
836
  var isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
@@ -887,113 +1115,6 @@ var listServersInConfigFile = (filePath, format, dottedKey) => {
887
1115
  return isPlainObject(entries) ? entries : {};
888
1116
  };
889
1117
 
890
- // src/config-store.ts
891
- var import_node_fs6 = require("fs");
892
-
893
- // src/resolve-config-target.ts
894
- var import_node_path3 = require("path");
895
- var resolveMcpConfigTarget = (agent, options = {}) => {
896
- const isGlobal = options.global ?? false;
897
- const cwd = options.cwd ?? process.cwd();
898
- const configPath = agent.resolveConfigPath ? agent.resolveConfigPath({ global: isGlobal, cwd }) : !isGlobal && agent.projectConfigPath ? (0, import_node_path3.join)(cwd, agent.projectConfigPath) : agent.globalConfigPath;
899
- const configKey = !isGlobal && agent.projectConfigKey ? agent.projectConfigKey : agent.configKey;
900
- return { configPath, configKey };
901
- };
902
-
903
- // src/config-store.ts
904
- var FsConfigStoreAdapter = class {
905
- exists(filePath) {
906
- return (0, import_node_fs6.existsSync)(filePath);
907
- }
908
- read(target) {
909
- return readConfigFile(target.filePath, target.format);
910
- }
911
- writeServer(target, serverName, serverConfig) {
912
- if (!target.dottedKey) {
913
- throw new Error(`Cannot write server: missing dottedKey for ${target.filePath}`);
914
- }
915
- writeServerToConfigFile(
916
- target.filePath,
917
- target.format,
918
- target.dottedKey,
919
- serverName,
920
- serverConfig
921
- );
922
- }
923
- removeServer(target, serverName) {
924
- if (!target.dottedKey) return false;
925
- return removeServerFromConfigFile(
926
- target.filePath,
927
- target.format,
928
- target.dottedKey,
929
- serverName
930
- );
931
- }
932
- listServers(target) {
933
- if (!target.dottedKey) return {};
934
- return listServersInConfigFile(target.filePath, target.format, target.dottedKey);
935
- }
936
- };
937
- var AgentConfigStore = class {
938
- constructor(adapter = new FsConfigStoreAdapter()) {
939
- this.adapter = adapter;
940
- }
941
- adapter;
942
- getAdapter() {
943
- return this.adapter;
944
- }
945
- resolveTarget(agent, options = {}) {
946
- const agentConfig = typeof agent === "string" ? getMcpAgentConfig(agent) : agent;
947
- const target = resolveMcpConfigTarget(agentConfig, options);
948
- return { agent: agentConfig, target };
949
- }
950
- resolveDescriptor(agent, options = {}) {
951
- const { agent: agentConfig, target } = this.resolveTarget(agent, options);
952
- return {
953
- filePath: target.configPath,
954
- format: agentConfig.format,
955
- dottedKey: target.configKey
956
- };
957
- }
958
- writeServer(agent, serverName, serverConfig, options = {}) {
959
- const descriptor = this.resolveDescriptor(agent, options);
960
- this.adapter.writeServer(descriptor, serverName, serverConfig);
961
- return { path: descriptor.filePath };
962
- }
963
- removeServer(agent, serverName, options = {}) {
964
- const descriptor = this.resolveDescriptor(agent, options);
965
- if (!this.adapter.exists(descriptor.filePath)) {
966
- return { path: descriptor.filePath, removed: false };
967
- }
968
- const removed = this.adapter.removeServer(descriptor, serverName);
969
- return { path: descriptor.filePath, removed };
970
- }
971
- listServers(agent, options = {}) {
972
- const descriptor = this.resolveDescriptor(agent, options);
973
- if (!this.adapter.exists(descriptor.filePath)) {
974
- return { path: descriptor.filePath, exists: false, servers: {} };
975
- }
976
- const servers = this.adapter.listServers(descriptor);
977
- return { path: descriptor.filePath, exists: true, servers };
978
- }
979
- read(agent, options = {}) {
980
- const descriptor = this.resolveDescriptor(agent, options);
981
- if (!this.adapter.exists(descriptor.filePath)) {
982
- return {};
983
- }
984
- return this.adapter.read(descriptor);
985
- }
986
- readServer(agent, serverName, options = {}) {
987
- const { exists, servers } = this.listServers(agent, options);
988
- if (!exists) return void 0;
989
- return servers[serverName];
990
- }
991
- };
992
- var agentConfigStore = new AgentConfigStore();
993
-
994
- // src/utils/to-error-message.ts
995
- var toErrorMessage = (error, fallback = "Unknown error") => error instanceof Error ? error.message : fallback;
996
-
997
1118
  // src/transforms/index.ts
998
1119
  var DIALECT_PRESETS = {
999
1120
  vscode: {
@@ -1201,30 +1322,295 @@ var transformServerConfigForAgent = (agent, serverName, config, context = { glob
1201
1322
  return config;
1202
1323
  };
1203
1324
 
1204
- // src/installer.ts
1205
- var installMcpServerForAgent = (serverName, serverConfig, agentType, options = {}) => {
1206
- const agent = getMcpAgentConfig(agentType);
1207
- const isGlobal = options.global ?? false;
1208
- const { target } = agentConfigStore.resolveTarget(agent, options);
1209
- try {
1210
- const transformed = transformServerConfigForAgent(agent, serverName, serverConfig, {
1211
- global: isGlobal
1212
- });
1213
- agentConfigStore.writeServer(agent, serverName, transformed, options);
1214
- return { agent: agentType, success: true, path: target.configPath };
1215
- } catch (error) {
1216
- return {
1217
- agent: agentType,
1218
- success: false,
1219
- path: target.configPath,
1220
- error: toErrorMessage(error)
1221
- };
1222
- }
1223
- };
1325
+ // src/utils/to-error-message.ts
1326
+ var toErrorMessage = (error, fallback = "Unknown error") => error instanceof Error ? error.message : fallback;
1224
1327
 
1225
- // src/utils/parse-mcp-agent-list.ts
1226
- var parseMcpAgentList = (input7) => {
1227
- if (!input7 || input7.length === 0) return void 0;
1328
+ // src/config-store.ts
1329
+ var resolveMcpConfigTarget = (agent, options = {}) => {
1330
+ const isGlobal = options.global ?? false;
1331
+ const cwd = options.cwd ?? process.cwd();
1332
+ const configPath = agent.resolveConfigPath ? agent.resolveConfigPath({ global: isGlobal, cwd }) : !isGlobal && agent.projectConfigPath ? (0, import_node_path3.join)(cwd, agent.projectConfigPath) : agent.globalConfigPath;
1333
+ const configKey = !isGlobal && agent.projectConfigKey ? agent.projectConfigKey : agent.configKey;
1334
+ return { configPath, configKey };
1335
+ };
1336
+ var getCandidateAgentsForScope = (options = {}) => {
1337
+ return options.global ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
1338
+ };
1339
+ var FsConfigStoreAdapter = class {
1340
+ exists(filePath) {
1341
+ return (0, import_node_fs6.existsSync)(filePath);
1342
+ }
1343
+ read(target) {
1344
+ return readConfigFile(target.filePath, target.format);
1345
+ }
1346
+ writeServer(target, serverName, serverConfig) {
1347
+ if (!target.dottedKey) {
1348
+ throw new Error(`Cannot write server: missing dottedKey for ${target.filePath}`);
1349
+ }
1350
+ writeServerToConfigFile(
1351
+ target.filePath,
1352
+ target.format,
1353
+ target.dottedKey,
1354
+ serverName,
1355
+ serverConfig
1356
+ );
1357
+ }
1358
+ removeServer(target, serverName) {
1359
+ if (!target.dottedKey) return false;
1360
+ return removeServerFromConfigFile(
1361
+ target.filePath,
1362
+ target.format,
1363
+ target.dottedKey,
1364
+ serverName
1365
+ );
1366
+ }
1367
+ listServers(target) {
1368
+ if (!target.dottedKey) return {};
1369
+ return listServersInConfigFile(target.filePath, target.format, target.dottedKey);
1370
+ }
1371
+ };
1372
+ var AgentConfigStore = class {
1373
+ constructor(adapter = new FsConfigStoreAdapter()) {
1374
+ this.adapter = adapter;
1375
+ }
1376
+ adapter;
1377
+ getAdapter() {
1378
+ return this.adapter;
1379
+ }
1380
+ // ---- Single-agent primitives ----
1381
+ resolveTarget(agent, options = {}) {
1382
+ const agentConfig = typeof agent === "string" ? getMcpAgentConfig(agent) : agent;
1383
+ const target = resolveMcpConfigTarget(agentConfig, options);
1384
+ return { agent: agentConfig, target };
1385
+ }
1386
+ resolveDescriptor(agent, options = {}) {
1387
+ const { agent: agentConfig, target } = this.resolveTarget(agent, options);
1388
+ return {
1389
+ filePath: target.configPath,
1390
+ format: agentConfig.format,
1391
+ dottedKey: target.configKey
1392
+ };
1393
+ }
1394
+ writeServer(agent, serverName, serverConfig, options = {}) {
1395
+ const descriptor = this.resolveDescriptor(agent, options);
1396
+ this.adapter.writeServer(descriptor, serverName, serverConfig);
1397
+ return { path: descriptor.filePath };
1398
+ }
1399
+ removeServer(agent, serverName, options = {}) {
1400
+ const descriptor = this.resolveDescriptor(agent, options);
1401
+ if (!this.adapter.exists(descriptor.filePath)) {
1402
+ return { path: descriptor.filePath, removed: false };
1403
+ }
1404
+ const removed = this.adapter.removeServer(descriptor, serverName);
1405
+ return { path: descriptor.filePath, removed };
1406
+ }
1407
+ listServers(agent, options = {}) {
1408
+ const descriptor = this.resolveDescriptor(agent, options);
1409
+ if (!this.adapter.exists(descriptor.filePath)) {
1410
+ return { path: descriptor.filePath, exists: false, servers: {} };
1411
+ }
1412
+ const servers = this.adapter.listServers(descriptor);
1413
+ return { path: descriptor.filePath, exists: true, servers };
1414
+ }
1415
+ /**
1416
+ * Batch lists servers for multiple agents, caching adapter reads for co-hosted
1417
+ * agents that share the exact same physical configuration file and dotted key.
1418
+ */
1419
+ listServersForAgents(agents, options = {}) {
1420
+ const readCache = /* @__PURE__ */ new Map();
1421
+ return agents.map((agentType) => {
1422
+ const descriptor = this.resolveDescriptor(agentType, options);
1423
+ const cacheKey = `${descriptor.filePath}::${descriptor.dottedKey ?? ""}`;
1424
+ let cached = readCache.get(cacheKey);
1425
+ if (!cached) {
1426
+ cached = this.listServers(agentType, options);
1427
+ readCache.set(cacheKey, cached);
1428
+ }
1429
+ return {
1430
+ agent: agentType,
1431
+ path: cached.path,
1432
+ exists: cached.exists,
1433
+ servers: cached.servers
1434
+ };
1435
+ });
1436
+ }
1437
+ read(agent, options = {}) {
1438
+ const descriptor = this.resolveDescriptor(agent, options);
1439
+ if (!this.adapter.exists(descriptor.filePath)) {
1440
+ return {};
1441
+ }
1442
+ return this.adapter.read(descriptor);
1443
+ }
1444
+ readServer(agent, serverName, options = {}) {
1445
+ const { exists, servers } = this.listServers(agent, options);
1446
+ if (!exists) return void 0;
1447
+ return servers[serverName];
1448
+ }
1449
+ // ---- Cluster & co-hosted awareness ----
1450
+ /**
1451
+ * Returns all other agents sharing the exact same physical configuration target
1452
+ * (same configPath and configKey) for the given scope.
1453
+ */
1454
+ getCoHostedAgents(agentType, options = {}) {
1455
+ const currentAgent = getMcpAgentConfig(agentType);
1456
+ const currentTarget = resolveMcpConfigTarget(currentAgent, options);
1457
+ const candidates = getCandidateAgentsForScope(options);
1458
+ const coHosted = [];
1459
+ for (const candidateType of candidates) {
1460
+ if (candidateType === agentType) continue;
1461
+ const candidateConfig = getMcpAgentConfig(candidateType);
1462
+ const candidateTarget = resolveMcpConfigTarget(candidateConfig, options);
1463
+ if (candidateTarget.configPath === currentTarget.configPath && candidateTarget.configKey === currentTarget.configKey) {
1464
+ coHosted.push(candidateType);
1465
+ }
1466
+ }
1467
+ return coHosted;
1468
+ }
1469
+ /**
1470
+ * Resolves configuration clusters for a given list of requested agents.
1471
+ * Groups requested agents sharing the same physical config path, and tracks
1472
+ * any remaining co-hosted agents sharing the same target that were not in the request.
1473
+ */
1474
+ resolveConfigClusters(agentTypes, options = {}) {
1475
+ const clustersByPath = /* @__PURE__ */ new Map();
1476
+ for (const agentType of agentTypes) {
1477
+ const agentConfig = getMcpAgentConfig(agentType);
1478
+ const target = resolveMcpConfigTarget(agentConfig, options);
1479
+ let keyMap = clustersByPath.get(target.configPath);
1480
+ if (!keyMap) {
1481
+ keyMap = /* @__PURE__ */ new Map();
1482
+ clustersByPath.set(target.configPath, keyMap);
1483
+ }
1484
+ let cluster = keyMap.get(target.configKey);
1485
+ if (!cluster) {
1486
+ const allCoHosted = this.getCoHostedAgents(agentType, options);
1487
+ cluster = {
1488
+ configPath: target.configPath,
1489
+ configKey: target.configKey,
1490
+ targetAgents: [],
1491
+ coHostedAgents: allCoHosted
1492
+ };
1493
+ keyMap.set(target.configKey, cluster);
1494
+ }
1495
+ if (!cluster.targetAgents.includes(agentType)) {
1496
+ cluster.targetAgents.push(agentType);
1497
+ }
1498
+ }
1499
+ const clusters = [];
1500
+ for (const keyMap of clustersByPath.values()) {
1501
+ for (const cluster of keyMap.values()) {
1502
+ cluster.coHostedAgents = cluster.coHostedAgents.filter(
1503
+ (co) => !cluster.targetAgents.includes(co)
1504
+ );
1505
+ clusters.push(cluster);
1506
+ }
1507
+ }
1508
+ return clusters;
1509
+ }
1510
+ /**
1511
+ * Sorts agent types so that agents sharing the same physical config target
1512
+ * appear adjacent to each other in the returned array.
1513
+ */
1514
+ sortAgentsByClusters(agentTypes, options = {}) {
1515
+ const clusters = this.resolveConfigClusters(agentTypes, options);
1516
+ const sorted = [];
1517
+ for (const cluster of clusters) {
1518
+ for (const agent of cluster.targetAgents) {
1519
+ if (!sorted.includes(agent)) {
1520
+ sorted.push(agent);
1521
+ }
1522
+ }
1523
+ }
1524
+ return sorted;
1525
+ }
1526
+ /**
1527
+ * Alias for sortAgentsByClusters for backward compatibility.
1528
+ */
1529
+ sortAgentsWithClusters(agentTypes, options = {}) {
1530
+ return this.sortAgentsByClusters(agentTypes, options);
1531
+ }
1532
+ // ---- Batch operations with automatic clustering & dialect transforms ----
1533
+ /**
1534
+ * Batch write: transforms and writes a server config to multiple agents,
1535
+ * with automatic cluster deduplication, dialect transform, and co-hosted awareness.
1536
+ * Callers pass standard McpServerConfig; the Store applies per-Agent dialect transforms
1537
+ * internally before persisting.
1538
+ */
1539
+ writeServers(agents, serverName, serverConfig, options = {}) {
1540
+ const clusters = this.resolveConfigClusters(agents, options);
1541
+ const resultsByAgent = /* @__PURE__ */ new Map();
1542
+ const isGlobal = options.global ?? false;
1543
+ for (const cluster of clusters) {
1544
+ const primaryAgentType = cluster.targetAgents[0];
1545
+ const primaryAgent = getMcpAgentConfig(primaryAgentType);
1546
+ try {
1547
+ const transformed = transformServerConfigForAgent(primaryAgent, serverName, serverConfig, {
1548
+ global: isGlobal
1549
+ });
1550
+ const descriptor = this.resolveDescriptor(primaryAgent, options);
1551
+ this.adapter.writeServer(descriptor, serverName, transformed);
1552
+ for (const agentType of cluster.targetAgents) {
1553
+ resultsByAgent.set(agentType, {
1554
+ agent: agentType,
1555
+ success: true,
1556
+ path: cluster.configPath,
1557
+ coConfiguredAgents: cluster.coHostedAgents.length > 0 ? cluster.coHostedAgents : void 0
1558
+ });
1559
+ }
1560
+ } catch (error) {
1561
+ const errorMsg = toErrorMessage(error);
1562
+ for (const agentType of cluster.targetAgents) {
1563
+ resultsByAgent.set(agentType, {
1564
+ agent: agentType,
1565
+ success: false,
1566
+ path: cluster.configPath,
1567
+ error: errorMsg
1568
+ });
1569
+ }
1570
+ }
1571
+ }
1572
+ return agents.map((agentType) => resultsByAgent.get(agentType));
1573
+ }
1574
+ /**
1575
+ * Batch remove: removes a server from multiple agents' configs,
1576
+ * with automatic cluster deduplication and co-hosted awareness.
1577
+ */
1578
+ removeServers(agents, serverName, options = {}) {
1579
+ const clusters = this.resolveConfigClusters(agents, options);
1580
+ const resultsByAgent = /* @__PURE__ */ new Map();
1581
+ for (const cluster of clusters) {
1582
+ const primaryAgentType = cluster.targetAgents[0];
1583
+ const primaryAgent = getMcpAgentConfig(primaryAgentType);
1584
+ try {
1585
+ const { removed } = this.removeServer(primaryAgent, serverName, options);
1586
+ for (const agentType of cluster.targetAgents) {
1587
+ resultsByAgent.set(agentType, {
1588
+ agent: agentType,
1589
+ path: cluster.configPath,
1590
+ removed,
1591
+ coAffectedAgents: removed && cluster.coHostedAgents.length > 0 ? cluster.coHostedAgents : void 0
1592
+ });
1593
+ }
1594
+ } catch (error) {
1595
+ const errorMsg = toErrorMessage(error);
1596
+ for (const agentType of cluster.targetAgents) {
1597
+ resultsByAgent.set(agentType, {
1598
+ agent: agentType,
1599
+ path: cluster.configPath,
1600
+ removed: false,
1601
+ error: errorMsg
1602
+ });
1603
+ }
1604
+ }
1605
+ }
1606
+ return agents.map((agentType) => resultsByAgent.get(agentType));
1607
+ }
1608
+ };
1609
+ var agentConfigStore = new AgentConfigStore();
1610
+
1611
+ // src/utils/parse-mcp-agent-list.ts
1612
+ var parseMcpAgentList = (input7) => {
1613
+ if (!input7 || input7.length === 0) return void 0;
1228
1614
  if (input7.includes("*")) return getMcpAgentTypes();
1229
1615
  const resolved = [];
1230
1616
  for (const value of input7) {
@@ -1293,115 +1679,33 @@ var resolveTargetAgents = (query = {}) => {
1293
1679
  };
1294
1680
  };
1295
1681
 
1296
- // src/source-parser.ts
1297
- var REMOTE_URL_REGEX = /^https?:\/\//i;
1298
- var HAS_WHITESPACE_REGEX = /\s/;
1299
- var PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(?:@[^\s]+)?$/;
1300
- var PATH_SEPARATOR_REGEX = /[/\\]/;
1301
- var stripVersionSuffix = (input7) => {
1302
- if (input7.startsWith("@")) {
1303
- const secondAtIndex = input7.indexOf("@", 1);
1304
- if (secondAtIndex > 0) return input7.slice(0, secondAtIndex);
1305
- return input7;
1306
- }
1307
- const atIndex = input7.lastIndexOf("@");
1308
- if (atIndex > 0) return input7.slice(0, atIndex);
1309
- return input7;
1310
- };
1311
- var stripScopePrefix = (input7) => {
1312
- if (!input7.startsWith("@") || !input7.includes("/")) return input7;
1313
- const parts = input7.split("/");
1314
- return parts[1] || input7;
1315
- };
1316
- var stripPathPrefix = (input7) => {
1317
- if (!PATH_SEPARATOR_REGEX.test(input7)) return input7;
1318
- const segments = input7.split(PATH_SEPARATOR_REGEX);
1319
- const basename = segments[segments.length - 1];
1320
- return basename || input7;
1321
- };
1322
- var extractPackageName = (input7) => {
1323
- let name = stripVersionSuffix(input7);
1324
- name = stripScopePrefix(name);
1325
- name = stripPathPrefix(name);
1326
- name = name.replace(SCRIPT_EXTENSION_REGEX, "");
1327
- for (const prefix of PACKAGE_NAME_PREFIX_STRIP) {
1328
- if (name.startsWith(prefix)) {
1329
- name = name.slice(prefix.length);
1330
- break;
1682
+ // src/install-compat.ts
1683
+ var installToCompatibleAgents = (serverName, serverConfig, options) => {
1684
+ const { allAgents, incompatible = [], global: isGlobal, cwd } = options;
1685
+ const incompatibleMap = new Map(incompatible.map((item) => [item.agent, item.reason]));
1686
+ const compatibleAgents = allAgents.filter((a) => !incompatibleMap.has(a));
1687
+ const installedResults = agentConfigStore.writeServers(
1688
+ compatibleAgents,
1689
+ serverName,
1690
+ serverConfig,
1691
+ {
1692
+ global: isGlobal,
1693
+ cwd
1331
1694
  }
1332
- }
1333
- for (const suffix of PACKAGE_NAME_SUFFIX_STRIP) {
1334
- if (name.endsWith(suffix)) {
1335
- name = name.slice(0, -suffix.length);
1336
- break;
1695
+ );
1696
+ const installedMap = new Map(installedResults.map((r) => [r.agent, r]));
1697
+ return allAgents.map((agentType) => {
1698
+ const incompatibleReason = incompatibleMap.get(agentType);
1699
+ if (incompatibleReason) {
1700
+ return {
1701
+ agent: agentType,
1702
+ success: false,
1703
+ path: "",
1704
+ error: incompatibleReason
1705
+ };
1337
1706
  }
1338
- }
1339
- return name || MCP_DEFAULT_SERVER_NAME;
1340
- };
1341
- var inferNameFromUrl = (input7) => {
1342
- try {
1343
- const url = new URL(input7);
1344
- const host = url.hostname;
1345
- const labels = host.split(".").filter((segment) => segment.length > 0);
1346
- if (labels.length === 0) return MCP_DEFAULT_SERVER_NAME;
1347
- const meaningfulLabels = labels.filter((label) => {
1348
- const lower = label.toLowerCase();
1349
- if (COMMON_TLD_LABELS.has(lower)) return false;
1350
- if (GENERIC_HOST_PREFIXES.has(lower)) return false;
1351
- return true;
1352
- });
1353
- if (meaningfulLabels.length > 0) return meaningfulLabels[0];
1354
- if (labels.length >= 2) return labels[labels.length - 2];
1355
- return labels[labels.length - 1] || MCP_DEFAULT_SERVER_NAME;
1356
- } catch {
1357
- return MCP_DEFAULT_SERVER_NAME;
1358
- }
1359
- };
1360
- var inferNameFromCommand = (command) => {
1361
- const tokens = command.trim().split(/\s+/);
1362
- const runnerBase = tokens[0]?.split(PATH_SEPARATOR_REGEX).pop() ?? "";
1363
- const startIndex = KNOWN_COMMAND_RUNNERS.has(runnerBase) ? 1 : 0;
1364
- for (let tokenIndex = startIndex; tokenIndex < tokens.length; tokenIndex += 1) {
1365
- const token = tokens[tokenIndex];
1366
- if (!token || token.startsWith("-")) continue;
1367
- return extractPackageName(token);
1368
- }
1369
- const firstNonFlag = tokens.find((token) => !token.startsWith("-"));
1370
- return firstNonFlag ? extractPackageName(firstNonFlag) : MCP_DEFAULT_SERVER_NAME;
1371
- };
1372
- var parseMcpSource = (input7) => {
1373
- const trimmed = input7.trim();
1374
- if (trimmed.length === 0) {
1375
- throw new Error(
1376
- "Invalid MCP source: input is empty. Expected a remote URL, an npm package, or a command line."
1377
- );
1378
- }
1379
- if (REMOTE_URL_REGEX.test(trimmed)) {
1380
- return {
1381
- type: "remote",
1382
- value: trimmed,
1383
- inferredName: inferNameFromUrl(trimmed)
1384
- };
1385
- }
1386
- if (HAS_WHITESPACE_REGEX.test(trimmed)) {
1387
- return {
1388
- type: "command",
1389
- value: trimmed,
1390
- inferredName: inferNameFromCommand(trimmed)
1391
- };
1392
- }
1393
- if (PACKAGE_NAME_REGEX.test(trimmed)) {
1394
- return {
1395
- type: "package",
1396
- value: trimmed,
1397
- inferredName: extractPackageName(trimmed)
1398
- };
1399
- }
1400
- return {
1401
- type: "command",
1402
- value: trimmed,
1403
- inferredName: inferNameFromCommand(trimmed)
1404
- };
1707
+ return installedMap.get(agentType);
1708
+ });
1405
1709
  };
1406
1710
 
1407
1711
  // src/install-mcp-server.ts
@@ -1418,64 +1722,116 @@ var installMcpServer = (options) => {
1418
1722
  });
1419
1723
  const requestedTransport = parsed.type === "remote" ? serverConfig.type ?? "http" : "stdio";
1420
1724
  const { allAgents, incompatible } = resolveTargetAgents({
1421
- requested: options.agents,
1725
+ requested: options.agents,
1726
+ global: isGlobal,
1727
+ cwd,
1728
+ transport: requestedTransport
1729
+ });
1730
+ const results = installToCompatibleAgents(serverName, serverConfig, {
1731
+ allAgents,
1732
+ incompatible,
1733
+ global: isGlobal,
1734
+ cwd
1735
+ });
1736
+ return { serverName, config: serverConfig, results };
1737
+ };
1738
+
1739
+ // src/list.ts
1740
+ var listInstalledMcpServers = (options = {}) => {
1741
+ const agentTypes = options.agents ?? getMcpAgentTypes();
1742
+ const collected = [];
1743
+ const results = agentConfigStore.listServersForAgents(agentTypes, options);
1744
+ for (const item of results) {
1745
+ if (!item.exists) continue;
1746
+ for (const [serverName, rawConfig] of Object.entries(item.servers)) {
1747
+ collected.push({
1748
+ serverName,
1749
+ agent: item.agent,
1750
+ path: item.path,
1751
+ config: rawConfig,
1752
+ serverConfig: parseServerConfig(rawConfig)
1753
+ });
1754
+ }
1755
+ }
1756
+ return collected;
1757
+ };
1758
+ var groupInstalledServersByName = (installed) => {
1759
+ const grouped = /* @__PURE__ */ new Map();
1760
+ for (const item of installed) {
1761
+ const itemConfig = item.serverConfig ?? parseServerConfig(item.config);
1762
+ let entry = grouped.get(item.serverName);
1763
+ if (!entry) {
1764
+ entry = {
1765
+ serverName: item.serverName,
1766
+ agents: [],
1767
+ paths: [],
1768
+ config: itemConfig,
1769
+ hasDivergence: false
1770
+ };
1771
+ grouped.set(item.serverName, entry);
1772
+ } else if (!entry.hasDivergence) {
1773
+ if (JSON.stringify(entry.config) !== JSON.stringify(itemConfig)) {
1774
+ entry.hasDivergence = true;
1775
+ }
1776
+ }
1777
+ if (!entry.agents.includes(item.agent)) {
1778
+ entry.agents.push(item.agent);
1779
+ }
1780
+ if (!entry.paths.includes(item.path)) {
1781
+ entry.paths.push(item.path);
1782
+ }
1783
+ }
1784
+ return grouped;
1785
+ };
1786
+ var queryGroupedInstalledServers = (options = {}) => {
1787
+ const installed = listInstalledMcpServers(options);
1788
+ return groupInstalledServersByName(installed);
1789
+ };
1790
+
1791
+ // src/update-mcp-server.ts
1792
+ var updateMcpServer = (options) => {
1793
+ const isGlobal = options.global ?? false;
1794
+ const cwd = options.cwd ?? process.cwd();
1795
+ let previousConfig = options.previousConfig;
1796
+ if (!previousConfig) {
1797
+ const existing = listInstalledMcpServers({
1798
+ global: isGlobal,
1799
+ cwd,
1800
+ agents: options.agents
1801
+ });
1802
+ const found = existing.find((s) => s.serverName === options.serverName && s.serverConfig);
1803
+ if (found) {
1804
+ previousConfig = found.serverConfig;
1805
+ }
1806
+ }
1807
+ const serverConfig = sanitizeUpdatedServerConfig(options.config, previousConfig);
1808
+ let targetAgents = options.agents;
1809
+ if (!targetAgents || targetAgents.length === 0) {
1810
+ const existing = listInstalledMcpServers({ global: isGlobal, cwd });
1811
+ targetAgents = existing.filter((s) => s.serverName === options.serverName).map((s) => s.agent);
1812
+ }
1813
+ const requestedTransport = serverConfig.url ? serverConfig.type ?? "http" : "stdio";
1814
+ const { allAgents, incompatible } = resolveTargetAgents({
1815
+ requested: targetAgents,
1422
1816
  global: isGlobal,
1423
1817
  cwd,
1424
1818
  transport: requestedTransport
1425
1819
  });
1426
- const incompatibleMap = new Map(incompatible.map((item) => [item.agent, item.reason]));
1427
- const results = allAgents.map((agentType) => {
1428
- const incompatibleReason = incompatibleMap.get(agentType);
1429
- if (incompatibleReason) {
1430
- return {
1431
- agent: agentType,
1432
- success: false,
1433
- path: "",
1434
- error: incompatibleReason
1435
- };
1436
- }
1437
- return installMcpServerForAgent(serverName, serverConfig, agentType, { global: isGlobal, cwd });
1820
+ const results = installToCompatibleAgents(options.serverName, serverConfig, {
1821
+ allAgents,
1822
+ incompatible,
1823
+ global: isGlobal,
1824
+ cwd
1438
1825
  });
1439
- return { serverName, config: serverConfig, results };
1440
- };
1441
-
1442
- // src/list.ts
1443
- var listInstalledMcpServers = (options = {}) => {
1444
- const agentTypes = options.agents ?? getMcpAgentTypes();
1445
- const collected = [];
1446
- for (const agentType of agentTypes) {
1447
- const agent = getMcpAgentConfig(agentType);
1448
- const { path, exists, servers } = agentConfigStore.listServers(agent, options);
1449
- if (!exists) continue;
1450
- for (const [serverName, rawConfig] of Object.entries(servers)) {
1451
- collected.push({
1452
- serverName,
1453
- agent: agentType,
1454
- path,
1455
- config: rawConfig,
1456
- serverConfig: parseServerConfig(rawConfig)
1457
- });
1458
- }
1459
- }
1460
- return collected;
1826
+ return {
1827
+ serverName: options.serverName,
1828
+ config: serverConfig,
1829
+ results,
1830
+ incompatible
1831
+ };
1461
1832
  };
1462
1833
 
1463
1834
  // src/remove.ts
1464
- var removeMcpServerFromAgent = (serverName, agentType, options = {}) => {
1465
- const agent = getMcpAgentConfig(agentType);
1466
- const { target } = agentConfigStore.resolveTarget(agent, options);
1467
- try {
1468
- const { removed } = agentConfigStore.removeServer(agent, serverName, options);
1469
- return { agent: agentType, path: target.configPath, removed };
1470
- } catch (error) {
1471
- return {
1472
- agent: agentType,
1473
- path: target.configPath,
1474
- removed: false,
1475
- error: toErrorMessage(error)
1476
- };
1477
- }
1478
- };
1479
1835
  var removeMcpServer = (options) => {
1480
1836
  const { allAgents } = resolveTargetAgents({
1481
1837
  requested: options.agents,
@@ -1483,24 +1839,35 @@ var removeMcpServer = (options) => {
1483
1839
  global: options.global,
1484
1840
  cwd: options.cwd
1485
1841
  });
1486
- const results = [];
1487
- for (const agentType of allAgents) {
1488
- const result = removeMcpServerFromAgent(options.name, agentType, {
1489
- global: options.global,
1490
- cwd: options.cwd
1491
- });
1492
- if (result.removed || result.error) results.push(result);
1842
+ const storeResults = agentConfigStore.removeServers(allAgents, options.name, {
1843
+ global: options.global,
1844
+ cwd: options.cwd
1845
+ });
1846
+ return storeResults.filter((r) => r.removed || Boolean(r.error));
1847
+ };
1848
+
1849
+ // src/utils/mask-secret.ts
1850
+ var SECRET_KEY_PATTERN = /(token|key|secret|password|passwd|auth|credential)/i;
1851
+ var maskSecretValue = (key, value) => {
1852
+ if (!SECRET_KEY_PATTERN.test(key) || value.length <= 4) {
1853
+ return value;
1854
+ }
1855
+ return `${value.slice(0, 2)}***${value.slice(-2)}`;
1856
+ };
1857
+ var SECRET_HEADER_PATTERN = /(authorization|token|key|secret|auth)/i;
1858
+ var maskSecretHeader = (key, value) => {
1859
+ if (!SECRET_HEADER_PATTERN.test(key) || value.length <= 8) {
1860
+ return value;
1493
1861
  }
1494
- return results;
1862
+ return `${value.slice(0, 4)}***${value.slice(-3)}`;
1495
1863
  };
1496
1864
 
1497
- // src/interactive/main-menu.ts
1498
- var import_prompts11 = require("@inquirer/prompts");
1865
+ // src/interactive/wizard-add.ts
1866
+ var import_prompts7 = require("@inquirer/prompts");
1499
1867
  var import_picocolors11 = __toESM(require("picocolors"), 1);
1500
1868
 
1501
- // src/interactive/wizard-add.ts
1502
- var import_prompts8 = require("@inquirer/prompts");
1503
- var import_picocolors8 = __toESM(require("picocolors"), 1);
1869
+ // src/utils/co-hosted-feedback.ts
1870
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
1504
1871
 
1505
1872
  // src/utils/logger.ts
1506
1873
  var import_picocolors = __toESM(require("picocolors"), 1);
@@ -1519,13 +1886,269 @@ var logger = {
1519
1886
  }
1520
1887
  };
1521
1888
 
1889
+ // src/utils/co-hosted-feedback.ts
1890
+ var formatCoHostedBadge = (kind, agents) => {
1891
+ if (!agents || agents.length === 0) return "";
1892
+ const label = kind === "configured" ? "co-configured" : "co-affected";
1893
+ return ` ${import_picocolors2.default.yellow(`(${label}: ${agents.join(", ")})`)}`;
1894
+ };
1895
+ var logCoHostedNotice = (kind, agents) => {
1896
+ if (!agents || agents.length === 0) return;
1897
+ const actionText = kind === "configured" ? "Also configured for" : "Also affects";
1898
+ logger.info(
1899
+ ` ${import_picocolors2.default.dim("Note:")} ${actionText} co-hosted agent(s): ${import_picocolors2.default.yellow(agents.join(", "))}`
1900
+ );
1901
+ };
1902
+
1522
1903
  // src/interactive/prompts/agents.ts
1523
- var import_prompts2 = require("@inquirer/prompts");
1904
+ var import_picocolors6 = __toESM(require("picocolors"), 1);
1905
+
1906
+ // src/interactive/utils/build-linked-agent-choices.ts
1524
1907
  var import_picocolors3 = __toESM(require("picocolors"), 1);
1908
+ var buildLinkedAgentChoices = (options) => {
1909
+ const { agents, checkedAgents, detectedAgents = [], scopeOptions = {} } = options;
1910
+ const alignedCheckedSet = new Set(checkedAgents);
1911
+ for (const agent of checkedAgents) {
1912
+ const coHosted = agentConfigStore.getCoHostedAgents(agent, scopeOptions);
1913
+ for (const co of coHosted) {
1914
+ if (agents.includes(co)) {
1915
+ alignedCheckedSet.add(co);
1916
+ }
1917
+ }
1918
+ }
1919
+ return agents.map((agent) => {
1920
+ const config = getMcpAgentConfig(agent);
1921
+ const displayName = config?.displayName ?? agent;
1922
+ const isDetected = detectedAgents.includes(agent);
1923
+ const coHosted = agentConfigStore.getCoHostedAgents(agent, scopeOptions).filter(
1924
+ (co) => agents.includes(co)
1925
+ );
1926
+ const detectedBadge = isDetected ? import_picocolors3.default.green(" [detected]") : "";
1927
+ const sharedBadge = coHosted.length > 0 ? import_picocolors3.default.dim(` [shared: ${coHosted.join(", ")}]`) : "";
1928
+ const label = `${displayName} ${import_picocolors3.default.dim(`(${agent})`)}${detectedBadge}${sharedBadge}`;
1929
+ return {
1930
+ name: label,
1931
+ value: agent,
1932
+ checked: alignedCheckedSet.has(agent),
1933
+ linkedValues: coHosted,
1934
+ description: coHosted.length > 0 ? `Linked with ${coHosted.map((a) => getMcpAgentConfig(a).displayName).join(", ")} (shared configuration)` : void 0
1935
+ };
1936
+ });
1937
+ };
1938
+
1939
+ // src/interactive/prompts/linked-checkbox.ts
1940
+ var import_core = require("@inquirer/core");
1941
+ var import_picocolors4 = __toESM(require("picocolors"), 1);
1942
+ var defaultTheme = {
1943
+ icon: {
1944
+ checked: import_picocolors4.default.green("[x]"),
1945
+ unchecked: import_picocolors4.default.dim("[ ]"),
1946
+ cursor: import_picocolors4.default.cyan(">"),
1947
+ disabledChecked: import_picocolors4.default.dim("[x]"),
1948
+ disabledUnchecked: import_picocolors4.default.dim("[-]")
1949
+ },
1950
+ style: {
1951
+ disabled: (text) => import_picocolors4.default.dim(text),
1952
+ renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", "),
1953
+ description: (text) => import_picocolors4.default.cyan(text),
1954
+ keysHelpTip: (keys) => keys.map(([key, action]) => `${import_picocolors4.default.bold(key)} ${import_picocolors4.default.dim(action)}`).join(import_picocolors4.default.dim(" | ")),
1955
+ highlight: (text) => import_picocolors4.default.cyan(text)
1956
+ },
1957
+ i18n: {
1958
+ disabledError: "This option is disabled and cannot be toggled."
1959
+ }
1960
+ };
1961
+ function isSelectable(item) {
1962
+ return !import_core.Separator.isSeparator(item) && !item.disabled;
1963
+ }
1964
+ function isNavigable(item) {
1965
+ return !import_core.Separator.isSeparator(item);
1966
+ }
1967
+ function isChecked(item) {
1968
+ return !import_core.Separator.isSeparator(item) && item.checked;
1969
+ }
1970
+ function normalizeChoices(choices) {
1971
+ return choices.map((choice) => {
1972
+ if (import_core.Separator.isSeparator(choice)) {
1973
+ return choice;
1974
+ }
1975
+ if (typeof choice !== "object" || choice === null || !("value" in choice)) {
1976
+ const name2 = String(choice);
1977
+ return {
1978
+ value: choice,
1979
+ name: name2,
1980
+ short: name2,
1981
+ checkedName: name2,
1982
+ disabled: false,
1983
+ checked: false,
1984
+ linkedValues: []
1985
+ };
1986
+ }
1987
+ const name = choice.name ?? String(choice.value);
1988
+ return {
1989
+ value: choice.value,
1990
+ name,
1991
+ short: choice.short ?? name,
1992
+ checkedName: choice.checkedName ?? name,
1993
+ description: choice.description,
1994
+ disabled: choice.disabled ?? false,
1995
+ checked: choice.checked ?? false,
1996
+ linkedValues: choice.linkedValues ?? []
1997
+ };
1998
+ });
1999
+ }
2000
+ var linkedCheckbox = (0, import_core.createPrompt)(
2001
+ (config, done) => {
2002
+ const { pageSize = 10, loop = true, required, validate = () => true } = config;
2003
+ const theme = (0, import_core.makeTheme)(defaultTheme, config.theme);
2004
+ const [status, setStatus] = (0, import_core.useState)("idle");
2005
+ const prefix = (0, import_core.usePrefix)({ status, theme });
2006
+ const [items, setItems] = (0, import_core.useState)(() => normalizeChoices(config.choices));
2007
+ const bounds = (0, import_core.useMemo)(() => {
2008
+ const first = items.findIndex(isNavigable);
2009
+ let last = -1;
2010
+ for (let i = items.length - 1; i >= 0; i--) {
2011
+ if (isNavigable(items[i])) {
2012
+ last = i;
2013
+ break;
2014
+ }
2015
+ }
2016
+ if (first === -1 || last === -1) {
2017
+ throw new import_core.ValidationError("[linkedCheckbox prompt] No selectable choices.");
2018
+ }
2019
+ return { first, last };
2020
+ }, [items]);
2021
+ const [active, setActive] = (0, import_core.useState)(bounds.first);
2022
+ const [errorMsg, setError] = (0, import_core.useState)();
2023
+ const toggleWithLinked = (targetIndex) => {
2024
+ const targetItem = items[targetIndex];
2025
+ if (!targetItem || import_core.Separator.isSeparator(targetItem) || targetItem.disabled) {
2026
+ return;
2027
+ }
2028
+ const nextChecked = !targetItem.checked;
2029
+ const targetValue = targetItem.value;
2030
+ const linked = new Set(targetItem.linkedValues);
2031
+ setItems(
2032
+ (prevItems) => prevItems.map((item) => {
2033
+ if (import_core.Separator.isSeparator(item) || item.disabled) {
2034
+ return item;
2035
+ }
2036
+ const isTargetOrLinked = item.value === targetValue || linked.has(item.value) || item.linkedValues.includes(targetValue);
2037
+ if (isTargetOrLinked) {
2038
+ return { ...item, checked: nextChecked };
2039
+ }
2040
+ return item;
2041
+ })
2042
+ );
2043
+ };
2044
+ (0, import_core.useKeypress)(async (key) => {
2045
+ if ((0, import_core.isEnterKey)(key)) {
2046
+ const selection = items.filter(isChecked);
2047
+ const isValid = await validate([...selection]);
2048
+ if (required && selection.length === 0) {
2049
+ setError("At least one choice must be selected");
2050
+ } else if (isValid === true) {
2051
+ setStatus("done");
2052
+ done(selection.map((choice) => choice.value));
2053
+ } else {
2054
+ setError(typeof isValid === "string" ? isValid : "You must select a valid value");
2055
+ }
2056
+ } else if ((0, import_core.isUpKey)(key) || (0, import_core.isDownKey)(key)) {
2057
+ if (errorMsg) setError(void 0);
2058
+ if (loop || (0, import_core.isUpKey)(key) && active !== bounds.first || (0, import_core.isDownKey)(key) && active !== bounds.last) {
2059
+ const offset = (0, import_core.isUpKey)(key) ? -1 : 1;
2060
+ let next = active;
2061
+ do {
2062
+ next = (next + offset + items.length) % items.length;
2063
+ } while (!isNavigable(items[next]));
2064
+ setActive(next);
2065
+ }
2066
+ } else if ((0, import_core.isSpaceKey)(key)) {
2067
+ const activeItem = items[active];
2068
+ if (activeItem && !import_core.Separator.isSeparator(activeItem)) {
2069
+ if (activeItem.disabled) {
2070
+ setError(theme.i18n.disabledError);
2071
+ } else {
2072
+ setError(void 0);
2073
+ toggleWithLinked(active);
2074
+ }
2075
+ }
2076
+ } else if (key.name === "a") {
2077
+ const hasUnchecked = items.some((choice) => isSelectable(choice) && !choice.checked);
2078
+ setItems(
2079
+ (prevItems) => prevItems.map((item) => isSelectable(item) ? { ...item, checked: hasUnchecked } : item)
2080
+ );
2081
+ } else if ((0, import_core.isNumberKey)(key)) {
2082
+ const selectedIndex = Number(key.name) - 1;
2083
+ let selectableIndex = -1;
2084
+ const position = items.findIndex((item) => {
2085
+ if (import_core.Separator.isSeparator(item)) return false;
2086
+ selectableIndex++;
2087
+ return selectableIndex === selectedIndex;
2088
+ });
2089
+ const selectedItem = items[position];
2090
+ if (selectedItem && isSelectable(selectedItem)) {
2091
+ setActive(position);
2092
+ setError(void 0);
2093
+ toggleWithLinked(position);
2094
+ }
2095
+ }
2096
+ });
2097
+ const message = theme.style.message(config.message, status);
2098
+ let description;
2099
+ const page = (0, import_core.usePagination)({
2100
+ items,
2101
+ active,
2102
+ renderItem({ item, isActive }) {
2103
+ if (import_core.Separator.isSeparator(item)) {
2104
+ return ` ${item.separator}`;
2105
+ }
2106
+ const cursor = isActive ? theme.icon.cursor : " ";
2107
+ if (item.disabled) {
2108
+ const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
2109
+ const checkbox2 = item.checked ? theme.icon.disabledChecked : theme.icon.disabledUnchecked;
2110
+ return theme.style.disabled(`${cursor} ${checkbox2} ${item.name} ${disabledLabel}`);
2111
+ }
2112
+ if (isActive) {
2113
+ description = item.description;
2114
+ }
2115
+ const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
2116
+ const name = item.checked ? item.checkedName : item.name;
2117
+ const color = isActive ? theme.style.highlight : (x) => x;
2118
+ return color(`${cursor} ${checkbox} ${name}`);
2119
+ },
2120
+ pageSize,
2121
+ loop
2122
+ });
2123
+ if (status === "done") {
2124
+ const selection = items.filter(isChecked);
2125
+ const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
2126
+ return [prefix, message, answer].filter(Boolean).join(" ");
2127
+ }
2128
+ const helpLine = theme.style.keysHelpTip([
2129
+ ["up/down", "navigate"],
2130
+ ["space", "toggle"],
2131
+ ["a", "all"],
2132
+ ["enter", "submit"]
2133
+ ]);
2134
+ const lines = [
2135
+ [prefix, message].filter(Boolean).join(" "),
2136
+ page,
2137
+ helpLine
2138
+ ];
2139
+ if (description) {
2140
+ lines.push(theme.style.description(description));
2141
+ }
2142
+ if (errorMsg) {
2143
+ lines.push(theme.style.error(errorMsg));
2144
+ }
2145
+ return lines.join("\n");
2146
+ }
2147
+ );
1525
2148
 
1526
2149
  // src/interactive/prompts/scope.ts
1527
2150
  var import_prompts = require("@inquirer/prompts");
1528
- var import_picocolors2 = __toESM(require("picocolors"), 1);
2151
+ var import_picocolors5 = __toESM(require("picocolors"), 1);
1529
2152
  var promptScope = async (options = {}) => {
1530
2153
  const initialGlobal = options.defaultGlobal ?? options.global;
1531
2154
  if (initialGlobal !== void 0) {
@@ -1536,11 +2159,11 @@ var promptScope = async (options = {}) => {
1536
2159
  message: options.message ?? "Select MCP scope:",
1537
2160
  choices: [
1538
2161
  {
1539
- name: `Current Project - ${import_picocolors2.default.dim(cwd)}`,
2162
+ name: `Current Project - ${import_picocolors5.default.dim(cwd)}`,
1540
2163
  value: false
1541
2164
  },
1542
2165
  {
1543
- name: `Global User Config - ${import_picocolors2.default.dim("applies across all projects")}`,
2166
+ name: `Global User Config - ${import_picocolors5.default.dim("applies across all projects")}`,
1544
2167
  value: true
1545
2168
  }
1546
2169
  ]
@@ -1560,26 +2183,26 @@ var promptScopeAndAgents = async (options = {}) => {
1560
2183
  cwd
1561
2184
  });
1562
2185
  const detected = resolution.detected;
1563
- const availableAgentTypes = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
2186
+ const rawAvailable = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
2187
+ const availableAgentTypes = agentConfigStore.sortAgentsByClusters(rawAvailable, {
2188
+ global: isGlobal,
2189
+ cwd
2190
+ });
1564
2191
  if (detected.length > 0) {
1565
2192
  logger.info(
1566
- `Detected configured agents: ${import_picocolors3.default.cyan(detected.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
2193
+ `Detected configured agents: ${import_picocolors6.default.cyan(detected.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
1567
2194
  );
1568
2195
  } else {
1569
2196
  logger.warn(`No active ${isGlobal ? "global" : "project"} agents detected`);
1570
2197
  }
1571
2198
  const defaultChecked = options.defaultAgents && options.defaultAgents.length > 0 ? options.defaultAgents : detected;
1572
- const choices = availableAgentTypes.map((agentType) => {
1573
- const config = getMcpAgentConfig(agentType);
1574
- const isDetected = detected.includes(agentType);
1575
- const label = `${config.displayName} ${import_picocolors3.default.dim(`(${agentType})`)}${isDetected ? import_picocolors3.default.green(" [detected]") : ""}`;
1576
- return {
1577
- name: label,
1578
- value: agentType,
1579
- checked: defaultChecked.includes(agentType)
1580
- };
2199
+ const choices = buildLinkedAgentChoices({
2200
+ agents: availableAgentTypes,
2201
+ checkedAgents: defaultChecked,
2202
+ detectedAgents: detected,
2203
+ scopeOptions: { global: isGlobal, cwd }
1581
2204
  });
1582
- const selectedAgents = await (0, import_prompts2.checkbox)({
2205
+ const selectedAgents = await linkedCheckbox({
1583
2206
  message: "Select target agents (Space to select, Enter to confirm):",
1584
2207
  choices,
1585
2208
  validate: (chosen) => {
@@ -1596,7 +2219,7 @@ var promptScopeAndAgents = async (options = {}) => {
1596
2219
  };
1597
2220
 
1598
2221
  // src/interactive/prompts/args.ts
1599
- var import_prompts3 = require("@inquirer/prompts");
2222
+ var import_prompts2 = require("@inquirer/prompts");
1600
2223
  var parseArgsString = (rawText) => {
1601
2224
  const matches = rawText.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
1602
2225
  if (!matches) return [];
@@ -1611,14 +2234,14 @@ var promptArgsConfig = async (initialArgs = []) => {
1611
2234
  if (initialArgs.length > 0) {
1612
2235
  return initialArgs;
1613
2236
  }
1614
- const needArgs = await (0, import_prompts3.confirm)({
2237
+ const needArgs = await (0, import_prompts2.confirm)({
1615
2238
  message: "Configure command arguments (e.g. file paths, connection strings)?",
1616
2239
  default: false
1617
2240
  });
1618
2241
  if (!needArgs) {
1619
2242
  return [];
1620
2243
  }
1621
- const raw = await (0, import_prompts3.input)({
2244
+ const raw = await (0, import_prompts2.input)({
1622
2245
  message: "Enter command arguments (space-separated, wrap paths with spaces in quotes):",
1623
2246
  validate: (val) => val.trim() ? true : "Arguments cannot be empty"
1624
2247
  });
@@ -1629,7 +2252,7 @@ var formatArgsString = (args) => {
1629
2252
  };
1630
2253
  var promptEditArgs = async (currentArgs = []) => {
1631
2254
  const defaultStr = formatArgsString(currentArgs);
1632
- const raw = await (0, import_prompts3.input)({
2255
+ const raw = await (0, import_prompts2.input)({
1633
2256
  message: "Edit command arguments (space-separated, wrap paths with spaces in quotes, leave empty to clear):",
1634
2257
  default: defaultStr
1635
2258
  });
@@ -1641,17 +2264,21 @@ var promptEditArgs = async (currentArgs = []) => {
1641
2264
  };
1642
2265
 
1643
2266
  // src/interactive/prompts/env.ts
1644
- var import_prompts6 = require("@inquirer/prompts");
1645
- var import_picocolors6 = __toESM(require("picocolors"), 1);
2267
+ var import_prompts5 = require("@inquirer/prompts");
2268
+ var import_picocolors9 = __toESM(require("picocolors"), 1);
2269
+
2270
+ // src/interactive/prompts/kv.ts
2271
+ var import_prompts4 = require("@inquirer/prompts");
2272
+ var import_picocolors8 = __toESM(require("picocolors"), 1);
1646
2273
 
1647
2274
  // src/interactive/prompts/multiline.ts
1648
2275
  var import_node_readline = require("readline");
1649
- var import_prompts4 = require("@inquirer/prompts");
1650
- var import_picocolors4 = __toESM(require("picocolors"), 1);
2276
+ var import_prompts3 = require("@inquirer/prompts");
2277
+ var import_picocolors7 = __toESM(require("picocolors"), 1);
1651
2278
  var readMultilineTextFromTerminal = async (message, endHint = "When done pasting, enter END on a new line or press Enter twice to finish") => {
1652
- console.log(import_picocolors4.default.cyan(`
2279
+ console.log(import_picocolors7.default.cyan(`
1653
2280
  ${message}`));
1654
- console.log(import_picocolors4.default.dim(` (Hint: ${endHint})
2281
+ console.log(import_picocolors7.default.dim(` (Hint: ${endHint})
1655
2282
  `));
1656
2283
  return new Promise((resolve) => {
1657
2284
  const rl = (0, import_node_readline.createInterface)({
@@ -1695,7 +2322,7 @@ ${message}`));
1695
2322
  };
1696
2323
  var promptEditorText = async (options) => {
1697
2324
  try {
1698
- return await (0, import_prompts4.editor)({
2325
+ return await (0, import_prompts3.editor)({
1699
2326
  message: options.message,
1700
2327
  default: options.defaultText ?? "",
1701
2328
  postfix: options.postfix
@@ -1706,24 +2333,22 @@ var promptEditorText = async (options) => {
1706
2333
  };
1707
2334
 
1708
2335
  // src/interactive/prompts/kv.ts
1709
- var import_prompts5 = require("@inquirer/prompts");
1710
- var import_picocolors5 = __toESM(require("picocolors"), 1);
1711
2336
  var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1712
2337
  let items = { ...currentItems };
1713
2338
  while (true) {
1714
2339
  const keys = Object.keys(items);
1715
2340
  console.log();
1716
2341
  if (keys.length === 0) {
1717
- console.log(import_picocolors5.default.dim(` No ${options.itemsNoun} configured.`));
2342
+ console.log(import_picocolors8.default.dim(` No ${options.itemsNoun} configured.`));
1718
2343
  } else {
1719
- console.log(import_picocolors5.default.cyan(import_picocolors5.default.bold(` Configured ${options.title} (${keys.length}):`)));
2344
+ console.log(import_picocolors8.default.cyan(import_picocolors8.default.bold(` Configured ${options.title} (${keys.length}):`)));
1720
2345
  for (const [k, v] of Object.entries(items)) {
1721
2346
  const sep = options.separator === "=" ? "=" : ": ";
1722
- console.log(` ${import_picocolors5.default.bold(k)}${sep}${import_picocolors5.default.dim(options.maskValue(k, v))}`);
2347
+ console.log(` ${import_picocolors8.default.bold(k)}${sep}${import_picocolors8.default.dim(options.maskValue(k, v))}`);
1723
2348
  }
1724
2349
  }
1725
2350
  console.log();
1726
- const choice = await (0, import_prompts5.select)({
2351
+ const choice = await (0, import_prompts4.select)({
1727
2352
  message: `Manage ${options.itemsNoun}:`,
1728
2353
  choices: [
1729
2354
  {
@@ -1770,7 +2395,7 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1770
2395
  items = parsed;
1771
2396
  logger.success(`${options.title} updated (${Object.keys(items).length} total)`);
1772
2397
  } else if (choice === "upsert") {
1773
- const key = await (0, import_prompts5.input)({
2398
+ const key = await (0, import_prompts4.input)({
1774
2399
  message: options.keyPromptMessage,
1775
2400
  validate: (val) => {
1776
2401
  const trimmed = val.trim();
@@ -1784,7 +2409,7 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1784
2409
  const isSecret = options.isSecretKey(trimmedKey);
1785
2410
  let newVal;
1786
2411
  if (isSecret) {
1787
- newVal = await (0, import_prompts5.password)({
2412
+ newVal = await (0, import_prompts4.password)({
1788
2413
  message: existingVal !== void 0 ? `New value for (${trimmedKey}) [leave empty to keep current]:` : `${options.valuePromptMessage} for (${trimmedKey}) [sensitive content masked]:`,
1789
2414
  mask: "*"
1790
2415
  });
@@ -1792,15 +2417,15 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1792
2417
  newVal = existingVal;
1793
2418
  }
1794
2419
  } else {
1795
- newVal = await (0, import_prompts5.input)({
2420
+ newVal = await (0, import_prompts4.input)({
1796
2421
  message: `${options.valuePromptMessage} for (${trimmedKey}):`,
1797
2422
  default: existingVal
1798
2423
  });
1799
2424
  }
1800
2425
  items[trimmedKey] = newVal;
1801
- logger.success(`${existingVal !== void 0 ? "Updated" : "Added"}: ${import_picocolors5.default.cyan(trimmedKey)}`);
2426
+ logger.success(`${existingVal !== void 0 ? "Updated" : "Added"}: ${import_picocolors8.default.cyan(trimmedKey)}`);
1802
2427
  } else if (choice === "delete") {
1803
- const toDelete = await (0, import_prompts5.select)({
2428
+ const toDelete = await (0, import_prompts4.select)({
1804
2429
  message: `Select ${options.itemNoun} to delete:`,
1805
2430
  choices: [
1806
2431
  ...keys.map((k) => ({ name: k, value: k })),
@@ -1809,7 +2434,7 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1809
2434
  });
1810
2435
  if (toDelete !== "__cancel__") {
1811
2436
  delete items[toDelete];
1812
- logger.success(`Deleted: ${import_picocolors5.default.cyan(toDelete)}`);
2437
+ logger.success(`Deleted: ${import_picocolors8.default.cyan(toDelete)}`);
1813
2438
  }
1814
2439
  } else if (choice === "paste") {
1815
2440
  const pasted = await readMultilineTextFromTerminal(options.pasteMessage);
@@ -1819,7 +2444,7 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1819
2444
  logger.warn(`No valid ${options.itemsNoun} recognized`);
1820
2445
  } else {
1821
2446
  if (keys.length > 0) {
1822
- const pasteMode = await (0, import_prompts5.select)({
2447
+ const pasteMode = await (0, import_prompts4.select)({
1823
2448
  message: `How to apply pasted ${options.itemsNoun}?`,
1824
2449
  choices: [
1825
2450
  { name: `Merge with existing ${options.itemsNoun}`, value: "merge" },
@@ -1834,10 +2459,10 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1834
2459
  } else {
1835
2460
  items = parsed;
1836
2461
  }
1837
- logger.success(`Successfully applied ${import_picocolors5.default.cyan(String(count))} ${options.itemsNoun}`);
2462
+ logger.success(`Successfully applied ${import_picocolors8.default.cyan(String(count))} ${options.itemsNoun}`);
1838
2463
  }
1839
2464
  } else if (choice === "clear") {
1840
- const confirmClear = await (0, import_prompts5.confirm)({
2465
+ const confirmClear = await (0, import_prompts4.confirm)({
1841
2466
  message: `Are you sure you want to clear all ${options.itemsNoun}?`,
1842
2467
  default: false
1843
2468
  });
@@ -1850,13 +2475,6 @@ var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1850
2475
  };
1851
2476
 
1852
2477
  // src/interactive/prompts/env.ts
1853
- var SECRET_KEY_PATTERN = /(token|key|secret|password|passwd|auth|credential)/i;
1854
- var maskSecretValue = (key, value) => {
1855
- if (!SECRET_KEY_PATTERN.test(key) || value.length <= 4) {
1856
- return value;
1857
- }
1858
- return `${value.slice(0, 2)}***${value.slice(-2)}`;
1859
- };
1860
2478
  var formatEnvText = (env) => {
1861
2479
  return Object.entries(env).map(([key, value]) => {
1862
2480
  if (/[\s"']/.test(value)) {
@@ -1890,9 +2508,9 @@ var promptEnvConfig = async (initialEnv = {}) => {
1890
2508
  const env = { ...initialEnv };
1891
2509
  const initialCount = Object.keys(env).length;
1892
2510
  if (initialCount > 0) {
1893
- logger.info(`Includes ${import_picocolors6.default.cyan(String(initialCount))} preset environment variables`);
2511
+ logger.info(`Includes ${import_picocolors9.default.cyan(String(initialCount))} preset environment variables`);
1894
2512
  }
1895
- const mode = await (0, import_prompts6.select)({
2513
+ const mode = await (0, import_prompts5.select)({
1896
2514
  message: "Configure environment variables?",
1897
2515
  choices: [
1898
2516
  {
@@ -1928,16 +2546,16 @@ var promptEnvConfig = async (initialEnv = {}) => {
1928
2546
  logger.warn("No valid KEY=VALUE pairs recognized");
1929
2547
  } else {
1930
2548
  Object.assign(env, parsed);
1931
- logger.success(`Successfully parsed ${import_picocolors6.default.cyan(String(count))} environment variables:`);
2549
+ logger.success(`Successfully parsed ${import_picocolors9.default.cyan(String(count))} environment variables:`);
1932
2550
  for (const [k, v] of Object.entries(parsed)) {
1933
- console.log(` ${import_picocolors6.default.bold(k)}=${import_picocolors6.default.dim(maskSecretValue(k, v))}`);
2551
+ console.log(` ${import_picocolors9.default.bold(k)}=${import_picocolors9.default.dim(maskSecretValue(k, v))}`);
1934
2552
  }
1935
2553
  }
1936
2554
  return env;
1937
2555
  }
1938
2556
  logger.info("Entering environment variables (leave key empty and press enter to finish):");
1939
2557
  while (true) {
1940
- const key = await (0, import_prompts6.input)({
2558
+ const key = await (0, import_prompts5.input)({
1941
2559
  message: "Variable name (Key, leave empty to finish):",
1942
2560
  validate: (val2) => {
1943
2561
  const trimmed = val2.trim();
@@ -1951,17 +2569,17 @@ var promptEnvConfig = async (initialEnv = {}) => {
1951
2569
  const isSecret = SECRET_KEY_PATTERN.test(trimmedKey);
1952
2570
  let val;
1953
2571
  if (isSecret) {
1954
- val = await (0, import_prompts6.password)({
2572
+ val = await (0, import_prompts5.password)({
1955
2573
  message: `Value for (${trimmedKey}) [secret masked]:`,
1956
2574
  mask: "*"
1957
2575
  });
1958
2576
  } else {
1959
- val = await (0, import_prompts6.input)({
2577
+ val = await (0, import_prompts5.input)({
1960
2578
  message: `Value for (${trimmedKey}):`
1961
2579
  });
1962
2580
  }
1963
2581
  env[trimmedKey] = val;
1964
- logger.success(`Added: ${import_picocolors6.default.cyan(trimmedKey)}`);
2582
+ logger.success(`Added: ${import_picocolors9.default.cyan(trimmedKey)}`);
1965
2583
  }
1966
2584
  return env;
1967
2585
  };
@@ -1982,15 +2600,8 @@ var promptEditEnvConfig = async (currentEnv = {}) => promptEditKeyValueConfig(cu
1982
2600
  });
1983
2601
 
1984
2602
  // src/interactive/prompts/headers.ts
1985
- var import_prompts7 = require("@inquirer/prompts");
1986
- var import_picocolors7 = __toESM(require("picocolors"), 1);
1987
- var SECRET_HEADER_PATTERN = /(authorization|token|key|secret|auth)/i;
1988
- var maskSecretHeader = (key, value) => {
1989
- if (!SECRET_HEADER_PATTERN.test(key) || value.length <= 8) {
1990
- return value;
1991
- }
1992
- return `${value.slice(0, 4)}***${value.slice(-3)}`;
1993
- };
2603
+ var import_prompts6 = require("@inquirer/prompts");
2604
+ var import_picocolors10 = __toESM(require("picocolors"), 1);
1994
2605
  var formatHeadersText = (headers) => {
1995
2606
  return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
1996
2607
  };
@@ -2023,7 +2634,7 @@ var parseHeadersText = (rawText) => {
2023
2634
  };
2024
2635
  var promptHeadersConfig = async (initialHeaders = {}) => {
2025
2636
  const headers = { ...initialHeaders };
2026
- const mode = await (0, import_prompts7.select)({
2637
+ const mode = await (0, import_prompts6.select)({
2027
2638
  message: "Select HTTP headers configuration method:",
2028
2639
  choices: [
2029
2640
  {
@@ -2060,16 +2671,16 @@ var promptHeadersConfig = async (initialHeaders = {}) => {
2060
2671
  logger.warn("No valid Key: Value pairs recognized");
2061
2672
  } else {
2062
2673
  Object.assign(headers, parsed);
2063
- logger.success(`Successfully parsed ${import_picocolors7.default.cyan(String(count))} headers:`);
2674
+ logger.success(`Successfully parsed ${import_picocolors10.default.cyan(String(count))} headers:`);
2064
2675
  for (const [k, v] of Object.entries(parsed)) {
2065
- console.log(` ${import_picocolors7.default.bold(k)}: ${import_picocolors7.default.dim(maskSecretHeader(k, v))}`);
2676
+ console.log(` ${import_picocolors10.default.bold(k)}: ${import_picocolors10.default.dim(maskSecretHeader(k, v))}`);
2066
2677
  }
2067
2678
  }
2068
2679
  return headers;
2069
2680
  }
2070
2681
  logger.info("Entering HTTP headers (leave header name empty and press enter to finish):");
2071
2682
  while (true) {
2072
- const name = await (0, import_prompts7.input)({
2683
+ const name = await (0, import_prompts6.input)({
2073
2684
  message: "Header name (e.g. Authorization, leave empty to finish):",
2074
2685
  validate: (val2) => {
2075
2686
  const trimmed = val2.trim();
@@ -2083,17 +2694,17 @@ var promptHeadersConfig = async (initialHeaders = {}) => {
2083
2694
  const isSecret = SECRET_HEADER_PATTERN.test(trimmedName);
2084
2695
  let val;
2085
2696
  if (isSecret) {
2086
- val = await (0, import_prompts7.password)({
2697
+ val = await (0, import_prompts6.password)({
2087
2698
  message: `Header value for (${trimmedName}) [sensitive content masked]:`,
2088
2699
  mask: "*"
2089
2700
  });
2090
2701
  } else {
2091
- val = await (0, import_prompts7.input)({
2702
+ val = await (0, import_prompts6.input)({
2092
2703
  message: `Header value for (${trimmedName}):`
2093
2704
  });
2094
2705
  }
2095
2706
  headers[trimmedName] = val;
2096
- logger.success(`Added: ${import_picocolors7.default.cyan(trimmedName)}`);
2707
+ logger.success(`Added: ${import_picocolors10.default.cyan(trimmedName)}`);
2097
2708
  }
2098
2709
  return headers;
2099
2710
  };
@@ -2115,10 +2726,10 @@ var promptEditHeadersConfig = async (currentHeaders = {}) => promptEditKeyValueC
2115
2726
  // src/interactive/wizard-add.ts
2116
2727
  var wizardAdd = async (initial = {}) => {
2117
2728
  const cwd = initial.cwd ?? process.cwd();
2118
- logger.info(import_picocolors8.default.bold("Welcome to the MCP interactive add wizard"));
2729
+ logger.info(import_picocolors11.default.bold("Welcome to the MCP interactive add wizard"));
2119
2730
  let source = initial.source;
2120
2731
  if (!source) {
2121
- const sourceType = await (0, import_prompts8.select)({
2732
+ const sourceType = await (0, import_prompts7.select)({
2122
2733
  message: "Select MCP server type:",
2123
2734
  choices: [
2124
2735
  {
@@ -2136,12 +2747,12 @@ var wizardAdd = async (initial = {}) => {
2136
2747
  ]
2137
2748
  });
2138
2749
  if (sourceType === "npm") {
2139
- source = await (0, import_prompts8.input)({
2750
+ source = await (0, import_prompts7.input)({
2140
2751
  message: "Enter npm package name (e.g. @modelcontextprotocol/server-postgres):",
2141
2752
  validate: (val) => val.trim() ? true : "Package name cannot be empty"
2142
2753
  });
2143
2754
  } else if (sourceType === "remote") {
2144
- source = await (0, import_prompts8.input)({
2755
+ source = await (0, import_prompts7.input)({
2145
2756
  message: "Enter remote server URL (e.g. https://mcp.example.com/sse):",
2146
2757
  validate: (val) => {
2147
2758
  const trimmed = val.trim();
@@ -2151,7 +2762,7 @@ var wizardAdd = async (initial = {}) => {
2151
2762
  }
2152
2763
  });
2153
2764
  } else {
2154
- source = await (0, import_prompts8.input)({
2765
+ source = await (0, import_prompts7.input)({
2155
2766
  message: "Enter command and arguments (e.g. python -m my_mcp_server or docker run ...):",
2156
2767
  validate: (val) => val.trim() ? true : "Command cannot be empty"
2157
2768
  });
@@ -2161,7 +2772,7 @@ var wizardAdd = async (initial = {}) => {
2161
2772
  const parsed = parseMcpSource(source);
2162
2773
  let serverName = initial.name;
2163
2774
  if (!serverName) {
2164
- serverName = await (0, import_prompts8.input)({
2775
+ serverName = await (0, import_prompts7.input)({
2165
2776
  message: "MCP server name:",
2166
2777
  default: parsed.inferredName,
2167
2778
  validate: (val) => val.trim() ? true : "Server name cannot be empty"
@@ -2173,7 +2784,7 @@ var wizardAdd = async (initial = {}) => {
2173
2784
  if (parsed.type === "remote") {
2174
2785
  if (!transport) {
2175
2786
  const isSseUrl = /\/sse\b/i.test(parsed.value);
2176
- transport = await (0, import_prompts8.select)({
2787
+ transport = await (0, import_prompts7.select)({
2177
2788
  message: "Select remote transport protocol:",
2178
2789
  choices: [
2179
2790
  { name: "HTTP", value: "http" },
@@ -2183,7 +2794,7 @@ var wizardAdd = async (initial = {}) => {
2183
2794
  });
2184
2795
  }
2185
2796
  if (Object.keys(headers).length === 0) {
2186
- const needHeader = await (0, import_prompts8.confirm)({
2797
+ const needHeader = await (0, import_prompts7.confirm)({
2187
2798
  message: "Configure HTTP headers (e.g. Authorization Bearer token)?",
2188
2799
  default: false
2189
2800
  });
@@ -2205,28 +2816,28 @@ var wizardAdd = async (initial = {}) => {
2205
2816
  if (parsed.type !== "remote") {
2206
2817
  env = await promptEnvConfig(env);
2207
2818
  }
2208
- console.log("\n" + import_picocolors8.default.cyan(import_picocolors8.default.bold("Configuration Preview:")));
2209
- console.log(` ${import_picocolors8.default.bold("Server Name:")} ${import_picocolors8.default.green(serverName)}`);
2210
- console.log(` ${import_picocolors8.default.bold("Server Type:")} ${import_picocolors8.default.magenta(parsed.type)}`);
2211
- console.log(` ${import_picocolors8.default.bold("Source/Command:")} ${import_picocolors8.default.dim(source)}`);
2212
- console.log(` ${import_picocolors8.default.bold("Scope:")} ${isGlobal ? import_picocolors8.default.yellow("Global") : import_picocolors8.default.blue("Project")}`);
2213
- console.log(` ${import_picocolors8.default.bold("Target Agents:")} ${import_picocolors8.default.cyan(selectedAgents.join(", "))}`);
2819
+ console.log("\n" + import_picocolors11.default.cyan(import_picocolors11.default.bold("Configuration Preview:")));
2820
+ console.log(` ${import_picocolors11.default.bold("Server Name:")} ${import_picocolors11.default.green(serverName)}`);
2821
+ console.log(` ${import_picocolors11.default.bold("Server Type:")} ${import_picocolors11.default.magenta(parsed.type)}`);
2822
+ console.log(` ${import_picocolors11.default.bold("Source/Command:")} ${import_picocolors11.default.dim(source)}`);
2823
+ console.log(` ${import_picocolors11.default.bold("Scope:")} ${isGlobal ? import_picocolors11.default.yellow("Global") : import_picocolors11.default.blue("Project")}`);
2824
+ console.log(` ${import_picocolors11.default.bold("Target Agents:")} ${import_picocolors11.default.cyan(selectedAgents.join(", "))}`);
2214
2825
  if (args.length > 0) {
2215
- console.log(` ${import_picocolors8.default.bold("Arguments:")} ${import_picocolors8.default.dim(args.join(" "))}`);
2826
+ console.log(` ${import_picocolors11.default.bold("Arguments:")} ${import_picocolors11.default.dim(args.join(" "))}`);
2216
2827
  }
2217
2828
  if (transport) {
2218
- console.log(` ${import_picocolors8.default.bold("Transport:")} ${import_picocolors8.default.magenta(transport)}`);
2829
+ console.log(` ${import_picocolors11.default.bold("Transport:")} ${import_picocolors11.default.magenta(transport)}`);
2219
2830
  }
2220
2831
  const envKeys = Object.keys(env);
2221
2832
  if (envKeys.length > 0) {
2222
- console.log(` ${import_picocolors8.default.bold("Environment Variables:")} ${import_picocolors8.default.dim(envKeys.join(", "))} (${envKeys.length})`);
2833
+ console.log(` ${import_picocolors11.default.bold("Environment Variables:")} ${import_picocolors11.default.dim(envKeys.join(", "))} (${envKeys.length})`);
2223
2834
  }
2224
2835
  const headerKeys = Object.keys(headers);
2225
2836
  if (headerKeys.length > 0) {
2226
- console.log(` ${import_picocolors8.default.bold("Headers:")} ${import_picocolors8.default.dim(headerKeys.join(", "))} (${headerKeys.length})`);
2837
+ console.log(` ${import_picocolors11.default.bold("Headers:")} ${import_picocolors11.default.dim(headerKeys.join(", "))} (${headerKeys.length})`);
2227
2838
  }
2228
2839
  console.log();
2229
- const proceed = await (0, import_prompts8.confirm)({
2840
+ const proceed = await (0, import_prompts7.confirm)({
2230
2841
  message: "Confirm installation with this configuration?",
2231
2842
  default: true
2232
2843
  });
@@ -2246,103 +2857,281 @@ var wizardAdd = async (initial = {}) => {
2246
2857
  env
2247
2858
  });
2248
2859
  logger.info(
2249
- `Writing ${import_picocolors8.default.bold(result.serverName)} to ${import_picocolors8.default.cyan(String(result.results.length))} agent config files...`
2860
+ `Writing ${import_picocolors11.default.bold(result.serverName)} to ${import_picocolors11.default.cyan(String(result.results.length))} agent config files...`
2250
2861
  );
2251
2862
  let allSuccess = true;
2252
2863
  for (const record of result.results) {
2253
2864
  if (record.success) {
2254
- logger.success(`${import_picocolors8.default.cyan(record.agent)}: Successfully written to ${import_picocolors8.default.dim(record.path)}`);
2865
+ logger.success(
2866
+ `${import_picocolors11.default.cyan(record.agent)}: Successfully written to ${import_picocolors11.default.dim(record.path)}${formatCoHostedBadge("configured", record.coConfiguredAgents)}`
2867
+ );
2255
2868
  } else {
2256
2869
  allSuccess = false;
2257
- logger.error(`${import_picocolors8.default.cyan(record.agent)}: Failed to write - ${record.error}`);
2870
+ logger.error(`${import_picocolors11.default.cyan(record.agent)}: Failed to write - ${record.error}`);
2871
+ }
2872
+ }
2873
+ if (allSuccess) {
2874
+ logger.success(import_picocolors11.default.bold(`MCP server "${serverName}" configured successfully!`));
2875
+ }
2876
+ return allSuccess;
2877
+ };
2878
+
2879
+ // src/utils/format-agent-list.ts
2880
+ var formatAgentList = (agentList, emptyLabel = "(none)") => agentList.length === 0 ? emptyLabel : agentList.join(", ");
2881
+
2882
+ // src/utils/parse-key-value-list.ts
2883
+ var parseKeyValueList = (entries, separator) => {
2884
+ if (!entries || entries.length === 0) return {};
2885
+ const result = {};
2886
+ for (const entry of entries) {
2887
+ const splitIndex = entry.indexOf(separator);
2888
+ if (splitIndex === -1) {
2889
+ throw new Error(`Invalid entry "${entry}": expected "${separator}" separator`);
2890
+ }
2891
+ const key = entry.slice(0, splitIndex).trim();
2892
+ const value = entry.slice(splitIndex + separator.length).trim();
2893
+ if (!key) throw new Error(`Invalid entry "${entry}": empty key`);
2894
+ result[key] = value;
2895
+ }
2896
+ return result;
2897
+ };
2898
+
2899
+ // src/utils/resolve-transport.ts
2900
+ var resolveTransport = (input7) => {
2901
+ if (!input7) return void 0;
2902
+ if (input7 === "http" || input7 === "sse") return input7;
2903
+ throw new Error(`Unsupported transport "${input7}" (expected: http, sse)`);
2904
+ };
2905
+
2906
+ // src/cli/add.ts
2907
+ var mcpAddCommand = new import_commander.Command("add").description("Add an MCP server to coding agents").argument("[source]", "Remote URL, npm package, or command line").option("-a, --agent <agents...>", "Target specific agents (use '*' for all)").option("-g, --global", "Install to user-level config instead of project").option("-t, --transport <type>", "Transport type for remote servers (http or sse)").option("--header <header...>", "HTTP header (Key: Value), repeatable").option("--env <env...>", "Env var for stdio servers (KEY=VALUE), repeatable").option("--args <args...>", "CLI arguments for stdio/package servers").option("-n, --name <name>", "Server name override").option("-y, --yes", "Skip all prompts").option("--all", "Install to all supported agents").action(async (source, options) => {
2908
+ try {
2909
+ const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
2910
+ if (!source) {
2911
+ if (isInteractive) {
2912
+ const success = await wizardAdd({
2913
+ name: options.name,
2914
+ global: options.global,
2915
+ args: options.args,
2916
+ transport: resolveTransport(options.transport),
2917
+ headers: parseKeyValueList(options.header, ":"),
2918
+ env: parseKeyValueList(options.env, "="),
2919
+ agents: options.all ? getMcpAgentTypes() : parseMcpAgentList(options.agent)
2920
+ });
2921
+ if (!success) process.exitCode = 1;
2922
+ return;
2923
+ }
2924
+ logger.error('Missing required argument: "source" (e.g. mcps add @modelcontextprotocol/server-filesystem)');
2925
+ process.exitCode = 1;
2926
+ return;
2927
+ }
2928
+ const parsed = parseMcpSource(source);
2929
+ const cwd = process.cwd();
2930
+ const isGlobal = Boolean(options.global);
2931
+ const explicitTransport = resolveTransport(options.transport);
2932
+ const transport = explicitTransport ?? (parsed.type === "remote" ? "http" : "stdio");
2933
+ const resolvedTargets = resolveTargetAgents({
2934
+ requested: options.agent,
2935
+ all: options.all,
2936
+ global: isGlobal,
2937
+ cwd,
2938
+ transport
2939
+ });
2940
+ if (resolvedTargets.agents.length === 0) {
2941
+ const message = resolvedTargets.diagnostic ?? `No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass ${import_picocolors12.default.cyan("-a <agent>")} (e.g. ${import_picocolors12.default.cyan("-a cursor")}) or ${import_picocolors12.default.cyan("--all")} to install.`;
2942
+ logger.warn(message);
2943
+ process.exitCode = 1;
2944
+ return;
2945
+ }
2946
+ if (resolvedTargets.isDetected) {
2947
+ logger.info(
2948
+ `Detected ${isGlobal ? "global" : "project"} agents: ${import_picocolors12.default.cyan(formatAgentList(resolvedTargets.detected, "(none detected)"))}`
2949
+ );
2950
+ if (resolvedTargets.incompatible.length > 0) {
2951
+ const skippedList = resolvedTargets.incompatible.map((item) => `${item.agent} (${item.reason})`).join(", ");
2952
+ logger.info(
2953
+ `Skipping detected agents incompatible with ${transport}: ${import_picocolors12.default.yellow(skippedList)}`
2954
+ );
2955
+ }
2956
+ }
2957
+ const targetAgents = resolvedTargets.isDetected ? resolvedTargets.agents : resolvedTargets.allAgents;
2958
+ const result = installMcpServer({
2959
+ source,
2960
+ name: options.name,
2961
+ agents: targetAgents,
2962
+ args: options.args,
2963
+ global: isGlobal,
2964
+ cwd,
2965
+ transport: explicitTransport,
2966
+ headers: parseKeyValueList(options.header, ":"),
2967
+ env: parseKeyValueList(options.env, "=")
2968
+ });
2969
+ logger.info(
2970
+ `Installing ${import_picocolors12.default.bold(result.serverName)} (${import_picocolors12.default.cyan(parsed.type)}) to ${import_picocolors12.default.cyan(String(result.results.length))} agent(s)`
2971
+ );
2972
+ for (const record of result.results) {
2973
+ if (record.success) {
2974
+ logger.success(`${import_picocolors12.default.cyan(record.agent)} ${import_picocolors12.default.dim(record.path)}`);
2975
+ logCoHostedNotice("configured", record.coConfiguredAgents);
2976
+ } else {
2977
+ logger.error(`${import_picocolors12.default.cyan(record.agent)}: ${record.error}`);
2978
+ }
2258
2979
  }
2980
+ if (result.results.some((record) => !record.success)) process.exitCode = 1;
2981
+ } catch (error) {
2982
+ logger.error(toErrorMessage(error));
2983
+ process.exitCode = 1;
2259
2984
  }
2260
- if (allSuccess) {
2261
- logger.success(import_picocolors8.default.bold(`MCP server "${serverName}" configured successfully!`));
2262
- }
2263
- return allSuccess;
2264
- };
2265
-
2266
- // src/interactive/wizard-manage.ts
2267
- var import_prompts9 = require("@inquirer/prompts");
2268
- var import_picocolors9 = __toESM(require("picocolors"), 1);
2985
+ });
2269
2986
 
2270
- // src/interactive/utils/group-installed-servers.ts
2271
- var normalizeServerConfig = parseServerConfig;
2272
- var groupInstalledServersByName = (installed) => {
2273
- const grouped = /* @__PURE__ */ new Map();
2274
- for (const item of installed) {
2275
- let entry = grouped.get(item.serverName);
2276
- if (!entry) {
2277
- entry = {
2278
- serverName: item.serverName,
2279
- agents: [],
2280
- paths: [],
2281
- config: normalizeServerConfig(item.config)
2282
- };
2283
- grouped.set(item.serverName, entry);
2987
+ // src/cli/list.ts
2988
+ var import_commander2 = require("commander");
2989
+ var import_picocolors13 = __toESM(require("picocolors"), 1);
2990
+ var mcpListCommand = new import_commander2.Command("list").alias("ls").description("List installed MCP servers across agents").option("-g, --global", "List global configs instead of project").option("-a, --agent <agents...>", "Filter by specific agents").option("--json", "Output as JSON").action((options) => {
2991
+ try {
2992
+ const entries = listInstalledMcpServers({
2993
+ global: Boolean(options.global),
2994
+ cwd: process.cwd(),
2995
+ agents: parseMcpAgentList(options.agent)
2996
+ });
2997
+ if (options.json) {
2998
+ console.log(JSON.stringify(entries, null, 2));
2999
+ return;
2284
3000
  }
2285
- if (!entry.agents.includes(item.agent)) {
2286
- entry.agents.push(item.agent);
3001
+ if (entries.length === 0) {
3002
+ logger.warn("No MCP servers installed");
3003
+ return;
2287
3004
  }
2288
- if (!entry.paths.includes(item.path)) {
2289
- entry.paths.push(item.path);
3005
+ const grouped = /* @__PURE__ */ new Map();
3006
+ for (const entry of entries) {
3007
+ const existing = grouped.get(entry.serverName) ?? [];
3008
+ existing.push(entry);
3009
+ grouped.set(entry.serverName, existing);
3010
+ }
3011
+ for (const [serverName, group] of grouped) {
3012
+ const agentLabels = group.map((record) => record.agent).join(", ");
3013
+ console.log(` ${import_picocolors13.default.bold(serverName)} ${import_picocolors13.default.dim(`[${agentLabels}]`)}`);
3014
+ const firstPath = group[0]?.path;
3015
+ if (firstPath) console.log(` ${import_picocolors13.default.dim(firstPath)}`);
2290
3016
  }
3017
+ } catch (error) {
3018
+ logger.error(toErrorMessage(error));
3019
+ process.exitCode = 1;
2291
3020
  }
2292
- return grouped;
2293
- };
3021
+ });
3022
+
3023
+ // src/cli/manage.ts
3024
+ var import_commander3 = require("commander");
3025
+ var import_picocolors16 = __toESM(require("picocolors"), 1);
2294
3026
 
2295
3027
  // src/interactive/wizard-manage.ts
3028
+ var import_prompts8 = require("@inquirer/prompts");
3029
+ var import_picocolors15 = __toESM(require("picocolors"), 1);
3030
+
3031
+ // src/utils/display-server-details.ts
3032
+ var import_picocolors14 = __toESM(require("picocolors"), 1);
2296
3033
  var displayServerDetails = ({
2297
3034
  serverName,
2298
3035
  config,
2299
3036
  agents,
2300
- isGlobal,
3037
+ hasDivergence,
3038
+ global: isGlobal,
2301
3039
  titlePrefix = "MCP Server Details"
2302
3040
  }) => {
2303
- console.log("\n" + import_picocolors9.default.cyan(import_picocolors9.default.bold(`${titlePrefix}: [${serverName}]`)));
3041
+ console.log("\n" + import_picocolors14.default.cyan(import_picocolors14.default.bold(`${titlePrefix}: [${serverName}]`)));
2304
3042
  if (isGlobal !== void 0) {
2305
- console.log(` ${import_picocolors9.default.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
3043
+ console.log(` ${import_picocolors14.default.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
2306
3044
  }
2307
3045
  if (agents && agents.length > 0) {
2308
3046
  console.log(
2309
- ` ${import_picocolors9.default.bold("Configured Agents:")} ${import_picocolors9.default.green(agents.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
3047
+ ` ${import_picocolors14.default.bold("Configured Agents:")} ${import_picocolors14.default.green(agents.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
3048
+ );
3049
+ }
3050
+ if (hasDivergence) {
3051
+ console.log(
3052
+ ` ${import_picocolors14.default.yellow(import_picocolors14.default.bold("Notice:"))} ${import_picocolors14.default.yellow("Configurations differ across installed agents. Showing configuration from the first agent.")}`
2310
3053
  );
2311
3054
  }
2312
3055
  const isRemote = Boolean(config.url && config.url.length > 0);
2313
3056
  if (isRemote) {
2314
- console.log(` ${import_picocolors9.default.bold("Transport:")} ${import_picocolors9.default.magenta(config.type ?? "http")}`);
2315
- console.log(` ${import_picocolors9.default.bold("URL:")} ${import_picocolors9.default.dim(config.url ?? "")}`);
3057
+ console.log(` ${import_picocolors14.default.bold("Transport:")} ${import_picocolors14.default.magenta(config.type ?? "http")}`);
3058
+ console.log(` ${import_picocolors14.default.bold("URL:")} ${import_picocolors14.default.dim(config.url ?? "")}`);
2316
3059
  const headerKeys = Object.keys(config.headers ?? {});
2317
3060
  if (headerKeys.length > 0) {
2318
- console.log(` ${import_picocolors9.default.bold("Headers:")} ${import_picocolors9.default.cyan(String(headerKeys.length))}`);
3061
+ console.log(` ${import_picocolors14.default.bold("Headers:")} ${import_picocolors14.default.cyan(String(headerKeys.length))}`);
2319
3062
  for (const [k, v] of Object.entries(config.headers ?? {})) {
2320
- console.log(` ${import_picocolors9.default.bold(k)}: ${import_picocolors9.default.dim(maskSecretHeader(k, v))}`);
3063
+ console.log(` ${import_picocolors14.default.bold(k)}: ${import_picocolors14.default.dim(maskSecretHeader(k, v))}`);
2321
3064
  }
2322
3065
  } else {
2323
- console.log(` ${import_picocolors9.default.bold("Headers:")} ${import_picocolors9.default.dim("(none)")}`);
3066
+ console.log(` ${import_picocolors14.default.bold("Headers:")} ${import_picocolors14.default.dim("(none)")}`);
2324
3067
  }
2325
3068
  } else {
2326
- console.log(` ${import_picocolors9.default.bold("Command:")} ${import_picocolors9.default.magenta(config.command ?? "")}`);
3069
+ console.log(` ${import_picocolors14.default.bold("Command:")} ${import_picocolors14.default.magenta(config.command ?? "")}`);
2327
3070
  const argsStr = config.args && config.args.length > 0 ? config.args.join(" ") : "(none)";
2328
- console.log(` ${import_picocolors9.default.bold("Arguments:")} ${import_picocolors9.default.dim(argsStr)}`);
3071
+ console.log(` ${import_picocolors14.default.bold("Arguments:")} ${import_picocolors14.default.dim(argsStr)}`);
2329
3072
  const envKeys = Object.keys(config.env ?? {});
2330
3073
  if (envKeys.length > 0) {
2331
- console.log(` ${import_picocolors9.default.bold("Environment Variables:")} ${import_picocolors9.default.cyan(String(envKeys.length))}`);
3074
+ console.log(` ${import_picocolors14.default.bold("Environment Variables:")} ${import_picocolors14.default.cyan(String(envKeys.length))}`);
2332
3075
  for (const [k, v] of Object.entries(config.env ?? {})) {
2333
- console.log(` ${import_picocolors9.default.bold(k)}=${import_picocolors9.default.dim(maskSecretValue(k, v))}`);
3076
+ console.log(` ${import_picocolors14.default.bold(k)}=${import_picocolors14.default.dim(maskSecretValue(k, v))}`);
2334
3077
  }
2335
3078
  } else {
2336
- console.log(` ${import_picocolors9.default.bold("Environment Variables:")} ${import_picocolors9.default.dim("(none)")}`);
3079
+ console.log(` ${import_picocolors14.default.bold("Environment Variables:")} ${import_picocolors14.default.dim("(none)")}`);
2337
3080
  }
2338
3081
  }
2339
3082
  console.log();
2340
3083
  };
2341
- var handleEditServerConfig = async ({
2342
- targetGroup,
2343
- isGlobal,
2344
- cwd
2345
- }) => {
3084
+
3085
+ // src/interactive/wizard-manage.ts
3086
+ var promptSwitchServerType = async (currentConfig, serverName) => {
3087
+ const isRemote = Boolean(currentConfig.url && currentConfig.url.length > 0);
3088
+ if (isRemote) {
3089
+ const newCmd = await (0, import_prompts8.input)({
3090
+ message: "Executable command (e.g. node, npx):",
3091
+ validate: (val) => val.trim() ? true : "Command cannot be empty"
3092
+ });
3093
+ const newArgs = await promptEditArgs([]);
3094
+ const newEnv = await promptEditEnvConfig({});
3095
+ logger.success(`Switched [${serverName}] configuration to stdio mode`);
3096
+ return toStdioServerConfig({
3097
+ command: newCmd.trim(),
3098
+ args: newArgs.length > 0 ? newArgs : void 0,
3099
+ env: Object.keys(newEnv).length > 0 ? newEnv : void 0
3100
+ });
3101
+ }
3102
+ const newUrl = await (0, import_prompts8.input)({
3103
+ message: "Remote server URL:",
3104
+ validate: (val) => {
3105
+ const trimmed = val.trim();
3106
+ if (!trimmed) return "URL cannot be empty";
3107
+ if (!/^https?:\/\//i.test(trimmed)) {
3108
+ return "Please enter a valid URL starting with http:// or https://";
3109
+ }
3110
+ return true;
3111
+ }
3112
+ });
3113
+ const transport = await (0, import_prompts8.select)({
3114
+ message: "Select remote transport protocol:",
3115
+ choices: [
3116
+ { name: "HTTP", value: "http" },
3117
+ { name: "SSE (Server-Sent Events)", value: "sse" }
3118
+ ],
3119
+ default: "http"
3120
+ });
3121
+ const newHeaders = await promptEditHeadersConfig({});
3122
+ logger.success(`Switched [${serverName}] configuration to remote mode`);
3123
+ return toRemoteServerConfig(
3124
+ {
3125
+ url: newUrl.trim(),
3126
+ headers: Object.keys(newHeaders).length > 0 ? newHeaders : void 0
3127
+ },
3128
+ transport
3129
+ );
3130
+ };
3131
+ var handleEditServerConfig = async (options) => {
3132
+ const { targetGroup } = options;
3133
+ const isGlobal = options.global ?? false;
3134
+ const cwd = options.cwd ?? process.cwd();
2346
3135
  const serverName = targetGroup.serverName;
2347
3136
  let workingConfig = {
2348
3137
  ...targetGroup.config,
@@ -2361,6 +3150,7 @@ var handleEditServerConfig = async ({
2361
3150
  { name: "Edit HTTP Headers (headers)", value: "headers" },
2362
3151
  { name: "Edit Remote URL (url)", value: "url" },
2363
3152
  { name: "Edit Transport Protocol (type)", value: "transport" },
3153
+ { name: "Switch to local command (stdio)", value: "switch_type" },
2364
3154
  { name: "Reset changes to original", value: "reset" },
2365
3155
  { name: "Save and apply changes", value: "save" },
2366
3156
  { name: "Cancel (discard changes)", value: "cancel" }
@@ -2368,11 +3158,12 @@ var handleEditServerConfig = async ({
2368
3158
  { name: "Edit Environment Variables (env)", value: "env" },
2369
3159
  { name: "Edit Command Arguments (args)", value: "args" },
2370
3160
  { name: "Edit Executable Command (command)", value: "command" },
3161
+ { name: "Switch to remote server (HTTP/SSE)", value: "switch_type" },
2371
3162
  { name: "Reset changes to original", value: "reset" },
2372
3163
  { name: "Save and apply changes", value: "save" },
2373
3164
  { name: "Cancel (discard changes)", value: "cancel" }
2374
3165
  ];
2375
- const editAction = await (0, import_prompts9.select)({
3166
+ const editAction = await (0, import_prompts8.select)({
2376
3167
  message: `What would you like to modify in [${serverName}]?`,
2377
3168
  choices: editChoices
2378
3169
  });
@@ -2390,12 +3181,16 @@ var handleEditServerConfig = async ({
2390
3181
  logger.info("Configuration reset to original");
2391
3182
  continue;
2392
3183
  }
3184
+ if (editAction === "switch_type") {
3185
+ workingConfig = await promptSwitchServerType(workingConfig, serverName);
3186
+ continue;
3187
+ }
2393
3188
  if (editAction === "env") {
2394
3189
  workingConfig.env = await promptEditEnvConfig(workingConfig.env ?? {});
2395
3190
  } else if (editAction === "args") {
2396
3191
  workingConfig.args = await promptEditArgs(workingConfig.args ?? []);
2397
3192
  } else if (editAction === "command") {
2398
- const newCmd = await (0, import_prompts9.input)({
3193
+ const newCmd = await (0, import_prompts8.input)({
2399
3194
  message: "Executable command:",
2400
3195
  default: workingConfig.command,
2401
3196
  validate: (val) => val.trim() ? true : "Command cannot be empty"
@@ -2404,7 +3199,7 @@ var handleEditServerConfig = async ({
2404
3199
  } else if (editAction === "headers") {
2405
3200
  workingConfig.headers = await promptEditHeadersConfig(workingConfig.headers ?? {});
2406
3201
  } else if (editAction === "url") {
2407
- const newUrl = await (0, import_prompts9.input)({
3202
+ const newUrl = await (0, import_prompts8.input)({
2408
3203
  message: "Remote server URL:",
2409
3204
  default: workingConfig.url,
2410
3205
  validate: (val) => {
@@ -2418,7 +3213,7 @@ var handleEditServerConfig = async ({
2418
3213
  });
2419
3214
  workingConfig.url = newUrl.trim();
2420
3215
  } else if (editAction === "transport") {
2421
- workingConfig.type = await (0, import_prompts9.select)({
3216
+ workingConfig.type = await (0, import_prompts8.select)({
2422
3217
  message: "Select remote transport protocol:",
2423
3218
  choices: [
2424
3219
  { name: "HTTP", value: "http" },
@@ -2429,42 +3224,46 @@ var handleEditServerConfig = async ({
2429
3224
  } else if (editAction === "save") {
2430
3225
  let targetAgents = targetGroup.agents;
2431
3226
  if (targetGroup.agents.length > 1) {
2432
- targetAgents = await (0, import_prompts9.checkbox)({
3227
+ const sortedAgents = agentConfigStore.sortAgentsByClusters(targetGroup.agents, { global: isGlobal, cwd });
3228
+ const choices = buildLinkedAgentChoices({
3229
+ agents: sortedAgents,
3230
+ checkedAgents: sortedAgents,
3231
+ scopeOptions: { global: isGlobal, cwd }
3232
+ });
3233
+ targetAgents = await linkedCheckbox({
2433
3234
  message: "Select agents to update configuration (Space to toggle):",
2434
- choices: targetGroup.agents.map((a) => ({
2435
- name: `${getMcpAgentConfig(a).displayName} (${a})`,
2436
- value: a,
2437
- checked: true
2438
- })),
3235
+ choices,
2439
3236
  loop: false,
2440
3237
  validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
2441
3238
  });
2442
- }
2443
- const requestedTransport = workingConfig.url ? workingConfig.type ?? "http" : "stdio";
2444
- const compatibleAgents = [];
2445
- const incompatibleAgents = [];
2446
- for (const agent of targetAgents) {
2447
- const agentConfig = getMcpAgentConfig(agent);
2448
- if (isMcpTransportSupported(agentConfig, requestedTransport)) {
2449
- compatibleAgents.push(agent);
2450
- } else {
2451
- const reason = agentConfig.unsupportedTransportMessage ?? `Agent does not support ${requestedTransport} transport`;
2452
- incompatibleAgents.push({ agent, reason });
3239
+ if (targetAgents.length < targetGroup.agents.length) {
3240
+ const unselected = targetGroup.agents.filter((a) => !targetAgents.includes(a));
3241
+ const unselectedNames = unselected.map((a) => getMcpAgentConfig(a).displayName).join(", ");
3242
+ logger.info(
3243
+ `Note: Updating only a subset of agents. Server configurations will diverge from: ${unselectedNames}.`
3244
+ );
2453
3245
  }
2454
3246
  }
2455
- if (incompatibleAgents.length > 0) {
2456
- for (const item of incompatibleAgents) {
2457
- logger.warn(`Skipping ${import_picocolors9.default.cyan(item.agent)}: ${item.reason}`);
3247
+ const requestedTransport = workingConfig.url ? workingConfig.type ?? "http" : "stdio";
3248
+ const resolution = resolveTargetAgents({
3249
+ requested: targetAgents,
3250
+ global: isGlobal,
3251
+ cwd,
3252
+ transport: requestedTransport
3253
+ });
3254
+ if (resolution.incompatible.length > 0) {
3255
+ for (const item of resolution.incompatible) {
3256
+ logger.warn(`Skipping ${import_picocolors15.default.cyan(item.agent)}: ${item.reason}`);
2458
3257
  }
2459
3258
  }
2460
- if (compatibleAgents.length === 0) {
3259
+ if (resolution.compatibleAgents.length === 0) {
2461
3260
  logger.error(
2462
3261
  `None of the selected agents support ${requestedTransport} transport. Cannot update.`
2463
3262
  );
2464
3263
  continue;
2465
3264
  }
2466
- const agentNames = compatibleAgents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
2467
- const confirmed = await (0, import_prompts9.confirm)({
3265
+ const agentNames = resolution.compatibleAgents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
3266
+ const confirmed = await (0, import_prompts8.confirm)({
2468
3267
  message: `Confirm updating configuration for [${serverName}] across: ${agentNames}?`,
2469
3268
  default: true
2470
3269
  });
@@ -2472,22 +3271,32 @@ var handleEditServerConfig = async ({
2472
3271
  logger.warn("Update cancelled");
2473
3272
  continue;
2474
3273
  }
2475
- for (const targetAgent of compatibleAgents) {
2476
- const res = installMcpServerForAgent(serverName, workingConfig, targetAgent, {
2477
- global: isGlobal,
2478
- cwd
2479
- });
3274
+ const updateResult = updateMcpServer({
3275
+ serverName,
3276
+ config: workingConfig,
3277
+ previousConfig: targetGroup.config,
3278
+ agents: resolution.compatibleAgents,
3279
+ global: isGlobal,
3280
+ cwd
3281
+ });
3282
+ let updatedAny = false;
3283
+ const succeededAgents = [];
3284
+ for (const res of updateResult.results) {
2480
3285
  if (res.success) {
3286
+ updatedAny = true;
3287
+ succeededAgents.push(res.agent);
2481
3288
  logger.success(
2482
- `${import_picocolors9.default.cyan(targetAgent)}: Successfully updated configuration in ${import_picocolors9.default.dim(res.path)}`
3289
+ `${import_picocolors15.default.cyan(res.agent)}: Successfully updated configuration in ${import_picocolors15.default.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
2483
3290
  );
2484
3291
  } else {
2485
- logger.error(`${import_picocolors9.default.cyan(targetAgent)}: Update failed - ${res.error}`);
3292
+ logger.error(`${import_picocolors15.default.cyan(res.agent)}: Update failed - ${res.error}`);
2486
3293
  }
2487
3294
  }
2488
- targetGroup.config = workingConfig;
2489
- logger.success(`Configuration for [${serverName}] updated successfully!`);
2490
- return;
3295
+ if (updatedAny) {
3296
+ targetGroup.config = updateResult.config;
3297
+ logger.success(`Configuration for [${serverName}] updated successfully!`);
3298
+ return;
3299
+ }
2491
3300
  }
2492
3301
  }
2493
3302
  };
@@ -2498,13 +3307,19 @@ var wizardManage = async (options = {}) => {
2498
3307
  defaultGlobal: options.global,
2499
3308
  message: "Select MCP scope to inspect and manage:"
2500
3309
  });
2501
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2502
- if (installed.length === 0) {
3310
+ const grouped = queryGroupedInstalledServers({ global: isGlobal, cwd });
3311
+ if (grouped.size === 0) {
2503
3312
  logger.warn(`No configured MCP servers found in ${isGlobal ? "global" : "project"} scope`);
2504
3313
  return;
2505
3314
  }
2506
- const grouped = groupInstalledServersByName(installed);
2507
3315
  let pendingServerName = options.serverName;
3316
+ const refreshGroupedServers = () => {
3317
+ const freshGrouped = queryGroupedInstalledServers({ global: isGlobal, cwd });
3318
+ grouped.clear();
3319
+ for (const [name, grp] of freshGrouped) {
3320
+ grouped.set(name, grp);
3321
+ }
3322
+ };
2508
3323
  while (true) {
2509
3324
  let chosenServerName;
2510
3325
  if (pendingServerName && grouped.has(pendingServerName)) {
@@ -2515,7 +3330,7 @@ var wizardManage = async (options = {}) => {
2515
3330
  const choices = Array.from(grouped.values()).map((g) => {
2516
3331
  const agentNames = g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
2517
3332
  return {
2518
- name: `${import_picocolors9.default.bold(g.serverName)} ${import_picocolors9.default.dim(`(configured in: ${agentNames})`)}`,
3333
+ name: `${import_picocolors15.default.bold(g.serverName)} ${import_picocolors15.default.dim(`(configured in: ${agentNames})`)}`,
2519
3334
  value: g.serverName
2520
3335
  };
2521
3336
  });
@@ -2523,7 +3338,7 @@ var wizardManage = async (options = {}) => {
2523
3338
  name: `Back`,
2524
3339
  value: "__back__"
2525
3340
  });
2526
- chosenServerName = await (0, import_prompts9.select)({
3341
+ chosenServerName = await (0, import_prompts8.select)({
2527
3342
  message: "Select MCP server to manage or sync:",
2528
3343
  choices
2529
3344
  });
@@ -2537,9 +3352,10 @@ var wizardManage = async (options = {}) => {
2537
3352
  serverName: chosenServerName,
2538
3353
  config: targetGroup.config,
2539
3354
  agents: targetGroup.agents,
2540
- isGlobal
3355
+ global: isGlobal,
3356
+ hasDivergence: targetGroup.hasDivergence
2541
3357
  });
2542
- const action = await (0, import_prompts9.select)({
3358
+ const action = await (0, import_prompts8.select)({
2543
3359
  message: `What would you like to do with [${chosenServerName}]?`,
2544
3360
  choices: [
2545
3361
  {
@@ -2560,31 +3376,34 @@ var wizardManage = async (options = {}) => {
2560
3376
  if (action === "edit") {
2561
3377
  await handleEditServerConfig({
2562
3378
  targetGroup,
2563
- isGlobal,
3379
+ global: isGlobal,
2564
3380
  cwd
2565
3381
  });
3382
+ refreshGroupedServers();
2566
3383
  continue;
2567
3384
  }
2568
3385
  if (action === "sync") {
2569
3386
  const allAllowedAgents = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
2570
- const candidateAgents = allAllowedAgents.filter((a) => !targetGroup.agents.includes(a));
2571
- if (candidateAgents.length === 0) {
3387
+ const rawCandidateAgents = allAllowedAgents.filter((a) => !targetGroup.agents.includes(a));
3388
+ if (rawCandidateAgents.length === 0) {
2572
3389
  logger.info(
2573
3390
  "All supported agents in this scope already have this MCP server configured; no sync needed"
2574
3391
  );
2575
3392
  continue;
2576
3393
  }
2577
- const selectedToSync = await (0, import_prompts9.checkbox)({
3394
+ const candidateAgents = agentConfigStore.sortAgentsByClusters(rawCandidateAgents, { global: isGlobal, cwd });
3395
+ const choices = buildLinkedAgentChoices({
3396
+ agents: candidateAgents,
3397
+ checkedAgents: [],
3398
+ scopeOptions: { global: isGlobal, cwd }
3399
+ });
3400
+ const selectedToSync = await linkedCheckbox({
2578
3401
  message: "Select target agents to sync to (Space to select):",
2579
- choices: candidateAgents.map((a) => ({
2580
- name: `${getMcpAgentConfig(a).displayName} (${a})`,
2581
- value: a,
2582
- checked: false
2583
- })),
3402
+ choices,
2584
3403
  loop: false,
2585
3404
  validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
2586
3405
  });
2587
- const confirmed = await (0, import_prompts9.confirm)({
3406
+ const confirmed = await (0, import_prompts8.confirm)({
2588
3407
  message: `Confirm syncing configuration of [${chosenServerName}] to: ${selectedToSync.join(", ")}?`,
2589
3408
  default: true
2590
3409
  });
@@ -2592,188 +3411,52 @@ var wizardManage = async (options = {}) => {
2592
3411
  logger.warn("Sync cancelled");
2593
3412
  continue;
2594
3413
  }
2595
- for (const targetAgent of selectedToSync) {
2596
- const res = installMcpServerForAgent(chosenServerName, targetGroup.config, targetAgent, {
2597
- global: isGlobal,
2598
- cwd
2599
- });
3414
+ const syncResult = updateMcpServer({
3415
+ serverName: chosenServerName,
3416
+ config: targetGroup.config,
3417
+ agents: selectedToSync,
3418
+ global: isGlobal,
3419
+ cwd
3420
+ });
3421
+ for (const item of syncResult.incompatible) {
3422
+ logger.warn(`Skipping ${import_picocolors15.default.cyan(item.agent)}: ${item.reason}`);
3423
+ }
3424
+ for (const res of syncResult.results) {
3425
+ if (syncResult.incompatible.some((i) => i.agent === res.agent)) {
3426
+ continue;
3427
+ }
2600
3428
  if (res.success) {
2601
- logger.success(`${import_picocolors9.default.cyan(targetAgent)}: Successfully synced to ${import_picocolors9.default.dim(res.path)}`);
2602
- targetGroup.agents.push(targetAgent);
3429
+ logger.success(
3430
+ `${import_picocolors15.default.cyan(res.agent)}: Successfully synced to ${import_picocolors15.default.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
3431
+ );
3432
+ targetGroup.agents.push(res.agent);
2603
3433
  } else {
2604
- logger.error(`${import_picocolors9.default.cyan(targetAgent)}: Sync failed - ${res.error}`);
3434
+ logger.error(`${import_picocolors15.default.cyan(res.agent)}: Sync failed - ${res.error}`);
2605
3435
  }
2606
- }
2607
- }
2608
- }
2609
- };
2610
-
2611
- // src/interactive/wizard-remove.ts
2612
- var import_prompts10 = require("@inquirer/prompts");
2613
- var import_picocolors10 = __toESM(require("picocolors"), 1);
2614
- var wizardRemove = async (options = {}) => {
2615
- const cwd = options.cwd ?? process.cwd();
2616
- const isGlobal = await promptScope({
2617
- cwd,
2618
- defaultGlobal: options.global,
2619
- message: "Select scope to remove MCP server from:"
2620
- });
2621
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2622
- if (installed.length === 0) {
2623
- logger.warn(`No installed MCP servers found in ${isGlobal ? "global" : "project"} scope`);
2624
- return false;
2625
- }
2626
- const serverMap = groupInstalledServersByName(installed);
2627
- let serverName = options.name;
2628
- if (!serverName) {
2629
- const choices = Array.from(serverMap.values()).map((g) => ({
2630
- name: `${import_picocolors10.default.bold(g.serverName)} ${import_picocolors10.default.dim(`(installed in: ${g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ")})`)}`,
2631
- value: g.serverName
2632
- }));
2633
- serverName = await (0, import_prompts10.select)({
2634
- message: "Select MCP server to remove:",
2635
- choices
2636
- });
2637
- }
2638
- const installedAgents = serverMap.get(serverName)?.agents || [];
2639
- if (installedAgents.length === 0) {
2640
- logger.warn(`No agents found with [${serverName}] installed`);
2641
- return false;
2642
- }
2643
- let targetAgents = options.agents;
2644
- if (!targetAgents || targetAgents.length === 0) {
2645
- targetAgents = await (0, import_prompts10.checkbox)({
2646
- message: `Select agents to remove [${serverName}] from:`,
2647
- choices: installedAgents.map((agent) => ({
2648
- name: `${getMcpAgentConfig(agent)?.displayName ?? agent} (${agent})`,
2649
- value: agent,
2650
- checked: true
2651
- })),
2652
- loop: false,
2653
- validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
2654
- });
2655
- } else {
2656
- const validAgents = targetAgents.filter((agent) => installedAgents.includes(agent));
2657
- if (validAgents.length === 0) {
2658
- logger.warn(`None of the specified agents (${targetAgents.join(", ")}) have [${serverName}] installed`);
2659
- return false;
2660
- }
2661
- targetAgents = validAgents;
2662
- }
2663
- const confirmed = await (0, import_prompts10.confirm)({
2664
- message: `Confirm removing MCP server [${serverName}] from ${targetAgents.join(", ")}?`,
2665
- default: true
2666
- });
2667
- if (!confirmed) {
2668
- logger.warn("Operation cancelled");
2669
- return false;
2670
- }
2671
- const results = removeMcpServer({
2672
- name: serverName,
2673
- agents: targetAgents,
2674
- global: isGlobal,
2675
- cwd
2676
- });
2677
- let removedCount = 0;
2678
- for (const res of results) {
2679
- if (res.removed) {
2680
- logger.success(`${import_picocolors10.default.cyan(res.agent)}: Successfully removed from ${import_picocolors10.default.dim(res.path)}`);
2681
- removedCount++;
2682
- } else if (res.error) {
2683
- logger.error(`${import_picocolors10.default.cyan(res.agent)}: Failed to remove - ${res.error}`);
2684
- }
2685
- }
2686
- if (removedCount > 0) {
2687
- logger.success(`Successfully removed [${serverName}] from ${removedCount} agent(s)`);
2688
- return true;
2689
- }
2690
- logger.warn(`Failed to remove [${serverName}] from specified agents`);
2691
- return false;
2692
- };
2693
-
2694
- // src/interactive/main-menu.ts
2695
- var mainMenu = async () => {
2696
- console.log();
2697
- console.log(import_picocolors11.default.bold(import_picocolors11.default.cyan("mcps - Cross-Platform MCP Manager for AI Coding Agents")));
2698
- console.log(import_picocolors11.default.dim("Cross-platform MCP server configuration & synchronization tool"));
2699
- console.log();
2700
- while (true) {
2701
- try {
2702
- const action = await (0, import_prompts11.select)({
2703
- message: "Select an action:",
2704
- choices: [
2705
- {
2706
- name: "Add MCP Server",
2707
- value: "add"
2708
- },
2709
- {
2710
- name: "Manage & Sync Installed MCP Servers",
2711
- value: "manage"
2712
- },
2713
- {
2714
- name: "Remove MCP Server",
2715
- value: "remove"
2716
- },
2717
- {
2718
- name: "Exit",
2719
- value: "exit"
2720
- }
2721
- ]
2722
- });
2723
- if (action === "exit") {
2724
- console.log(import_picocolors11.default.dim("Goodbye!"));
2725
- break;
2726
- }
2727
- if (action === "add") {
2728
- await wizardAdd();
2729
- } else if (action === "manage") {
2730
- await wizardManage();
2731
- } else if (action === "remove") {
2732
- await wizardRemove();
2733
- }
2734
- console.log();
2735
- } catch (error) {
2736
- if (error?.name === "ExitPromptError") {
2737
- console.log("\n" + import_picocolors11.default.dim("Exited."));
2738
- break;
2739
- }
2740
- throw error;
2741
- }
2742
- }
2743
- };
2744
-
2745
- // src/cli/manage.ts
2746
- var import_commander = require("commander");
2747
- var import_picocolors12 = __toESM(require("picocolors"), 1);
2748
-
2749
- // src/utils/parse-key-value-list.ts
2750
- var parseKeyValueList = (entries, separator) => {
2751
- if (!entries || entries.length === 0) return {};
2752
- const result = {};
2753
- for (const entry of entries) {
2754
- const splitIndex = entry.indexOf(separator);
2755
- if (splitIndex === -1) {
2756
- throw new Error(`Invalid entry "${entry}": expected "${separator}" separator`);
3436
+ }
3437
+ refreshGroupedServers();
2757
3438
  }
2758
- const key = entry.slice(0, splitIndex).trim();
2759
- const value = entry.slice(splitIndex + separator.length).trim();
2760
- if (!key) throw new Error(`Invalid entry "${entry}": empty key`);
2761
- result[key] = value;
2762
3439
  }
2763
- return result;
2764
3440
  };
2765
3441
 
2766
3442
  // src/cli/manage.ts
2767
- var resolveTransport = (input7) => {
2768
- if (!input7) return void 0;
2769
- if (input7 === "http" || input7 === "sse") return input7;
2770
- throw new Error(`Unsupported transport "${input7}" (expected: http, sse)`);
3443
+ var requireTargetServerGroup = (serverName, scope) => {
3444
+ const grouped = queryGroupedInstalledServers(scope);
3445
+ const targetGroup = grouped.get(serverName);
3446
+ if (!targetGroup) {
3447
+ logger.error(
3448
+ `MCP server "${serverName}" is not configured in ${scope.global ? "global" : "project"} scope.`
3449
+ );
3450
+ process.exitCode = 1;
3451
+ return void 0;
3452
+ }
3453
+ return targetGroup;
2771
3454
  };
2772
- var mcpManageCommand = new import_commander.Command("manage").description("Inspect, modify, and sync installed MCP servers across coding agents").argument("[server-name]", "Optional server name to inspect or manage").option("-a, --agent <agents...>", "Target specific agents for update").option("-g, --global", "Manage global scope servers instead of project").option("-t, --transport <type>", "Transport type for remote servers (http or sse)").option("--header <header...>", "HTTP header (Header: Value), repeatable").option("--env <env...>", "Env var for stdio servers (KEY=VALUE), repeatable").option("--args <args...>", "CLI arguments for stdio/package servers").option("--command <command>", "Executable command for stdio servers").option("--url <url>", "Remote endpoint URL").option("-y, --yes", "Skip interactive prompts").action(async (serverName, options) => {
3455
+ var mcpManageCommand = new import_commander3.Command("manage").description("Inspect, modify, and sync installed MCP servers across coding agents").argument("[server-name]", "Optional server name to inspect or manage").option("-a, --agent <agents...>", "Target specific agents for update").option("-g, --global", "Manage global scope servers instead of project").option("-t, --transport <type>", "Transport type for remote servers (http or sse)").option("--header <header...>", "HTTP header (Header: Value), repeatable").option("--clear-headers", "Clear all HTTP headers for remote servers").option("--env <env...>", "Env var for stdio servers (KEY=VALUE), repeatable").option("--clear-env", "Clear all environment variables for stdio servers").option("--args <args...>", "CLI arguments for stdio/package servers").option("--clear-args", "Clear all arguments for stdio/package servers").option("--command <command>", "Executable command for stdio servers").option("--url <url>", "Remote endpoint URL").option("-y, --yes", "Skip interactive prompts").action(async (serverName, options) => {
2773
3456
  try {
2774
3457
  const cwd = process.cwd();
2775
3458
  const isGlobal = Boolean(options.global);
2776
- const hasModifications = options.command !== void 0 || options.args !== void 0 || options.env !== void 0 || options.header !== void 0 || options.url !== void 0 || options.transport !== void 0;
3459
+ const hasModifications = options.command !== void 0 || options.args !== void 0 || Boolean(options.clearArgs) || options.env !== void 0 || Boolean(options.clearEnv) || options.header !== void 0 || Boolean(options.clearHeaders) || options.url !== void 0 || options.transport !== void 0;
2777
3460
  const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
2778
3461
  if (hasModifications) {
2779
3462
  if (!serverName) {
@@ -2781,55 +3464,64 @@ var mcpManageCommand = new import_commander.Command("manage").description("Inspe
2781
3464
  process.exitCode = 1;
2782
3465
  return;
2783
3466
  }
2784
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2785
- const grouped = groupInstalledServersByName(installed);
2786
- const targetGroup = grouped.get(serverName);
3467
+ const targetGroup = requireTargetServerGroup(serverName, { global: isGlobal, cwd });
2787
3468
  if (!targetGroup) {
2788
- logger.error(
2789
- `MCP server "${serverName}" is not configured in ${isGlobal ? "global" : "project"} scope.`
2790
- );
2791
- process.exitCode = 1;
2792
3469
  return;
2793
3470
  }
2794
- const updatedConfig = {
2795
- ...targetGroup.config,
2796
- args: targetGroup.config.args ? [...targetGroup.config.args] : void 0,
2797
- env: targetGroup.config.env ? { ...targetGroup.config.env } : void 0,
2798
- headers: targetGroup.config.headers ? { ...targetGroup.config.headers } : void 0
2799
- };
2800
- if (options.command !== void 0) {
2801
- updatedConfig.command = options.command;
2802
- }
2803
- if (options.args !== void 0) {
2804
- updatedConfig.args = options.args;
2805
- }
2806
- if (options.url !== void 0) {
2807
- updatedConfig.url = options.url;
2808
- }
2809
- if (options.transport !== void 0) {
2810
- updatedConfig.type = resolveTransport(options.transport);
2811
- }
2812
- if (options.env !== void 0) {
2813
- const parsedEnv = parseKeyValueList(options.env, "=");
2814
- updatedConfig.env = { ...updatedConfig.env ?? {}, ...parsedEnv };
3471
+ const parsedEnv = options.env !== void 0 ? parseKeyValueList(options.env, "=") : void 0;
3472
+ const parsedHeaders = options.header !== void 0 ? parseKeyValueList(options.header, ":") : void 0;
3473
+ let deltaResult;
3474
+ try {
3475
+ deltaResult = applyServerConfigDelta(targetGroup.config, {
3476
+ command: options.command,
3477
+ args: options.args,
3478
+ clearArgs: options.clearArgs,
3479
+ env: parsedEnv,
3480
+ clearEnv: options.clearEnv,
3481
+ url: options.url,
3482
+ transport: resolveTransport(options.transport),
3483
+ headers: parsedHeaders,
3484
+ clearHeaders: options.clearHeaders
3485
+ });
3486
+ } catch (error) {
3487
+ logger.error(toErrorMessage(error));
3488
+ process.exitCode = 1;
3489
+ return;
2815
3490
  }
2816
- if (options.header !== void 0) {
2817
- const parsedHeaders = parseKeyValueList(options.header, ":");
2818
- updatedConfig.headers = { ...updatedConfig.headers ?? {}, ...parsedHeaders };
3491
+ if (deltaResult.ignoredFlags.length > 0) {
3492
+ const isRemote = deltaResult.protocol === "remote";
3493
+ const hint = isRemote ? options.url !== void 0 ? "When configuring a remote server, stdio flags are ignored." : "Use --command to switch to stdio mode." : options.command !== void 0 ? "When configuring a stdio server, remote flags are ignored." : "Use --url to switch to remote mode.";
3494
+ logger.warn(
3495
+ `Server "${serverName}" is a ${isRemote ? "remote" : "stdio"} server. The following ${isRemote ? "stdio" : "remote"} flags will be ignored: ${deltaResult.ignoredFlags.join(", ")}. ${hint}`
3496
+ );
2819
3497
  }
2820
- const targetAgents = options.agent ? parseMcpAgentList(options.agent) ?? targetGroup.agents : targetGroup.agents;
2821
- const requestedTransport = updatedConfig.url ? updatedConfig.type ?? "http" : "stdio";
2822
- const compatibleAgents = [];
2823
- for (const agent of targetAgents) {
2824
- const agentConfig = getMcpAgentConfig(agent);
2825
- if (isMcpTransportSupported(agentConfig, requestedTransport)) {
2826
- compatibleAgents.push(agent);
2827
- } else {
2828
- const reason = agentConfig.unsupportedTransportMessage ?? `Agent does not support ${requestedTransport} transport`;
2829
- logger.warn(`Skipping ${import_picocolors12.default.cyan(agent)}: ${reason}`);
3498
+ const incomingDelta = deltaResult.config;
3499
+ let targetAgents = targetGroup.agents;
3500
+ if (options.agent !== void 0) {
3501
+ const parsed = parseMcpAgentList(options.agent);
3502
+ if (!parsed || parsed.length === 0) {
3503
+ logger.error(`No valid agents recognized from: "${options.agent.join(", ")}".`);
3504
+ process.exitCode = 1;
3505
+ return;
2830
3506
  }
3507
+ targetAgents = parsed;
3508
+ }
3509
+ const updateResult = updateMcpServer({
3510
+ serverName,
3511
+ config: incomingDelta,
3512
+ previousConfig: targetGroup.config,
3513
+ agents: targetAgents,
3514
+ global: isGlobal,
3515
+ cwd
3516
+ });
3517
+ for (const item of updateResult.incompatible) {
3518
+ logger.warn(`Skipping ${import_picocolors16.default.cyan(item.agent)}: ${item.reason}`);
2831
3519
  }
2832
- if (compatibleAgents.length === 0) {
3520
+ const attemptedResults = updateResult.results.filter(
3521
+ (r) => !updateResult.incompatible.some((i) => i.agent === r.agent)
3522
+ );
3523
+ if (attemptedResults.length === 0) {
3524
+ const requestedTransport = updateResult.config.url ? updateResult.config.type ?? "http" : "stdio";
2833
3525
  logger.error(
2834
3526
  `None of the target agents support ${requestedTransport} transport. Update aborted.`
2835
3527
  );
@@ -2837,19 +3529,16 @@ var mcpManageCommand = new import_commander.Command("manage").description("Inspe
2837
3529
  return;
2838
3530
  }
2839
3531
  logger.info(
2840
- `Updating ${import_picocolors12.default.bold(serverName)} across ${import_picocolors12.default.cyan(String(compatibleAgents.length))} agent(s)...`
3532
+ `Updating ${import_picocolors16.default.bold(serverName)} across ${import_picocolors16.default.cyan(String(attemptedResults.length))} agent(s)...`
2841
3533
  );
2842
3534
  let allSuccess = true;
2843
- for (const agent of compatibleAgents) {
2844
- const res = installMcpServerForAgent(serverName, updatedConfig, agent, {
2845
- global: isGlobal,
2846
- cwd
2847
- });
3535
+ for (const res of attemptedResults) {
2848
3536
  if (res.success) {
2849
- logger.success(`${import_picocolors12.default.cyan(agent)}: Successfully updated in ${import_picocolors12.default.dim(res.path)}`);
3537
+ logger.success(`${import_picocolors16.default.cyan(res.agent)}: Successfully updated in ${import_picocolors16.default.dim(res.path)}`);
3538
+ logCoHostedNotice("configured", res.coConfiguredAgents);
2850
3539
  } else {
2851
3540
  allSuccess = false;
2852
- logger.error(`${import_picocolors12.default.cyan(agent)}: Update failed - ${res.error}`);
3541
+ logger.error(`${import_picocolors16.default.cyan(res.agent)}: Update failed - ${res.error}`);
2853
3542
  }
2854
3543
  }
2855
3544
  if (!allSuccess) {
@@ -2865,21 +3554,16 @@ var mcpManageCommand = new import_commander.Command("manage").description("Inspe
2865
3554
  process.exitCode = 1;
2866
3555
  return;
2867
3556
  }
2868
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2869
- const grouped = groupInstalledServersByName(installed);
2870
- const targetGroup = grouped.get(serverName);
3557
+ const targetGroup = requireTargetServerGroup(serverName, { global: isGlobal, cwd });
2871
3558
  if (!targetGroup) {
2872
- logger.error(
2873
- `MCP server "${serverName}" is not configured in ${isGlobal ? "global" : "project"} scope.`
2874
- );
2875
- process.exitCode = 1;
2876
3559
  return;
2877
3560
  }
2878
3561
  displayServerDetails({
2879
3562
  serverName,
2880
3563
  config: targetGroup.config,
2881
3564
  agents: targetGroup.agents,
2882
- isGlobal
3565
+ global: isGlobal,
3566
+ hasDivergence: targetGroup.hasDivergence
2883
3567
  });
2884
3568
  return;
2885
3569
  }
@@ -2896,133 +3580,96 @@ var mcpManageCommand = new import_commander.Command("manage").description("Inspe
2896
3580
  }
2897
3581
  });
2898
3582
 
2899
- // src/utils/format-agent-list.ts
2900
- var formatAgentList = (agentList, emptyLabel = "(none)") => agentList.length === 0 ? emptyLabel : agentList.join(", ");
3583
+ // src/cli/remove.ts
3584
+ var import_commander4 = require("commander");
3585
+ var import_picocolors18 = __toESM(require("picocolors"), 1);
2901
3586
 
2902
- // src/cli/add.ts
2903
- var resolveTransport2 = (input7) => {
2904
- if (!input7) return void 0;
2905
- if (input7 === "http" || input7 === "sse") return input7;
2906
- throw new Error(`Unsupported transport "${input7}" (expected: http, sse)`);
2907
- };
2908
- var mcpAddCommand = new import_commander2.Command("add").description("Add an MCP server to coding agents").argument("[source]", "Remote URL, npm package, or command line").option("-a, --agent <agents...>", "Target specific agents (use '*' for all)").option("-g, --global", "Install to user-level config instead of project").option("-t, --transport <type>", "Transport type for remote servers (http or sse)").option("--header <header...>", "HTTP header (Key: Value), repeatable").option("--env <env...>", "Env var for stdio servers (KEY=VALUE), repeatable").option("--args <args...>", "CLI arguments for stdio/package servers").option("-n, --name <name>", "Server name override").option("-y, --yes", "Skip all prompts").option("--all", "Install to all supported agents").action(async (source, options) => {
2909
- try {
2910
- const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
2911
- if (!source) {
2912
- if (isInteractive) {
2913
- const success = await wizardAdd({
2914
- name: options.name,
2915
- global: options.global,
2916
- args: options.args,
2917
- transport: resolveTransport2(options.transport),
2918
- headers: parseKeyValueList(options.header, ":"),
2919
- env: parseKeyValueList(options.env, "="),
2920
- agents: options.all ? getMcpAgentTypes() : parseMcpAgentList(options.agent)
2921
- });
2922
- if (!success) process.exitCode = 1;
2923
- return;
2924
- }
2925
- logger.error('Missing required argument: "source" (e.g. mcps add @modelcontextprotocol/server-filesystem)');
2926
- process.exitCode = 1;
2927
- return;
2928
- }
2929
- const parsed = parseMcpSource(source);
2930
- const cwd = process.cwd();
2931
- const isGlobal = Boolean(options.global);
2932
- const explicitTransport = resolveTransport2(options.transport);
2933
- const transport = explicitTransport ?? (parsed.type === "remote" ? "http" : "stdio");
2934
- const resolvedTargets = resolveTargetAgents({
2935
- requested: options.agent,
2936
- all: options.all,
2937
- global: isGlobal,
2938
- cwd,
2939
- transport
2940
- });
2941
- if (resolvedTargets.agents.length === 0) {
2942
- const message = resolvedTargets.diagnostic ?? `No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass ${import_picocolors13.default.cyan("-a <agent>")} (e.g. ${import_picocolors13.default.cyan("-a cursor")}) or ${import_picocolors13.default.cyan("--all")} to install.`;
2943
- logger.warn(message);
2944
- process.exitCode = 1;
2945
- return;
2946
- }
2947
- if (resolvedTargets.isDetected) {
2948
- logger.info(
2949
- `Detected ${isGlobal ? "global" : "project"} agents: ${import_picocolors13.default.cyan(formatAgentList(resolvedTargets.detected, "(none detected)"))}`
2950
- );
2951
- if (resolvedTargets.incompatible.length > 0) {
2952
- const skippedList = resolvedTargets.incompatible.map((item) => `${item.agent} (${item.reason})`).join(", ");
2953
- logger.info(
2954
- `Skipping detected agents incompatible with ${transport}: ${import_picocolors13.default.yellow(skippedList)}`
2955
- );
2956
- }
2957
- }
2958
- const targetAgents = resolvedTargets.isDetected ? resolvedTargets.agents : resolvedTargets.allAgents;
2959
- const result = installMcpServer({
2960
- source,
2961
- name: options.name,
2962
- agents: targetAgents,
2963
- args: options.args,
2964
- global: isGlobal,
2965
- cwd,
2966
- transport: explicitTransport,
2967
- headers: parseKeyValueList(options.header, ":"),
2968
- env: parseKeyValueList(options.env, "=")
3587
+ // src/interactive/wizard-remove.ts
3588
+ var import_prompts9 = require("@inquirer/prompts");
3589
+ var import_picocolors17 = __toESM(require("picocolors"), 1);
3590
+ var wizardRemove = async (options = {}) => {
3591
+ const cwd = options.cwd ?? process.cwd();
3592
+ const isGlobal = await promptScope({
3593
+ cwd,
3594
+ defaultGlobal: options.global,
3595
+ message: "Select scope to remove MCP server from:"
3596
+ });
3597
+ const serverMap = queryGroupedInstalledServers({ global: isGlobal, cwd });
3598
+ if (serverMap.size === 0) {
3599
+ logger.warn(`No installed MCP servers found in ${isGlobal ? "global" : "project"} scope`);
3600
+ return false;
3601
+ }
3602
+ let serverName = options.name;
3603
+ if (!serverName) {
3604
+ const choices = Array.from(serverMap.values()).map((g) => ({
3605
+ name: `${import_picocolors17.default.bold(g.serverName)} ${import_picocolors17.default.dim(`(installed in: ${g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ")})`)}`,
3606
+ value: g.serverName
3607
+ }));
3608
+ serverName = await (0, import_prompts9.select)({
3609
+ message: "Select MCP server to remove:",
3610
+ choices
2969
3611
  });
2970
- logger.info(
2971
- `Installing ${import_picocolors13.default.bold(result.serverName)} (${import_picocolors13.default.cyan(parsed.type)}) to ${import_picocolors13.default.cyan(String(result.results.length))} agent(s)`
2972
- );
2973
- for (const record of result.results) {
2974
- if (record.success) {
2975
- logger.success(`${import_picocolors13.default.cyan(record.agent)} ${import_picocolors13.default.dim(record.path)}`);
2976
- } else {
2977
- logger.error(`${import_picocolors13.default.cyan(record.agent)}: ${record.error}`);
2978
- }
2979
- }
2980
- if (result.results.some((record) => !record.success)) process.exitCode = 1;
2981
- } catch (error) {
2982
- logger.error(toErrorMessage(error));
2983
- process.exitCode = 1;
2984
3612
  }
2985
- });
2986
-
2987
- // src/cli/list.ts
2988
- var import_commander3 = require("commander");
2989
- var import_picocolors14 = __toESM(require("picocolors"), 1);
2990
- var mcpListCommand = new import_commander3.Command("list").alias("ls").description("List installed MCP servers across agents").option("-g, --global", "List global configs instead of project").option("-a, --agent <agents...>", "Filter by specific agents").option("--json", "Output as JSON").action((options) => {
2991
- try {
2992
- const entries = listInstalledMcpServers({
2993
- global: Boolean(options.global),
2994
- cwd: process.cwd(),
2995
- agents: parseMcpAgentList(options.agent)
3613
+ const rawInstalledAgents = serverMap.get(serverName)?.agents || [];
3614
+ if (rawInstalledAgents.length === 0) {
3615
+ logger.warn(`No agents found with [${serverName}] installed`);
3616
+ return false;
3617
+ }
3618
+ const installedAgents = agentConfigStore.sortAgentsByClusters(rawInstalledAgents, { global: isGlobal, cwd });
3619
+ let targetAgents = options.agents;
3620
+ if (!targetAgents || targetAgents.length === 0) {
3621
+ const choices = buildLinkedAgentChoices({
3622
+ agents: installedAgents,
3623
+ checkedAgents: installedAgents,
3624
+ scopeOptions: { global: isGlobal, cwd }
2996
3625
  });
2997
- if (options.json) {
2998
- console.log(JSON.stringify(entries, null, 2));
2999
- return;
3000
- }
3001
- if (entries.length === 0) {
3002
- logger.warn("No MCP servers installed");
3003
- return;
3004
- }
3005
- const grouped = /* @__PURE__ */ new Map();
3006
- for (const entry of entries) {
3007
- const existing = grouped.get(entry.serverName) ?? [];
3008
- existing.push(entry);
3009
- grouped.set(entry.serverName, existing);
3626
+ targetAgents = await linkedCheckbox({
3627
+ message: `Select agents to remove [${serverName}] from:`,
3628
+ choices,
3629
+ validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
3630
+ });
3631
+ } else {
3632
+ const validAgents = targetAgents.filter((agent) => installedAgents.includes(agent));
3633
+ if (validAgents.length === 0) {
3634
+ logger.warn(`None of the specified agents (${targetAgents.join(", ")}) have [${serverName}] installed`);
3635
+ return false;
3010
3636
  }
3011
- for (const [serverName, group] of grouped) {
3012
- const agentLabels = group.map((record) => record.agent).join(", ");
3013
- console.log(` ${import_picocolors14.default.bold(serverName)} ${import_picocolors14.default.dim(`[${agentLabels}]`)}`);
3014
- const firstPath = group[0]?.path;
3015
- if (firstPath) console.log(` ${import_picocolors14.default.dim(firstPath)}`);
3637
+ targetAgents = validAgents;
3638
+ }
3639
+ const confirmed = await (0, import_prompts9.confirm)({
3640
+ message: `Confirm removing MCP server [${serverName}] from ${targetAgents.join(", ")}?`,
3641
+ default: true
3642
+ });
3643
+ if (!confirmed) {
3644
+ logger.warn("Operation cancelled");
3645
+ return false;
3646
+ }
3647
+ const results = removeMcpServer({
3648
+ name: serverName,
3649
+ agents: targetAgents,
3650
+ global: isGlobal,
3651
+ cwd
3652
+ });
3653
+ let removedCount = 0;
3654
+ for (const res of results) {
3655
+ if (res.removed) {
3656
+ logger.success(
3657
+ `${import_picocolors17.default.cyan(res.agent)}: Successfully removed from ${import_picocolors17.default.dim(res.path)}${formatCoHostedBadge("affected", res.coAffectedAgents)}`
3658
+ );
3659
+ removedCount++;
3660
+ } else if (res.error) {
3661
+ logger.error(`${import_picocolors17.default.cyan(res.agent)}: Failed to remove - ${res.error}`);
3016
3662
  }
3017
- } catch (error) {
3018
- logger.error(toErrorMessage(error));
3019
- process.exitCode = 1;
3020
3663
  }
3021
- });
3664
+ if (removedCount > 0) {
3665
+ logger.success(`Successfully removed [${serverName}] from ${removedCount} agent(s)`);
3666
+ return true;
3667
+ }
3668
+ logger.warn(`Failed to remove [${serverName}] from specified agents`);
3669
+ return false;
3670
+ };
3022
3671
 
3023
3672
  // src/cli/remove.ts
3024
- var import_commander4 = require("commander");
3025
- var import_picocolors15 = __toESM(require("picocolors"), 1);
3026
3673
  var mcpRemoveCommand = new import_commander4.Command("remove").alias("rm").description("Remove an MCP server from agent configs").argument("[name]", "Server name").option("-g, --global", "Remove from global scope").option("-a, --agent <agents...>", "Filter by specific agents (use '*' for all)").option("-y, --yes", "Skip confirmation prompts").action(async (name, options) => {
3027
3674
  try {
3028
3675
  const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
@@ -3046,16 +3693,17 @@ var mcpRemoveCommand = new import_commander4.Command("remove").alias("rm").descr
3046
3693
  cwd: process.cwd()
3047
3694
  });
3048
3695
  if (results.length === 0) {
3049
- logger.warn(`No agent config contained ${import_picocolors15.default.bold(name)}`);
3696
+ logger.warn(`No agent config contained ${import_picocolors18.default.bold(name)}`);
3050
3697
  return;
3051
3698
  }
3052
3699
  for (const record of results) {
3053
3700
  if (record.removed) {
3054
3701
  logger.success(
3055
- `${import_picocolors15.default.cyan(record.agent)} removed ${import_picocolors15.default.bold(name)} ${import_picocolors15.default.dim(record.path)}`
3702
+ `${import_picocolors18.default.cyan(record.agent)} removed ${import_picocolors18.default.bold(name)} ${import_picocolors18.default.dim(record.path)}`
3056
3703
  );
3704
+ logCoHostedNotice("affected", record.coAffectedAgents);
3057
3705
  } else {
3058
- logger.error(`${import_picocolors15.default.cyan(record.agent)}: ${record.error ?? "not found"}`);
3706
+ logger.error(`${import_picocolors18.default.cyan(record.agent)}: ${record.error ?? "not found"}`);
3059
3707
  }
3060
3708
  }
3061
3709
  } catch (error) {
@@ -3064,8 +3712,61 @@ var mcpRemoveCommand = new import_commander4.Command("remove").alias("rm").descr
3064
3712
  }
3065
3713
  });
3066
3714
 
3715
+ // src/interactive/main-menu.ts
3716
+ var import_prompts10 = require("@inquirer/prompts");
3717
+ var import_picocolors19 = __toESM(require("picocolors"), 1);
3718
+ var mainMenu = async () => {
3719
+ console.log();
3720
+ console.log(import_picocolors19.default.bold(import_picocolors19.default.cyan("mcps - Cross-Platform MCP Manager for AI Coding Agents")));
3721
+ console.log(import_picocolors19.default.dim("Cross-platform MCP server configuration & synchronization tool"));
3722
+ console.log();
3723
+ while (true) {
3724
+ try {
3725
+ const action = await (0, import_prompts10.select)({
3726
+ message: "Select an action:",
3727
+ choices: [
3728
+ {
3729
+ name: "Add MCP Server",
3730
+ value: "add"
3731
+ },
3732
+ {
3733
+ name: "Manage & Sync Installed MCP Servers",
3734
+ value: "manage"
3735
+ },
3736
+ {
3737
+ name: "Remove MCP Server",
3738
+ value: "remove"
3739
+ },
3740
+ {
3741
+ name: "Exit",
3742
+ value: "exit"
3743
+ }
3744
+ ]
3745
+ });
3746
+ if (action === "exit") {
3747
+ console.log(import_picocolors19.default.dim("Goodbye!"));
3748
+ break;
3749
+ }
3750
+ if (action === "add") {
3751
+ await wizardAdd();
3752
+ } else if (action === "manage") {
3753
+ await wizardManage();
3754
+ } else if (action === "remove") {
3755
+ await wizardRemove();
3756
+ }
3757
+ console.log();
3758
+ } catch (error) {
3759
+ if (error?.name === "ExitPromptError") {
3760
+ console.log("\n" + import_picocolors19.default.dim("Exited."));
3761
+ break;
3762
+ }
3763
+ throw error;
3764
+ }
3765
+ }
3766
+ };
3767
+
3067
3768
  // src/cli.ts
3068
- var VERSION = "0.1.0-beta.2";
3769
+ var VERSION = "0.1.0";
3069
3770
  process.on("SIGINT", () => process.exit(0));
3070
3771
  process.on("SIGTERM", () => process.exit(0));
3071
3772
  var program = new import_commander5.Command().name("mcps").description("Install, list, and remove MCP servers across AI coding agents").version(VERSION, "-v, --version", "display the version number");