@wuyax/mcps 0.1.0-beta.3 → 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_picocolors17 = __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,220 +1115,45 @@ 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
- // src/resolve-config-clusters.ts
998
- var getCandidateAgentsForScope = (options = {}) => {
999
- return options.global ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
1000
- };
1001
- var getCoHostedAgents = (agentType, options = {}) => {
1002
- const currentAgent = getMcpAgentConfig(agentType);
1003
- const currentTarget = resolveMcpConfigTarget(currentAgent, options);
1004
- const candidates = getCandidateAgentsForScope(options);
1005
- const coHosted = [];
1006
- for (const candidateType of candidates) {
1007
- if (candidateType === agentType) continue;
1008
- const candidateConfig = getMcpAgentConfig(candidateType);
1009
- const candidateTarget = resolveMcpConfigTarget(candidateConfig, options);
1010
- if (candidateTarget.configPath === currentTarget.configPath && candidateTarget.configKey === currentTarget.configKey) {
1011
- coHosted.push(candidateType);
1012
- }
1013
- }
1014
- return coHosted;
1015
- };
1016
- var resolveConfigClusters = (agentTypes, options = {}) => {
1017
- const clustersByPath = /* @__PURE__ */ new Map();
1018
- for (const agentType of agentTypes) {
1019
- const agentConfig = getMcpAgentConfig(agentType);
1020
- const target = resolveMcpConfigTarget(agentConfig, options);
1021
- let keyMap = clustersByPath.get(target.configPath);
1022
- if (!keyMap) {
1023
- keyMap = /* @__PURE__ */ new Map();
1024
- clustersByPath.set(target.configPath, keyMap);
1025
- }
1026
- let cluster = keyMap.get(target.configKey);
1027
- if (!cluster) {
1028
- const allCoHosted = getCoHostedAgents(agentType, options);
1029
- cluster = {
1030
- configPath: target.configPath,
1031
- configKey: target.configKey,
1032
- targetAgents: [],
1033
- coHostedAgents: allCoHosted
1034
- };
1035
- keyMap.set(target.configKey, cluster);
1036
- }
1037
- if (!cluster.targetAgents.includes(agentType)) {
1038
- cluster.targetAgents.push(agentType);
1039
- }
1040
- }
1041
- const clusters = [];
1042
- for (const keyMap of clustersByPath.values()) {
1043
- for (const cluster of keyMap.values()) {
1044
- cluster.coHostedAgents = cluster.coHostedAgents.filter(
1045
- (co) => !cluster.targetAgents.includes(co)
1046
- );
1047
- clusters.push(cluster);
1048
- }
1049
- }
1050
- return clusters;
1051
- };
1052
- var sortAgentsWithClusters = (agentTypes, options = {}) => {
1053
- const clusters = resolveConfigClusters(agentTypes, options);
1054
- const sorted = [];
1055
- for (const cluster of clusters) {
1056
- for (const agent of cluster.targetAgents) {
1057
- if (!sorted.includes(agent)) {
1058
- sorted.push(agent);
1059
- }
1060
- }
1061
- }
1062
- return sorted;
1063
- };
1064
-
1065
- // src/transforms/index.ts
1066
- var DIALECT_PRESETS = {
1067
- vscode: {
1068
- stdioTransport: "type-stdio",
1069
- remoteTransport: "type-http-sse"
1070
- },
1071
- augment: {
1072
- stdioTransport: "none",
1073
- remoteTransport: "type-http-sse"
1074
- },
1075
- amp: {
1076
- stdioTransport: "none",
1077
- remoteTransport: "none"
1078
- },
1079
- trae: {
1080
- stdioTransport: "none",
1081
- remoteTransport: "sse-only-type"
1082
- },
1083
- grok: {
1084
- stdioTransport: "none",
1085
- remoteTransport: "sse-only-type"
1086
- },
1087
- cline: {
1088
- stdioTransport: "none",
1089
- remoteTransport: "streamableHttp"
1090
- },
1091
- goose: {
1092
- stdioTransport: "type-stdio",
1093
- remoteTransport: "streamable_http",
1094
- commandField: "cmd",
1095
- envField: "envs",
1096
- urlField: "uri",
1097
- defaultEnvEmpty: true,
1098
- defaultHeadersEmpty: true,
1099
- includeServerName: true,
1100
- timeoutSeconds: GOOSE_TIMEOUT_SECONDS,
1101
- extraFields: {
1102
- description: "",
1103
- enabled: true
1118
+ // src/transforms/index.ts
1119
+ var DIALECT_PRESETS = {
1120
+ vscode: {
1121
+ stdioTransport: "type-stdio",
1122
+ remoteTransport: "type-http-sse"
1123
+ },
1124
+ augment: {
1125
+ stdioTransport: "none",
1126
+ remoteTransport: "type-http-sse"
1127
+ },
1128
+ amp: {
1129
+ stdioTransport: "none",
1130
+ remoteTransport: "none"
1131
+ },
1132
+ trae: {
1133
+ stdioTransport: "none",
1134
+ remoteTransport: "sse-only-type"
1135
+ },
1136
+ grok: {
1137
+ stdioTransport: "none",
1138
+ remoteTransport: "sse-only-type"
1139
+ },
1140
+ cline: {
1141
+ stdioTransport: "none",
1142
+ remoteTransport: "streamableHttp"
1143
+ },
1144
+ goose: {
1145
+ stdioTransport: "type-stdio",
1146
+ remoteTransport: "streamable_http",
1147
+ commandField: "cmd",
1148
+ envField: "envs",
1149
+ urlField: "uri",
1150
+ defaultEnvEmpty: true,
1151
+ defaultHeadersEmpty: true,
1152
+ includeServerName: true,
1153
+ timeoutSeconds: GOOSE_TIMEOUT_SECONDS,
1154
+ extraFields: {
1155
+ description: "",
1156
+ enabled: true
1104
1157
  }
1105
1158
  },
1106
1159
  "kimi-code": {
@@ -1269,246 +1322,392 @@ var transformServerConfigForAgent = (agent, serverName, config, context = { glob
1269
1322
  return config;
1270
1323
  };
1271
1324
 
1272
- // src/installer.ts
1273
- var installMcpServerForAgents = (serverName, serverConfig, agentTypes, options = {}) => {
1274
- const clusters = resolveConfigClusters(agentTypes, options);
1275
- const resultsByAgent = /* @__PURE__ */ new Map();
1325
+ // src/utils/to-error-message.ts
1326
+ var toErrorMessage = (error, fallback = "Unknown error") => error instanceof Error ? error.message : fallback;
1327
+
1328
+ // src/config-store.ts
1329
+ var resolveMcpConfigTarget = (agent, options = {}) => {
1276
1330
  const isGlobal = options.global ?? false;
1277
- for (const cluster of clusters) {
1278
- const primaryAgentType = cluster.targetAgents[0];
1279
- const primaryAgent = getMcpAgentConfig(primaryAgentType);
1280
- try {
1281
- const transformed = transformServerConfigForAgent(primaryAgent, serverName, serverConfig, {
1282
- global: isGlobal
1283
- });
1284
- agentConfigStore.writeServer(primaryAgent, serverName, transformed, options);
1285
- for (const agentType of cluster.targetAgents) {
1286
- resultsByAgent.set(agentType, {
1287
- agent: agentType,
1288
- success: true,
1289
- path: cluster.configPath,
1290
- coConfiguredAgents: cluster.coHostedAgents.length > 0 ? cluster.coHostedAgents : void 0
1291
- });
1292
- }
1293
- } catch (error) {
1294
- const errorMsg = toErrorMessage(error);
1295
- for (const agentType of cluster.targetAgents) {
1296
- resultsByAgent.set(agentType, {
1297
- agent: agentType,
1298
- success: false,
1299
- path: cluster.configPath,
1300
- error: errorMsg
1301
- });
1302
- }
1303
- }
1304
- }
1305
- return agentTypes.map((agentType) => resultsByAgent.get(agentType));
1306
- };
1307
- var installToCompatibleAgents = (serverName, serverConfig, options) => {
1308
- const { allAgents, incompatible = [], global: isGlobal, cwd } = options;
1309
- const incompatibleMap = new Map(incompatible.map((item) => [item.agent, item.reason]));
1310
- const compatibleAgents = allAgents.filter((a) => !incompatibleMap.has(a));
1311
- const installedResults = installMcpServerForAgents(serverName, serverConfig, compatibleAgents, {
1312
- global: isGlobal,
1313
- cwd
1314
- });
1315
- const installedMap = new Map(installedResults.map((r) => [r.agent, r]));
1316
- return allAgents.map((agentType) => {
1317
- const incompatibleReason = incompatibleMap.get(agentType);
1318
- if (incompatibleReason) {
1319
- return {
1320
- agent: agentType,
1321
- success: false,
1322
- path: "",
1323
- error: incompatibleReason
1324
- };
1325
- }
1326
- return installedMap.get(agentType);
1327
- });
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 };
1328
1335
  };
1329
-
1330
- // src/utils/parse-mcp-agent-list.ts
1331
- var parseMcpAgentList = (input7) => {
1332
- if (!input7 || input7.length === 0) return void 0;
1333
- if (input7.includes("*")) return getMcpAgentTypes();
1334
- const resolved = [];
1335
- for (const value of input7) {
1336
- const agentType = resolveMcpAgentAlias(value);
1337
- if (!agentType) throw new Error(`Unknown MCP agent "${value}"`);
1338
- resolved.push(agentType);
1339
- }
1340
- return resolved;
1336
+ var getCandidateAgentsForScope = (options = {}) => {
1337
+ return options.global ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
1341
1338
  };
1342
-
1343
- // src/resolve-target-agents.ts
1344
- var normalizeRequestedAgents = (input7) => {
1345
- if (!input7 || input7.length === 0) return void 0;
1346
- const rawList = [...input7];
1347
- if (rawList.every((item) => isMcpAgentType(item))) {
1348
- return rawList;
1339
+ var FsConfigStoreAdapter = class {
1340
+ exists(filePath) {
1341
+ return (0, import_node_fs6.existsSync)(filePath);
1349
1342
  }
1350
- return parseMcpAgentList(rawList);
1351
- };
1352
- var resolveTargetAgents = (query = {}) => {
1353
- const cwd = query.cwd ?? process.cwd();
1354
- const isGlobal = query.global ?? false;
1355
- let explicitAgents = normalizeRequestedAgents(query.requested);
1356
- if (query.all) {
1357
- explicitAgents = getMcpAgentTypes();
1343
+ read(target) {
1344
+ return readConfigFile(target.filePath, target.format);
1358
1345
  }
1359
- const isDetected = !explicitAgents || explicitAgents.length === 0;
1360
- const detected = isGlobal ? detectGloballyInstalledMcpAgents() : detectProjectInstalledMcpAgents(cwd);
1361
- const candidateAgents = isDetected ? detected : explicitAgents ?? [];
1362
- const allAgents = candidateAgents.filter(
1363
- (type, index) => candidateAgents.indexOf(type) === index
1364
- );
1365
- const incompatible = [];
1366
- const compatibleAgents = [];
1367
- for (const agentType of allAgents) {
1368
- const config = getMcpAgentConfig(agentType);
1369
- if (query.transport && !isMcpTransportSupported(config, query.transport)) {
1370
- incompatible.push({
1371
- agent: agentType,
1372
- reason: config.unsupportedTransportMessage ?? `agent ${agentType} only supports ${config.supportedTransports.join(", ")} transport (attempted ${query.transport})`
1373
- });
1374
- } else {
1375
- compatibleAgents.push(agentType);
1346
+ writeServer(target, serverName, serverConfig) {
1347
+ if (!target.dottedKey) {
1348
+ throw new Error(`Cannot write server: missing dottedKey for ${target.filePath}`);
1376
1349
  }
1350
+ writeServerToConfigFile(
1351
+ target.filePath,
1352
+ target.format,
1353
+ target.dottedKey,
1354
+ serverName,
1355
+ serverConfig
1356
+ );
1377
1357
  }
1378
- let diagnostic;
1379
- if (compatibleAgents.length === 0) {
1380
- if (isDetected) {
1381
- diagnostic = `No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass -a <agent> (e.g. -a cursor) or --all to install.`;
1382
- } else if (allAgents.length > 0 && incompatible.length > 0 && query.transport) {
1383
- const list = incompatible.map((item) => `${item.agent} (${item.reason})`).join(", ");
1384
- diagnostic = `None of the selected agents support ${query.transport} transport: ${list}`;
1385
- } else {
1386
- diagnostic = "No valid target agents specified.";
1387
- }
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
+ );
1388
1366
  }
1389
- return {
1390
- agents: compatibleAgents,
1391
- compatibleAgents,
1392
- allAgents,
1393
- candidateAgents: allAgents,
1394
- detected,
1395
- isDetected,
1396
- incompatible,
1397
- diagnostic
1398
- };
1399
- };
1400
-
1401
- // src/source-parser.ts
1402
- var REMOTE_URL_REGEX = /^https?:\/\//i;
1403
- var HAS_WHITESPACE_REGEX = /\s/;
1404
- var PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(?:@[^\s]+)?$/;
1405
- var PATH_SEPARATOR_REGEX = /[/\\]/;
1406
- var stripVersionSuffix = (input7) => {
1407
- if (input7.startsWith("@")) {
1408
- const secondAtIndex = input7.indexOf("@", 1);
1409
- if (secondAtIndex > 0) return input7.slice(0, secondAtIndex);
1410
- return input7;
1367
+ listServers(target) {
1368
+ if (!target.dottedKey) return {};
1369
+ return listServersInConfigFile(target.filePath, target.format, target.dottedKey);
1411
1370
  }
1412
- const atIndex = input7.lastIndexOf("@");
1413
- if (atIndex > 0) return input7.slice(0, atIndex);
1414
- return input7;
1415
- };
1416
- var stripScopePrefix = (input7) => {
1417
- if (!input7.startsWith("@") || !input7.includes("/")) return input7;
1418
- const parts = input7.split("/");
1419
- return parts[1] || input7;
1420
1371
  };
1421
- var stripPathPrefix = (input7) => {
1422
- if (!PATH_SEPARATOR_REGEX.test(input7)) return input7;
1423
- const segments = input7.split(PATH_SEPARATOR_REGEX);
1424
- const basename = segments[segments.length - 1];
1425
- return basename || input7;
1426
- };
1427
- var extractPackageName = (input7) => {
1428
- let name = stripVersionSuffix(input7);
1429
- name = stripScopePrefix(name);
1430
- name = stripPathPrefix(name);
1431
- name = name.replace(SCRIPT_EXTENSION_REGEX, "");
1432
- for (const prefix of PACKAGE_NAME_PREFIX_STRIP) {
1433
- if (name.startsWith(prefix)) {
1434
- name = name.slice(prefix.length);
1435
- break;
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 };
1436
1403
  }
1404
+ const removed = this.adapter.removeServer(descriptor, serverName);
1405
+ return { path: descriptor.filePath, removed };
1437
1406
  }
1438
- for (const suffix of PACKAGE_NAME_SUFFIX_STRIP) {
1439
- if (name.endsWith(suffix)) {
1440
- name = name.slice(0, -suffix.length);
1441
- break;
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: {} };
1442
1411
  }
1412
+ const servers = this.adapter.listServers(descriptor);
1413
+ return { path: descriptor.filePath, exists: true, servers };
1443
1414
  }
1444
- return name || MCP_DEFAULT_SERVER_NAME;
1445
- };
1446
- var inferNameFromUrl = (input7) => {
1447
- try {
1448
- const url = new URL(input7);
1449
- const host = url.hostname;
1450
- const labels = host.split(".").filter((segment) => segment.length > 0);
1451
- if (labels.length === 0) return MCP_DEFAULT_SERVER_NAME;
1452
- const meaningfulLabels = labels.filter((label) => {
1453
- const lower = label.toLowerCase();
1454
- if (COMMON_TLD_LABELS.has(lower)) return false;
1455
- if (GENERIC_HOST_PREFIXES.has(lower)) return false;
1456
- return true;
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
+ };
1457
1435
  });
1458
- if (meaningfulLabels.length > 0) return meaningfulLabels[0];
1459
- if (labels.length >= 2) return labels[labels.length - 2];
1460
- return labels[labels.length - 1] || MCP_DEFAULT_SERVER_NAME;
1461
- } catch {
1462
- return MCP_DEFAULT_SERVER_NAME;
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));
1463
1607
  }
1464
1608
  };
1465
- var inferNameFromCommand = (command) => {
1466
- const tokens = command.trim().split(/\s+/);
1467
- const runnerBase = tokens[0]?.split(PATH_SEPARATOR_REGEX).pop() ?? "";
1468
- const startIndex = KNOWN_COMMAND_RUNNERS.has(runnerBase) ? 1 : 0;
1469
- for (let tokenIndex = startIndex; tokenIndex < tokens.length; tokenIndex += 1) {
1470
- const token = tokens[tokenIndex];
1471
- if (!token || token.startsWith("-")) continue;
1472
- return extractPackageName(token);
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;
1614
+ if (input7.includes("*")) return getMcpAgentTypes();
1615
+ const resolved = [];
1616
+ for (const value of input7) {
1617
+ const agentType = resolveMcpAgentAlias(value);
1618
+ if (!agentType) throw new Error(`Unknown MCP agent "${value}"`);
1619
+ resolved.push(agentType);
1473
1620
  }
1474
- const firstNonFlag = tokens.find((token) => !token.startsWith("-"));
1475
- return firstNonFlag ? extractPackageName(firstNonFlag) : MCP_DEFAULT_SERVER_NAME;
1621
+ return resolved;
1476
1622
  };
1477
- var parseMcpSource = (input7) => {
1478
- const trimmed = input7.trim();
1479
- if (trimmed.length === 0) {
1480
- throw new Error(
1481
- "Invalid MCP source: input is empty. Expected a remote URL, an npm package, or a command line."
1482
- );
1623
+
1624
+ // src/resolve-target-agents.ts
1625
+ var normalizeRequestedAgents = (input7) => {
1626
+ if (!input7 || input7.length === 0) return void 0;
1627
+ const rawList = [...input7];
1628
+ if (rawList.every((item) => isMcpAgentType(item))) {
1629
+ return rawList;
1483
1630
  }
1484
- if (REMOTE_URL_REGEX.test(trimmed)) {
1485
- return {
1486
- type: "remote",
1487
- value: trimmed,
1488
- inferredName: inferNameFromUrl(trimmed)
1489
- };
1631
+ return parseMcpAgentList(rawList);
1632
+ };
1633
+ var resolveTargetAgents = (query = {}) => {
1634
+ const cwd = query.cwd ?? process.cwd();
1635
+ const isGlobal = query.global ?? false;
1636
+ let explicitAgents = normalizeRequestedAgents(query.requested);
1637
+ if (query.all) {
1638
+ explicitAgents = getMcpAgentTypes();
1490
1639
  }
1491
- if (HAS_WHITESPACE_REGEX.test(trimmed)) {
1492
- return {
1493
- type: "command",
1494
- value: trimmed,
1495
- inferredName: inferNameFromCommand(trimmed)
1496
- };
1640
+ const isDetected = !explicitAgents || explicitAgents.length === 0;
1641
+ const detected = isGlobal ? detectGloballyInstalledMcpAgents() : detectProjectInstalledMcpAgents(cwd);
1642
+ const candidateAgents = isDetected ? detected : explicitAgents ?? [];
1643
+ const allAgents = candidateAgents.filter(
1644
+ (type, index) => candidateAgents.indexOf(type) === index
1645
+ );
1646
+ const incompatible = [];
1647
+ const compatibleAgents = [];
1648
+ for (const agentType of allAgents) {
1649
+ const config = getMcpAgentConfig(agentType);
1650
+ if (query.transport && !isMcpTransportSupported(config, query.transport)) {
1651
+ incompatible.push({
1652
+ agent: agentType,
1653
+ reason: config.unsupportedTransportMessage ?? `agent ${agentType} only supports ${config.supportedTransports.join(", ")} transport (attempted ${query.transport})`
1654
+ });
1655
+ } else {
1656
+ compatibleAgents.push(agentType);
1657
+ }
1497
1658
  }
1498
- if (PACKAGE_NAME_REGEX.test(trimmed)) {
1499
- return {
1500
- type: "package",
1501
- value: trimmed,
1502
- inferredName: extractPackageName(trimmed)
1503
- };
1659
+ let diagnostic;
1660
+ if (compatibleAgents.length === 0) {
1661
+ if (isDetected) {
1662
+ diagnostic = `No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass -a <agent> (e.g. -a cursor) or --all to install.`;
1663
+ } else if (allAgents.length > 0 && incompatible.length > 0 && query.transport) {
1664
+ const list = incompatible.map((item) => `${item.agent} (${item.reason})`).join(", ");
1665
+ diagnostic = `None of the selected agents support ${query.transport} transport: ${list}`;
1666
+ } else {
1667
+ diagnostic = "No valid target agents specified.";
1668
+ }
1504
1669
  }
1505
1670
  return {
1506
- type: "command",
1507
- value: trimmed,
1508
- inferredName: inferNameFromCommand(trimmed)
1671
+ agents: compatibleAgents,
1672
+ compatibleAgents,
1673
+ allAgents,
1674
+ candidateAgents: allAgents,
1675
+ detected,
1676
+ isDetected,
1677
+ incompatible,
1678
+ diagnostic
1509
1679
  };
1510
1680
  };
1511
1681
 
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
1694
+ }
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
+ };
1706
+ }
1707
+ return installedMap.get(agentType);
1708
+ });
1709
+ };
1710
+
1512
1711
  // src/install-mcp-server.ts
1513
1712
  var installMcpServer = (options) => {
1514
1713
  const parsed = parseMcpSource(options.source);
@@ -1541,15 +1740,14 @@ var installMcpServer = (options) => {
1541
1740
  var listInstalledMcpServers = (options = {}) => {
1542
1741
  const agentTypes = options.agents ?? getMcpAgentTypes();
1543
1742
  const collected = [];
1544
- for (const agentType of agentTypes) {
1545
- const agent = getMcpAgentConfig(agentType);
1546
- const { path, exists, servers } = agentConfigStore.listServers(agent, options);
1547
- if (!exists) continue;
1548
- for (const [serverName, rawConfig] of Object.entries(servers)) {
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)) {
1549
1747
  collected.push({
1550
1748
  serverName,
1551
- agent: agentType,
1552
- path,
1749
+ agent: item.agent,
1750
+ path: item.path,
1553
1751
  config: rawConfig,
1554
1752
  serverConfig: parseServerConfig(rawConfig)
1555
1753
  });
@@ -1557,110 +1755,40 @@ var listInstalledMcpServers = (options = {}) => {
1557
1755
  }
1558
1756
  return collected;
1559
1757
  };
1560
-
1561
- // src/remove.ts
1562
- var removeMcpServer = (options) => {
1563
- const { allAgents } = resolveTargetAgents({
1564
- requested: options.agents,
1565
- all: !options.agents,
1566
- global: options.global,
1567
- cwd: options.cwd
1568
- });
1569
- const clusters = resolveConfigClusters(allAgents, {
1570
- global: options.global,
1571
- cwd: options.cwd
1572
- });
1573
- const results = [];
1574
- for (const cluster of clusters) {
1575
- const primaryAgentType = cluster.targetAgents[0];
1576
- const primaryAgent = getMcpAgentConfig(primaryAgentType);
1577
- try {
1578
- const { removed } = agentConfigStore.removeServer(primaryAgent, options.name, {
1579
- global: options.global,
1580
- cwd: options.cwd
1581
- });
1582
- if (removed) {
1583
- for (const agentType of cluster.targetAgents) {
1584
- results.push({
1585
- agent: agentType,
1586
- path: cluster.configPath,
1587
- removed: true,
1588
- coAffectedAgents: cluster.coHostedAgents.length > 0 ? cluster.coHostedAgents : void 0
1589
- });
1590
- }
1591
- }
1592
- } catch (error) {
1593
- const errorMsg = toErrorMessage(error);
1594
- for (const agentType of cluster.targetAgents) {
1595
- results.push({
1596
- agent: agentType,
1597
- path: cluster.configPath,
1598
- removed: false,
1599
- error: errorMsg
1600
- });
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;
1601
1775
  }
1602
1776
  }
1603
- }
1604
- return results;
1605
- };
1606
-
1607
- // src/update-mcp-server.ts
1608
- var toRemoteServerConfig = (config, defaultTransport = "http") => {
1609
- const {
1610
- command: _droppedCommand,
1611
- args: _droppedArgs,
1612
- env: _droppedEnv,
1613
- ...remoteConfig
1614
- } = config;
1615
- return {
1616
- ...remoteConfig,
1617
- type: remoteConfig.type ?? defaultTransport
1618
- };
1619
- };
1620
- var toStdioServerConfig = (config) => {
1621
- const {
1622
- url: _droppedUrl,
1623
- type: _droppedType,
1624
- headers: _droppedHeaders,
1625
- ...stdioConfig
1626
- } = config;
1627
- return stdioConfig;
1628
- };
1629
- var detectUpdateTransition = (incoming, previous) => {
1630
- if (!previous) {
1631
- return incoming.url ? "switch-to-remote" : "switch-to-stdio";
1632
- }
1633
- if (incoming.url && !incoming.command) {
1634
- return "switch-to-remote";
1635
- }
1636
- if (incoming.command && !incoming.url) {
1637
- return "switch-to-stdio";
1638
- }
1639
- if (incoming.url || !incoming.command && previous.url) {
1640
- return "merge-remote";
1641
- }
1642
- return "merge-stdio";
1643
- };
1644
- var sanitizeUpdatedServerConfig = (incoming, previous) => {
1645
- const transition = detectUpdateTransition(incoming, previous);
1646
- const targetTransport = incoming.type ?? previous?.type ?? "http";
1647
- switch (transition) {
1648
- case "switch-to-remote": {
1649
- const cleanBase = previous ? toRemoteServerConfig(previous, targetTransport) : {};
1650
- return toRemoteServerConfig({ ...cleanBase, ...incoming }, targetTransport);
1651
- }
1652
- case "switch-to-stdio": {
1653
- const cleanBase = previous ? toStdioServerConfig(previous) : {};
1654
- return toStdioServerConfig({ ...cleanBase, ...incoming });
1655
- }
1656
- case "merge-remote": {
1657
- return toRemoteServerConfig({ ...previous, ...incoming }, targetTransport);
1777
+ if (!entry.agents.includes(item.agent)) {
1778
+ entry.agents.push(item.agent);
1658
1779
  }
1659
- case "merge-stdio": {
1660
- return toStdioServerConfig({ ...previous, ...incoming });
1780
+ if (!entry.paths.includes(item.path)) {
1781
+ entry.paths.push(item.path);
1661
1782
  }
1662
1783
  }
1784
+ return grouped;
1663
1785
  };
1786
+ var queryGroupedInstalledServers = (options = {}) => {
1787
+ const installed = listInstalledMcpServers(options);
1788
+ return groupInstalledServersByName(installed);
1789
+ };
1790
+
1791
+ // src/update-mcp-server.ts
1664
1792
  var updateMcpServer = (options) => {
1665
1793
  const isGlobal = options.global ?? false;
1666
1794
  const cwd = options.cwd ?? process.cwd();
@@ -1703,9 +1831,36 @@ var updateMcpServer = (options) => {
1703
1831
  };
1704
1832
  };
1705
1833
 
1706
- // src/interactive/main-menu.ts
1707
- var import_prompts10 = require("@inquirer/prompts");
1708
- var import_picocolors15 = __toESM(require("picocolors"), 1);
1834
+ // src/remove.ts
1835
+ var removeMcpServer = (options) => {
1836
+ const { allAgents } = resolveTargetAgents({
1837
+ requested: options.agents,
1838
+ all: !options.agents,
1839
+ global: options.global,
1840
+ cwd: options.cwd
1841
+ });
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;
1861
+ }
1862
+ return `${value.slice(0, 4)}***${value.slice(-3)}`;
1863
+ };
1709
1864
 
1710
1865
  // src/interactive/wizard-add.ts
1711
1866
  var import_prompts7 = require("@inquirer/prompts");
@@ -1754,7 +1909,7 @@ var buildLinkedAgentChoices = (options) => {
1754
1909
  const { agents, checkedAgents, detectedAgents = [], scopeOptions = {} } = options;
1755
1910
  const alignedCheckedSet = new Set(checkedAgents);
1756
1911
  for (const agent of checkedAgents) {
1757
- const coHosted = getCoHostedAgents(agent, scopeOptions);
1912
+ const coHosted = agentConfigStore.getCoHostedAgents(agent, scopeOptions);
1758
1913
  for (const co of coHosted) {
1759
1914
  if (agents.includes(co)) {
1760
1915
  alignedCheckedSet.add(co);
@@ -1765,7 +1920,7 @@ var buildLinkedAgentChoices = (options) => {
1765
1920
  const config = getMcpAgentConfig(agent);
1766
1921
  const displayName = config?.displayName ?? agent;
1767
1922
  const isDetected = detectedAgents.includes(agent);
1768
- const coHosted = getCoHostedAgents(agent, scopeOptions).filter(
1923
+ const coHosted = agentConfigStore.getCoHostedAgents(agent, scopeOptions).filter(
1769
1924
  (co) => agents.includes(co)
1770
1925
  );
1771
1926
  const detectedBadge = isDetected ? import_picocolors3.default.green(" [detected]") : "";
@@ -2029,7 +2184,10 @@ var promptScopeAndAgents = async (options = {}) => {
2029
2184
  });
2030
2185
  const detected = resolution.detected;
2031
2186
  const rawAvailable = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
2032
- const availableAgentTypes = sortAgentsWithClusters(rawAvailable, { global: isGlobal, cwd });
2187
+ const availableAgentTypes = agentConfigStore.sortAgentsByClusters(rawAvailable, {
2188
+ global: isGlobal,
2189
+ cwd
2190
+ });
2033
2191
  if (detected.length > 0) {
2034
2192
  logger.info(
2035
2193
  `Detected configured agents: ${import_picocolors6.default.cyan(detected.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
@@ -2109,22 +2267,6 @@ var promptEditArgs = async (currentArgs = []) => {
2109
2267
  var import_prompts5 = require("@inquirer/prompts");
2110
2268
  var import_picocolors9 = __toESM(require("picocolors"), 1);
2111
2269
 
2112
- // src/utils/mask-secret.ts
2113
- var SECRET_KEY_PATTERN = /(token|key|secret|password|passwd|auth|credential)/i;
2114
- var maskSecretValue = (key, value) => {
2115
- if (!SECRET_KEY_PATTERN.test(key) || value.length <= 4) {
2116
- return value;
2117
- }
2118
- return `${value.slice(0, 2)}***${value.slice(-2)}`;
2119
- };
2120
- var SECRET_HEADER_PATTERN = /(authorization|token|key|secret|auth)/i;
2121
- var maskSecretHeader = (key, value) => {
2122
- if (!SECRET_HEADER_PATTERN.test(key) || value.length <= 8) {
2123
- return value;
2124
- }
2125
- return `${value.slice(0, 4)}***${value.slice(-3)}`;
2126
- };
2127
-
2128
2270
  // src/interactive/prompts/kv.ts
2129
2271
  var import_prompts4 = require("@inquirer/prompts");
2130
2272
  var import_picocolors8 = __toESM(require("picocolors"), 1);
@@ -2728,18 +2870,166 @@ var wizardAdd = async (initial = {}) => {
2728
2870
  logger.error(`${import_picocolors11.default.cyan(record.agent)}: Failed to write - ${record.error}`);
2729
2871
  }
2730
2872
  }
2731
- if (allSuccess) {
2732
- logger.success(import_picocolors11.default.bold(`MCP server "${serverName}" configured successfully!`));
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
+ }
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
+ }
2985
+ });
2986
+
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;
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);
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)}`);
3016
+ }
3017
+ } catch (error) {
3018
+ logger.error(toErrorMessage(error));
3019
+ process.exitCode = 1;
2733
3020
  }
2734
- return allSuccess;
2735
- };
3021
+ });
3022
+
3023
+ // src/cli/manage.ts
3024
+ var import_commander3 = require("commander");
3025
+ var import_picocolors16 = __toESM(require("picocolors"), 1);
2736
3026
 
2737
3027
  // src/interactive/wizard-manage.ts
2738
3028
  var import_prompts8 = require("@inquirer/prompts");
2739
- var import_picocolors13 = __toESM(require("picocolors"), 1);
3029
+ var import_picocolors15 = __toESM(require("picocolors"), 1);
2740
3030
 
2741
3031
  // src/utils/display-server-details.ts
2742
- var import_picocolors12 = __toESM(require("picocolors"), 1);
3032
+ var import_picocolors14 = __toESM(require("picocolors"), 1);
2743
3033
  var displayServerDetails = ({
2744
3034
  serverName,
2745
3035
  config,
@@ -2748,81 +3038,50 @@ var displayServerDetails = ({
2748
3038
  global: isGlobal,
2749
3039
  titlePrefix = "MCP Server Details"
2750
3040
  }) => {
2751
- console.log("\n" + import_picocolors12.default.cyan(import_picocolors12.default.bold(`${titlePrefix}: [${serverName}]`)));
3041
+ console.log("\n" + import_picocolors14.default.cyan(import_picocolors14.default.bold(`${titlePrefix}: [${serverName}]`)));
2752
3042
  if (isGlobal !== void 0) {
2753
- console.log(` ${import_picocolors12.default.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
3043
+ console.log(` ${import_picocolors14.default.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
2754
3044
  }
2755
3045
  if (agents && agents.length > 0) {
2756
3046
  console.log(
2757
- ` ${import_picocolors12.default.bold("Configured Agents:")} ${import_picocolors12.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(", "))}`
2758
3048
  );
2759
3049
  }
2760
3050
  if (hasDivergence) {
2761
3051
  console.log(
2762
- ` ${import_picocolors12.default.yellow(import_picocolors12.default.bold("Notice:"))} ${import_picocolors12.default.yellow("Configurations differ across installed agents. Showing configuration from the first agent.")}`
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.")}`
2763
3053
  );
2764
3054
  }
2765
3055
  const isRemote = Boolean(config.url && config.url.length > 0);
2766
3056
  if (isRemote) {
2767
- console.log(` ${import_picocolors12.default.bold("Transport:")} ${import_picocolors12.default.magenta(config.type ?? "http")}`);
2768
- console.log(` ${import_picocolors12.default.bold("URL:")} ${import_picocolors12.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 ?? "")}`);
2769
3059
  const headerKeys = Object.keys(config.headers ?? {});
2770
3060
  if (headerKeys.length > 0) {
2771
- console.log(` ${import_picocolors12.default.bold("Headers:")} ${import_picocolors12.default.cyan(String(headerKeys.length))}`);
3061
+ console.log(` ${import_picocolors14.default.bold("Headers:")} ${import_picocolors14.default.cyan(String(headerKeys.length))}`);
2772
3062
  for (const [k, v] of Object.entries(config.headers ?? {})) {
2773
- console.log(` ${import_picocolors12.default.bold(k)}: ${import_picocolors12.default.dim(maskSecretHeader(k, v))}`);
3063
+ console.log(` ${import_picocolors14.default.bold(k)}: ${import_picocolors14.default.dim(maskSecretHeader(k, v))}`);
2774
3064
  }
2775
3065
  } else {
2776
- console.log(` ${import_picocolors12.default.bold("Headers:")} ${import_picocolors12.default.dim("(none)")}`);
3066
+ console.log(` ${import_picocolors14.default.bold("Headers:")} ${import_picocolors14.default.dim("(none)")}`);
2777
3067
  }
2778
3068
  } else {
2779
- console.log(` ${import_picocolors12.default.bold("Command:")} ${import_picocolors12.default.magenta(config.command ?? "")}`);
3069
+ console.log(` ${import_picocolors14.default.bold("Command:")} ${import_picocolors14.default.magenta(config.command ?? "")}`);
2780
3070
  const argsStr = config.args && config.args.length > 0 ? config.args.join(" ") : "(none)";
2781
- console.log(` ${import_picocolors12.default.bold("Arguments:")} ${import_picocolors12.default.dim(argsStr)}`);
3071
+ console.log(` ${import_picocolors14.default.bold("Arguments:")} ${import_picocolors14.default.dim(argsStr)}`);
2782
3072
  const envKeys = Object.keys(config.env ?? {});
2783
3073
  if (envKeys.length > 0) {
2784
- console.log(` ${import_picocolors12.default.bold("Environment Variables:")} ${import_picocolors12.default.cyan(String(envKeys.length))}`);
3074
+ console.log(` ${import_picocolors14.default.bold("Environment Variables:")} ${import_picocolors14.default.cyan(String(envKeys.length))}`);
2785
3075
  for (const [k, v] of Object.entries(config.env ?? {})) {
2786
- console.log(` ${import_picocolors12.default.bold(k)}=${import_picocolors12.default.dim(maskSecretValue(k, v))}`);
3076
+ console.log(` ${import_picocolors14.default.bold(k)}=${import_picocolors14.default.dim(maskSecretValue(k, v))}`);
2787
3077
  }
2788
3078
  } else {
2789
- console.log(` ${import_picocolors12.default.bold("Environment Variables:")} ${import_picocolors12.default.dim("(none)")}`);
3079
+ console.log(` ${import_picocolors14.default.bold("Environment Variables:")} ${import_picocolors14.default.dim("(none)")}`);
2790
3080
  }
2791
3081
  }
2792
3082
  console.log();
2793
3083
  };
2794
3084
 
2795
- // src/interactive/utils/group-installed-servers.ts
2796
- var normalizeServerConfig = parseServerConfig;
2797
- var groupInstalledServersByName = (installed) => {
2798
- const grouped = /* @__PURE__ */ new Map();
2799
- for (const item of installed) {
2800
- const itemConfig = normalizeServerConfig(item.config);
2801
- let entry = grouped.get(item.serverName);
2802
- if (!entry) {
2803
- entry = {
2804
- serverName: item.serverName,
2805
- agents: [],
2806
- paths: [],
2807
- config: itemConfig,
2808
- hasDivergence: false
2809
- };
2810
- grouped.set(item.serverName, entry);
2811
- } else if (!entry.hasDivergence) {
2812
- if (JSON.stringify(entry.config) !== JSON.stringify(itemConfig)) {
2813
- entry.hasDivergence = true;
2814
- }
2815
- }
2816
- if (!entry.agents.includes(item.agent)) {
2817
- entry.agents.push(item.agent);
2818
- }
2819
- if (!entry.paths.includes(item.path)) {
2820
- entry.paths.push(item.path);
2821
- }
2822
- }
2823
- return grouped;
2824
- };
2825
-
2826
3085
  // src/interactive/wizard-manage.ts
2827
3086
  var promptSwitchServerType = async (currentConfig, serverName) => {
2828
3087
  const isRemote = Boolean(currentConfig.url && currentConfig.url.length > 0);
@@ -2834,11 +3093,11 @@ var promptSwitchServerType = async (currentConfig, serverName) => {
2834
3093
  const newArgs = await promptEditArgs([]);
2835
3094
  const newEnv = await promptEditEnvConfig({});
2836
3095
  logger.success(`Switched [${serverName}] configuration to stdio mode`);
2837
- return {
3096
+ return toStdioServerConfig({
2838
3097
  command: newCmd.trim(),
2839
3098
  args: newArgs.length > 0 ? newArgs : void 0,
2840
3099
  env: Object.keys(newEnv).length > 0 ? newEnv : void 0
2841
- };
3100
+ });
2842
3101
  }
2843
3102
  const newUrl = await (0, import_prompts8.input)({
2844
3103
  message: "Remote server URL:",
@@ -2861,11 +3120,13 @@ var promptSwitchServerType = async (currentConfig, serverName) => {
2861
3120
  });
2862
3121
  const newHeaders = await promptEditHeadersConfig({});
2863
3122
  logger.success(`Switched [${serverName}] configuration to remote mode`);
2864
- return {
2865
- url: newUrl.trim(),
2866
- type: transport,
2867
- headers: Object.keys(newHeaders).length > 0 ? newHeaders : void 0
2868
- };
3123
+ return toRemoteServerConfig(
3124
+ {
3125
+ url: newUrl.trim(),
3126
+ headers: Object.keys(newHeaders).length > 0 ? newHeaders : void 0
3127
+ },
3128
+ transport
3129
+ );
2869
3130
  };
2870
3131
  var handleEditServerConfig = async (options) => {
2871
3132
  const { targetGroup } = options;
@@ -2963,7 +3224,7 @@ var handleEditServerConfig = async (options) => {
2963
3224
  } else if (editAction === "save") {
2964
3225
  let targetAgents = targetGroup.agents;
2965
3226
  if (targetGroup.agents.length > 1) {
2966
- const sortedAgents = sortAgentsWithClusters(targetGroup.agents, { global: isGlobal, cwd });
3227
+ const sortedAgents = agentConfigStore.sortAgentsByClusters(targetGroup.agents, { global: isGlobal, cwd });
2967
3228
  const choices = buildLinkedAgentChoices({
2968
3229
  agents: sortedAgents,
2969
3230
  checkedAgents: sortedAgents,
@@ -2992,7 +3253,7 @@ var handleEditServerConfig = async (options) => {
2992
3253
  });
2993
3254
  if (resolution.incompatible.length > 0) {
2994
3255
  for (const item of resolution.incompatible) {
2995
- logger.warn(`Skipping ${import_picocolors13.default.cyan(item.agent)}: ${item.reason}`);
3256
+ logger.warn(`Skipping ${import_picocolors15.default.cyan(item.agent)}: ${item.reason}`);
2996
3257
  }
2997
3258
  }
2998
3259
  if (resolution.compatibleAgents.length === 0) {
@@ -3025,10 +3286,10 @@ var handleEditServerConfig = async (options) => {
3025
3286
  updatedAny = true;
3026
3287
  succeededAgents.push(res.agent);
3027
3288
  logger.success(
3028
- `${import_picocolors13.default.cyan(res.agent)}: Successfully updated configuration in ${import_picocolors13.default.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
3289
+ `${import_picocolors15.default.cyan(res.agent)}: Successfully updated configuration in ${import_picocolors15.default.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
3029
3290
  );
3030
3291
  } else {
3031
- logger.error(`${import_picocolors13.default.cyan(res.agent)}: Update failed - ${res.error}`);
3292
+ logger.error(`${import_picocolors15.default.cyan(res.agent)}: Update failed - ${res.error}`);
3032
3293
  }
3033
3294
  }
3034
3295
  if (updatedAny) {
@@ -3046,16 +3307,14 @@ var wizardManage = async (options = {}) => {
3046
3307
  defaultGlobal: options.global,
3047
3308
  message: "Select MCP scope to inspect and manage:"
3048
3309
  });
3049
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
3050
- if (installed.length === 0) {
3310
+ const grouped = queryGroupedInstalledServers({ global: isGlobal, cwd });
3311
+ if (grouped.size === 0) {
3051
3312
  logger.warn(`No configured MCP servers found in ${isGlobal ? "global" : "project"} scope`);
3052
3313
  return;
3053
3314
  }
3054
- const grouped = groupInstalledServersByName(installed);
3055
3315
  let pendingServerName = options.serverName;
3056
3316
  const refreshGroupedServers = () => {
3057
- const freshInstalled = listInstalledMcpServers({ global: isGlobal, cwd });
3058
- const freshGrouped = groupInstalledServersByName(freshInstalled);
3317
+ const freshGrouped = queryGroupedInstalledServers({ global: isGlobal, cwd });
3059
3318
  grouped.clear();
3060
3319
  for (const [name, grp] of freshGrouped) {
3061
3320
  grouped.set(name, grp);
@@ -3071,7 +3330,7 @@ var wizardManage = async (options = {}) => {
3071
3330
  const choices = Array.from(grouped.values()).map((g) => {
3072
3331
  const agentNames = g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
3073
3332
  return {
3074
- name: `${import_picocolors13.default.bold(g.serverName)} ${import_picocolors13.default.dim(`(configured in: ${agentNames})`)}`,
3333
+ name: `${import_picocolors15.default.bold(g.serverName)} ${import_picocolors15.default.dim(`(configured in: ${agentNames})`)}`,
3075
3334
  value: g.serverName
3076
3335
  };
3077
3336
  });
@@ -3132,7 +3391,7 @@ var wizardManage = async (options = {}) => {
3132
3391
  );
3133
3392
  continue;
3134
3393
  }
3135
- const candidateAgents = sortAgentsWithClusters(rawCandidateAgents, { global: isGlobal, cwd });
3394
+ const candidateAgents = agentConfigStore.sortAgentsByClusters(rawCandidateAgents, { global: isGlobal, cwd });
3136
3395
  const choices = buildLinkedAgentChoices({
3137
3396
  agents: candidateAgents,
3138
3397
  checkedAgents: [],
@@ -3160,7 +3419,7 @@ var wizardManage = async (options = {}) => {
3160
3419
  cwd
3161
3420
  });
3162
3421
  for (const item of syncResult.incompatible) {
3163
- logger.warn(`Skipping ${import_picocolors13.default.cyan(item.agent)}: ${item.reason}`);
3422
+ logger.warn(`Skipping ${import_picocolors15.default.cyan(item.agent)}: ${item.reason}`);
3164
3423
  }
3165
3424
  for (const res of syncResult.results) {
3166
3425
  if (syncResult.incompatible.some((i) => i.agent === res.agent)) {
@@ -3168,187 +3427,21 @@ var wizardManage = async (options = {}) => {
3168
3427
  }
3169
3428
  if (res.success) {
3170
3429
  logger.success(
3171
- `${import_picocolors13.default.cyan(res.agent)}: Successfully synced to ${import_picocolors13.default.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
3430
+ `${import_picocolors15.default.cyan(res.agent)}: Successfully synced to ${import_picocolors15.default.dim(res.path)}${formatCoHostedBadge("configured", res.coConfiguredAgents)}`
3172
3431
  );
3173
3432
  targetGroup.agents.push(res.agent);
3174
3433
  } else {
3175
- logger.error(`${import_picocolors13.default.cyan(res.agent)}: Sync failed - ${res.error}`);
3434
+ logger.error(`${import_picocolors15.default.cyan(res.agent)}: Sync failed - ${res.error}`);
3176
3435
  }
3177
3436
  }
3178
- refreshGroupedServers();
3179
- }
3180
- }
3181
- };
3182
-
3183
- // src/interactive/wizard-remove.ts
3184
- var import_prompts9 = require("@inquirer/prompts");
3185
- var import_picocolors14 = __toESM(require("picocolors"), 1);
3186
- var wizardRemove = async (options = {}) => {
3187
- const cwd = options.cwd ?? process.cwd();
3188
- const isGlobal = await promptScope({
3189
- cwd,
3190
- defaultGlobal: options.global,
3191
- message: "Select scope to remove MCP server from:"
3192
- });
3193
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
3194
- if (installed.length === 0) {
3195
- logger.warn(`No installed MCP servers found in ${isGlobal ? "global" : "project"} scope`);
3196
- return false;
3197
- }
3198
- const serverMap = groupInstalledServersByName(installed);
3199
- let serverName = options.name;
3200
- if (!serverName) {
3201
- const choices = Array.from(serverMap.values()).map((g) => ({
3202
- name: `${import_picocolors14.default.bold(g.serverName)} ${import_picocolors14.default.dim(`(installed in: ${g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ")})`)}`,
3203
- value: g.serverName
3204
- }));
3205
- serverName = await (0, import_prompts9.select)({
3206
- message: "Select MCP server to remove:",
3207
- choices
3208
- });
3209
- }
3210
- const rawInstalledAgents = serverMap.get(serverName)?.agents || [];
3211
- if (rawInstalledAgents.length === 0) {
3212
- logger.warn(`No agents found with [${serverName}] installed`);
3213
- return false;
3214
- }
3215
- const installedAgents = sortAgentsWithClusters(rawInstalledAgents, { global: isGlobal, cwd });
3216
- let targetAgents = options.agents;
3217
- if (!targetAgents || targetAgents.length === 0) {
3218
- const choices = buildLinkedAgentChoices({
3219
- agents: installedAgents,
3220
- checkedAgents: installedAgents,
3221
- scopeOptions: { global: isGlobal, cwd }
3222
- });
3223
- targetAgents = await linkedCheckbox({
3224
- message: `Select agents to remove [${serverName}] from:`,
3225
- choices,
3226
- validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
3227
- });
3228
- } else {
3229
- const validAgents = targetAgents.filter((agent) => installedAgents.includes(agent));
3230
- if (validAgents.length === 0) {
3231
- logger.warn(`None of the specified agents (${targetAgents.join(", ")}) have [${serverName}] installed`);
3232
- return false;
3233
- }
3234
- targetAgents = validAgents;
3235
- }
3236
- const confirmed = await (0, import_prompts9.confirm)({
3237
- message: `Confirm removing MCP server [${serverName}] from ${targetAgents.join(", ")}?`,
3238
- default: true
3239
- });
3240
- if (!confirmed) {
3241
- logger.warn("Operation cancelled");
3242
- return false;
3243
- }
3244
- const results = removeMcpServer({
3245
- name: serverName,
3246
- agents: targetAgents,
3247
- global: isGlobal,
3248
- cwd
3249
- });
3250
- let removedCount = 0;
3251
- for (const res of results) {
3252
- if (res.removed) {
3253
- logger.success(
3254
- `${import_picocolors14.default.cyan(res.agent)}: Successfully removed from ${import_picocolors14.default.dim(res.path)}${formatCoHostedBadge("affected", res.coAffectedAgents)}`
3255
- );
3256
- removedCount++;
3257
- } else if (res.error) {
3258
- logger.error(`${import_picocolors14.default.cyan(res.agent)}: Failed to remove - ${res.error}`);
3259
- }
3260
- }
3261
- if (removedCount > 0) {
3262
- logger.success(`Successfully removed [${serverName}] from ${removedCount} agent(s)`);
3263
- return true;
3264
- }
3265
- logger.warn(`Failed to remove [${serverName}] from specified agents`);
3266
- return false;
3267
- };
3268
-
3269
- // src/interactive/main-menu.ts
3270
- var mainMenu = async () => {
3271
- console.log();
3272
- console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan("mcps - Cross-Platform MCP Manager for AI Coding Agents")));
3273
- console.log(import_picocolors15.default.dim("Cross-platform MCP server configuration & synchronization tool"));
3274
- console.log();
3275
- while (true) {
3276
- try {
3277
- const action = await (0, import_prompts10.select)({
3278
- message: "Select an action:",
3279
- choices: [
3280
- {
3281
- name: "Add MCP Server",
3282
- value: "add"
3283
- },
3284
- {
3285
- name: "Manage & Sync Installed MCP Servers",
3286
- value: "manage"
3287
- },
3288
- {
3289
- name: "Remove MCP Server",
3290
- value: "remove"
3291
- },
3292
- {
3293
- name: "Exit",
3294
- value: "exit"
3295
- }
3296
- ]
3297
- });
3298
- if (action === "exit") {
3299
- console.log(import_picocolors15.default.dim("Goodbye!"));
3300
- break;
3301
- }
3302
- if (action === "add") {
3303
- await wizardAdd();
3304
- } else if (action === "manage") {
3305
- await wizardManage();
3306
- } else if (action === "remove") {
3307
- await wizardRemove();
3308
- }
3309
- console.log();
3310
- } catch (error) {
3311
- if (error?.name === "ExitPromptError") {
3312
- console.log("\n" + import_picocolors15.default.dim("Exited."));
3313
- break;
3314
- }
3315
- throw error;
3316
- }
3317
- }
3318
- };
3319
-
3320
- // src/utils/resolve-transport.ts
3321
- var resolveTransport = (input7) => {
3322
- if (!input7) return void 0;
3323
- if (input7 === "http" || input7 === "sse") return input7;
3324
- throw new Error(`Unsupported transport "${input7}" (expected: http, sse)`);
3325
- };
3326
-
3327
- // src/cli/manage.ts
3328
- var import_commander = require("commander");
3329
- var import_picocolors16 = __toESM(require("picocolors"), 1);
3330
-
3331
- // src/utils/parse-key-value-list.ts
3332
- var parseKeyValueList = (entries, separator) => {
3333
- if (!entries || entries.length === 0) return {};
3334
- const result = {};
3335
- for (const entry of entries) {
3336
- const splitIndex = entry.indexOf(separator);
3337
- if (splitIndex === -1) {
3338
- throw new Error(`Invalid entry "${entry}": expected "${separator}" separator`);
3437
+ refreshGroupedServers();
3339
3438
  }
3340
- const key = entry.slice(0, splitIndex).trim();
3341
- const value = entry.slice(splitIndex + separator.length).trim();
3342
- if (!key) throw new Error(`Invalid entry "${entry}": empty key`);
3343
- result[key] = value;
3344
3439
  }
3345
- return result;
3346
3440
  };
3347
3441
 
3348
3442
  // src/cli/manage.ts
3349
3443
  var requireTargetServerGroup = (serverName, scope) => {
3350
- const installed = listInstalledMcpServers(scope);
3351
- const grouped = groupInstalledServersByName(installed);
3444
+ const grouped = queryGroupedInstalledServers(scope);
3352
3445
  const targetGroup = grouped.get(serverName);
3353
3446
  if (!targetGroup) {
3354
3447
  logger.error(
@@ -3359,18 +3452,13 @@ var requireTargetServerGroup = (serverName, scope) => {
3359
3452
  }
3360
3453
  return targetGroup;
3361
3454
  };
3362
- 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("--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) => {
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) => {
3363
3456
  try {
3364
3457
  const cwd = process.cwd();
3365
3458
  const isGlobal = Boolean(options.global);
3366
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;
3367
3460
  const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
3368
3461
  if (hasModifications) {
3369
- if (options.url !== void 0 && options.command !== void 0) {
3370
- logger.error('Cannot specify both "--url" (remote) and "--command" (stdio) simultaneously.');
3371
- process.exitCode = 1;
3372
- return;
3373
- }
3374
3462
  if (!serverName) {
3375
3463
  logger.error('Missing required argument: "server-name" when passing modification flags.');
3376
3464
  process.exitCode = 1;
@@ -3380,64 +3468,34 @@ var mcpManageCommand = new import_commander.Command("manage").description("Inspe
3380
3468
  if (!targetGroup) {
3381
3469
  return;
3382
3470
  }
3383
- const isCurrentRemote = Boolean(targetGroup.config.url && targetGroup.config.url.length > 0);
3384
- const willBeRemote = options.url !== void 0 ? true : options.command !== void 0 ? false : isCurrentRemote;
3385
- if (willBeRemote) {
3386
- const ignoredStdioFlags = [];
3387
- if (options.env !== void 0) ignoredStdioFlags.push("--env");
3388
- if (options.clearEnv) ignoredStdioFlags.push("--clear-env");
3389
- if (options.args !== void 0) ignoredStdioFlags.push("--args");
3390
- if (options.clearArgs) ignoredStdioFlags.push("--clear-args");
3391
- if (ignoredStdioFlags.length > 0) {
3392
- const hint = options.url !== void 0 ? "When configuring a remote server, stdio flags are ignored." : "Use --command to switch to stdio mode.";
3393
- logger.warn(
3394
- `Server "${serverName}" is a remote server. The following stdio flags will be ignored: ${ignoredStdioFlags.join(", ")}. ${hint}`
3395
- );
3396
- }
3397
- } else {
3398
- const ignoredRemoteFlags = [];
3399
- if (options.header !== void 0) ignoredRemoteFlags.push("--header");
3400
- if (options.clearHeaders) ignoredRemoteFlags.push("--clear-headers");
3401
- if (options.transport !== void 0) ignoredRemoteFlags.push("--transport");
3402
- if (ignoredRemoteFlags.length > 0) {
3403
- const hint = options.command !== void 0 ? "When configuring a stdio server, remote flags are ignored." : "Use --url to switch to remote mode.";
3404
- logger.warn(
3405
- `Server "${serverName}" is a stdio server. The following remote flags will be ignored: ${ignoredRemoteFlags.join(", ")}. ${hint}`
3406
- );
3407
- }
3408
- }
3409
- const incomingDelta = {};
3410
- if (options.command !== void 0) {
3411
- incomingDelta.command = options.command;
3412
- }
3413
- if (options.clearArgs) {
3414
- incomingDelta.args = void 0;
3415
- }
3416
- if (options.args !== void 0) {
3417
- incomingDelta.args = options.args;
3418
- }
3419
- if (options.url !== void 0) {
3420
- incomingDelta.url = options.url;
3421
- }
3422
- if (options.transport !== void 0) {
3423
- incomingDelta.type = resolveTransport(options.transport);
3424
- }
3425
- if (options.clearEnv) {
3426
- incomingDelta.env = void 0;
3427
- }
3428
- if (options.env !== void 0) {
3429
- const parsedEnv = parseKeyValueList(options.env, "=");
3430
- const baseEnv = options.clearEnv ? {} : targetGroup.config.env ?? {};
3431
- incomingDelta.env = { ...baseEnv, ...parsedEnv };
3432
- }
3433
- if (options.clearHeaders) {
3434
- incomingDelta.headers = void 0;
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;
3435
3490
  }
3436
- if (options.header !== void 0) {
3437
- const parsedHeaders = parseKeyValueList(options.header, ":");
3438
- const baseHeaders = options.clearHeaders ? {} : targetGroup.config.headers ?? {};
3439
- incomingDelta.headers = { ...baseHeaders, ...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
+ );
3440
3497
  }
3498
+ const incomingDelta = deltaResult.config;
3441
3499
  let targetAgents = targetGroup.agents;
3442
3500
  if (options.agent !== void 0) {
3443
3501
  const parsed = parseMcpAgentList(options.agent);
@@ -3522,129 +3580,96 @@ var mcpManageCommand = new import_commander.Command("manage").description("Inspe
3522
3580
  }
3523
3581
  });
3524
3582
 
3525
- // src/utils/format-agent-list.ts
3526
- 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);
3527
3586
 
3528
- // src/cli/add.ts
3529
- 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) => {
3530
- try {
3531
- const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
3532
- if (!source) {
3533
- if (isInteractive) {
3534
- const success = await wizardAdd({
3535
- name: options.name,
3536
- global: options.global,
3537
- args: options.args,
3538
- transport: resolveTransport(options.transport),
3539
- headers: parseKeyValueList(options.header, ":"),
3540
- env: parseKeyValueList(options.env, "="),
3541
- agents: options.all ? getMcpAgentTypes() : parseMcpAgentList(options.agent)
3542
- });
3543
- if (!success) process.exitCode = 1;
3544
- return;
3545
- }
3546
- logger.error('Missing required argument: "source" (e.g. mcps add @modelcontextprotocol/server-filesystem)');
3547
- process.exitCode = 1;
3548
- return;
3549
- }
3550
- const parsed = parseMcpSource(source);
3551
- const cwd = process.cwd();
3552
- const isGlobal = Boolean(options.global);
3553
- const explicitTransport = resolveTransport(options.transport);
3554
- const transport = explicitTransport ?? (parsed.type === "remote" ? "http" : "stdio");
3555
- const resolvedTargets = resolveTargetAgents({
3556
- requested: options.agent,
3557
- all: options.all,
3558
- global: isGlobal,
3559
- cwd,
3560
- transport
3561
- });
3562
- if (resolvedTargets.agents.length === 0) {
3563
- const message = resolvedTargets.diagnostic ?? `No ${isGlobal ? "global" : "project"}-installed MCP agents detected. Pass ${import_picocolors17.default.cyan("-a <agent>")} (e.g. ${import_picocolors17.default.cyan("-a cursor")}) or ${import_picocolors17.default.cyan("--all")} to install.`;
3564
- logger.warn(message);
3565
- process.exitCode = 1;
3566
- return;
3567
- }
3568
- if (resolvedTargets.isDetected) {
3569
- logger.info(
3570
- `Detected ${isGlobal ? "global" : "project"} agents: ${import_picocolors17.default.cyan(formatAgentList(resolvedTargets.detected, "(none detected)"))}`
3571
- );
3572
- if (resolvedTargets.incompatible.length > 0) {
3573
- const skippedList = resolvedTargets.incompatible.map((item) => `${item.agent} (${item.reason})`).join(", ");
3574
- logger.info(
3575
- `Skipping detected agents incompatible with ${transport}: ${import_picocolors17.default.yellow(skippedList)}`
3576
- );
3577
- }
3578
- }
3579
- const targetAgents = resolvedTargets.isDetected ? resolvedTargets.agents : resolvedTargets.allAgents;
3580
- const result = installMcpServer({
3581
- source,
3582
- name: options.name,
3583
- agents: targetAgents,
3584
- args: options.args,
3585
- global: isGlobal,
3586
- cwd,
3587
- transport: explicitTransport,
3588
- headers: parseKeyValueList(options.header, ":"),
3589
- 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
3590
3611
  });
3591
- logger.info(
3592
- `Installing ${import_picocolors17.default.bold(result.serverName)} (${import_picocolors17.default.cyan(parsed.type)}) to ${import_picocolors17.default.cyan(String(result.results.length))} agent(s)`
3593
- );
3594
- for (const record of result.results) {
3595
- if (record.success) {
3596
- logger.success(`${import_picocolors17.default.cyan(record.agent)} ${import_picocolors17.default.dim(record.path)}`);
3597
- logCoHostedNotice("configured", record.coConfiguredAgents);
3598
- } else {
3599
- logger.error(`${import_picocolors17.default.cyan(record.agent)}: ${record.error}`);
3600
- }
3601
- }
3602
- if (result.results.some((record) => !record.success)) process.exitCode = 1;
3603
- } catch (error) {
3604
- logger.error(toErrorMessage(error));
3605
- process.exitCode = 1;
3606
3612
  }
3607
- });
3608
-
3609
- // src/cli/list.ts
3610
- var import_commander3 = require("commander");
3611
- var import_picocolors18 = __toESM(require("picocolors"), 1);
3612
- 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) => {
3613
- try {
3614
- const entries = listInstalledMcpServers({
3615
- global: Boolean(options.global),
3616
- cwd: process.cwd(),
3617
- 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 }
3618
3625
  });
3619
- if (options.json) {
3620
- console.log(JSON.stringify(entries, null, 2));
3621
- return;
3622
- }
3623
- if (entries.length === 0) {
3624
- logger.warn("No MCP servers installed");
3625
- return;
3626
- }
3627
- const grouped = /* @__PURE__ */ new Map();
3628
- for (const entry of entries) {
3629
- const existing = grouped.get(entry.serverName) ?? [];
3630
- existing.push(entry);
3631
- 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;
3632
3636
  }
3633
- for (const [serverName, group] of grouped) {
3634
- const agentLabels = group.map((record) => record.agent).join(", ");
3635
- console.log(` ${import_picocolors18.default.bold(serverName)} ${import_picocolors18.default.dim(`[${agentLabels}]`)}`);
3636
- const firstPath = group[0]?.path;
3637
- if (firstPath) console.log(` ${import_picocolors18.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}`);
3638
3662
  }
3639
- } catch (error) {
3640
- logger.error(toErrorMessage(error));
3641
- process.exitCode = 1;
3642
3663
  }
3643
- });
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
+ };
3644
3671
 
3645
3672
  // src/cli/remove.ts
3646
- var import_commander4 = require("commander");
3647
- var import_picocolors19 = __toESM(require("picocolors"), 1);
3648
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) => {
3649
3674
  try {
3650
3675
  const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
@@ -3668,17 +3693,17 @@ var mcpRemoveCommand = new import_commander4.Command("remove").alias("rm").descr
3668
3693
  cwd: process.cwd()
3669
3694
  });
3670
3695
  if (results.length === 0) {
3671
- logger.warn(`No agent config contained ${import_picocolors19.default.bold(name)}`);
3696
+ logger.warn(`No agent config contained ${import_picocolors18.default.bold(name)}`);
3672
3697
  return;
3673
3698
  }
3674
3699
  for (const record of results) {
3675
3700
  if (record.removed) {
3676
3701
  logger.success(
3677
- `${import_picocolors19.default.cyan(record.agent)} removed ${import_picocolors19.default.bold(name)} ${import_picocolors19.default.dim(record.path)}`
3702
+ `${import_picocolors18.default.cyan(record.agent)} removed ${import_picocolors18.default.bold(name)} ${import_picocolors18.default.dim(record.path)}`
3678
3703
  );
3679
3704
  logCoHostedNotice("affected", record.coAffectedAgents);
3680
3705
  } else {
3681
- logger.error(`${import_picocolors19.default.cyan(record.agent)}: ${record.error ?? "not found"}`);
3706
+ logger.error(`${import_picocolors18.default.cyan(record.agent)}: ${record.error ?? "not found"}`);
3682
3707
  }
3683
3708
  }
3684
3709
  } catch (error) {
@@ -3687,8 +3712,61 @@ var mcpRemoveCommand = new import_commander4.Command("remove").alias("rm").descr
3687
3712
  }
3688
3713
  });
3689
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
+
3690
3768
  // src/cli.ts
3691
- var VERSION = "0.1.0-beta.3";
3769
+ var VERSION = "0.1.0";
3692
3770
  process.on("SIGINT", () => process.exit(0));
3693
3771
  process.on("SIGTERM", () => process.exit(0));
3694
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");