@wuyax/mcps 0.1.0-beta.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,73 +32,47 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AgentConfigStore: () => AgentConfigStore,
34
34
  DEFAULT_REMOTE_TRANSPORT: () => DEFAULT_REMOTE_TRANSPORT,
35
- NPX_COMMAND: () => NPX_COMMAND,
36
- NPX_DASH_Y: () => NPX_DASH_Y,
37
- add: () => installMcpServer,
35
+ SECRET_HEADER_PATTERN: () => SECRET_HEADER_PATTERN,
36
+ SECRET_KEY_PATTERN: () => SECRET_KEY_PATTERN,
38
37
  agentConfigStore: () => agentConfigStore,
38
+ applyServerConfigDelta: () => applyServerConfigDelta,
39
39
  buildMcpServerConfig: () => buildMcpServerConfig,
40
40
  createAgentTransform: () => createAgentTransform,
41
41
  detectGloballyInstalledMcpAgents: () => detectGloballyInstalledMcpAgents,
42
42
  detectProjectInstalledMcpAgents: () => detectProjectInstalledMcpAgents,
43
- displayServerDetails: () => displayServerDetails,
43
+ detectUpdateTransition: () => detectUpdateTransition,
44
44
  extractPackageName: () => extractPackageName,
45
- formatArgsString: () => formatArgsString,
46
- formatEnvText: () => formatEnvText,
47
- formatHeadersText: () => formatHeadersText,
45
+ getCandidateAgentsForScope: () => getCandidateAgentsForScope,
46
+ getCoHostedAgents: () => getCoHostedAgents,
48
47
  getMcpAgentConfig: () => getMcpAgentConfig,
49
48
  getMcpAgentTypes: () => getMcpAgentTypes,
50
49
  getMcpAgentsSupportingProjectScope: () => getMcpAgentsSupportingProjectScope,
51
- groupInstalledServersByName: () => groupInstalledServersByName,
52
- install: () => installMcpServer,
53
50
  installMcpServer: () => installMcpServer,
54
51
  installMcpServerForAgent: () => installMcpServerForAgent,
55
52
  installMcpServerForAgents: () => installMcpServerForAgents,
53
+ installToCompatibleAgents: () => installToCompatibleAgents,
56
54
  isMcpAgentType: () => isMcpAgentType,
57
55
  isMcpTransportSupported: () => isMcpTransportSupported,
58
56
  isRemoteMcpSource: () => isRemoteMcpSource,
59
57
  isRemoteServerConfig: () => isRemoteServerConfig,
60
58
  isStdioServerConfig: () => isStdioServerConfig,
61
- list: () => listInstalledMcpServers,
62
59
  listInstalledMcpServers: () => listInstalledMcpServers,
63
- listServersInConfigFile: () => listServersInConfigFile,
64
- mainMenu: () => mainMenu,
65
60
  maskSecretHeader: () => maskSecretHeader,
66
61
  maskSecretValue: () => maskSecretValue,
67
- mcpAgentAliases: () => mcpAgentAliases,
68
- mcpAgents: () => mcpAgents,
69
- mcpManageCommand: () => mcpManageCommand,
70
- normalizeServerConfig: () => normalizeServerConfig,
71
- parseArgsString: () => parseArgsString,
72
- parseEnvText: () => parseEnvText,
73
- parseHeadersText: () => parseHeadersText,
74
62
  parseMcpSource: () => parseMcpSource,
75
63
  parseServerConfig: () => parseServerConfig,
76
- parseSource: () => parseMcpSource,
77
- promptArgsConfig: () => promptArgsConfig,
78
- promptEditArgs: () => promptEditArgs,
79
- promptEditEnvConfig: () => promptEditEnvConfig,
80
- promptEditHeadersConfig: () => promptEditHeadersConfig,
81
- promptEditKeyValueConfig: () => promptEditKeyValueConfig,
82
- promptEnvConfig: () => promptEnvConfig,
83
- promptHeadersConfig: () => promptHeadersConfig,
84
- promptScope: () => promptScope,
85
- promptScopeAndAgents: () => promptScopeAndAgents,
86
- readConfigFile: () => readConfigFile,
87
- remove: () => removeMcpServer,
64
+ queryGroupedInstalledServers: () => queryGroupedInstalledServers,
88
65
  removeMcpServer: () => removeMcpServer,
89
- removeMcpServerFromAgent: () => removeMcpServerFromAgent,
90
- removeServerFromConfigFile: () => removeServerFromConfigFile,
66
+ resolveConfigClusters: () => resolveConfigClusters,
91
67
  resolveMcpAgentAlias: () => resolveMcpAgentAlias,
92
68
  resolveMcpConfigTarget: () => resolveMcpConfigTarget,
93
69
  resolveTargetAgents: () => resolveTargetAgents,
94
- transformServerConfig: () => transformServerConfig,
70
+ sanitizeUpdatedServerConfig: () => sanitizeUpdatedServerConfig,
71
+ sortAgentsByClusters: () => sortAgentsByClusters,
72
+ toRemoteServerConfig: () => toRemoteServerConfig,
73
+ toStdioServerConfig: () => toStdioServerConfig,
95
74
  transformServerConfigForAgent: () => transformServerConfigForAgent,
96
- updateMcpServerForAgent: () => installMcpServerForAgent,
97
- updateMcpServerForAgents: () => installMcpServerForAgents,
98
- wizardAdd: () => wizardAdd,
99
- wizardManage: () => wizardManage,
100
- wizardRemove: () => wizardRemove,
101
- writeServerToConfigFile: () => writeServerToConfigFile
75
+ updateMcpServer: () => updateMcpServer
102
76
  });
103
77
  module.exports = __toCommonJS(src_exports);
104
78
 
@@ -556,9 +530,9 @@ var mcpAgentAliases = {
556
530
  var getMcpAgentConfig = (agentType) => mcpAgents[agentType];
557
531
  var getMcpAgentTypes = () => Object.values(mcpAgents).map((config) => config.name);
558
532
  var isMcpAgentType = (value) => value in mcpAgents;
559
- var resolveMcpAgentAlias = (input7) => {
560
- if (isMcpAgentType(input7)) return input7;
561
- return mcpAgentAliases[input7] ?? null;
533
+ var resolveMcpAgentAlias = (input) => {
534
+ if (isMcpAgentType(input)) return input;
535
+ return mcpAgentAliases[input] ?? null;
562
536
  };
563
537
  var isMcpTransportSupported = (agent, transport) => agent.supportedTransports.includes(transport);
564
538
  var detectProjectInstalledMcpAgents = (cwd) => getMcpAgentTypes().filter(
@@ -610,7 +584,9 @@ var KNOWN_COMMAND_RUNNERS = /* @__PURE__ */ new Set([
610
584
  ]);
611
585
  var SCRIPT_EXTENSION_REGEX = /\.(?:js|ts|mjs|cjs|py|sh|rb|go)$/i;
612
586
 
613
- // src/build-server-config.ts
587
+ // src/server-config.ts
588
+ var isRemoteServerConfig = (config) => typeof config.url === "string" && config.url.length > 0;
589
+ var isStdioServerConfig = (config) => typeof config.command === "string" && config.command.length > 0;
614
590
  var buildMcpServerConfig = (parsed, options = {}) => {
615
591
  if (parsed.type === "remote") {
616
592
  const config2 = {
@@ -648,10 +624,6 @@ var buildMcpServerConfig = (parsed, options = {}) => {
648
624
  }
649
625
  return config;
650
626
  };
651
-
652
- // src/parse-server-config.ts
653
- var isRemoteServerConfig = (config) => typeof config.url === "string" && config.url.length > 0;
654
- var isStdioServerConfig = (config) => typeof config.command === "string" && config.command.length > 0;
655
627
  var parseServerConfig = (raw) => {
656
628
  if (!raw || typeof raw !== "object") return {};
657
629
  const data = raw;
@@ -678,6 +650,237 @@ var parseServerConfig = (raw) => {
678
650
  }
679
651
  return {};
680
652
  };
653
+ var toRemoteServerConfig = (config, defaultTransport = "http") => {
654
+ const {
655
+ command: _droppedCommand,
656
+ args: _droppedArgs,
657
+ env: _droppedEnv,
658
+ ...remoteConfig
659
+ } = config;
660
+ return {
661
+ ...remoteConfig,
662
+ type: remoteConfig.type ?? defaultTransport
663
+ };
664
+ };
665
+ var toStdioServerConfig = (config) => {
666
+ const {
667
+ url: _droppedUrl,
668
+ type: _droppedType,
669
+ headers: _droppedHeaders,
670
+ ...stdioConfig
671
+ } = config;
672
+ return stdioConfig;
673
+ };
674
+ var detectUpdateTransition = (incoming, previous) => {
675
+ if (!previous) {
676
+ return incoming.url ? "switch-to-remote" : "switch-to-stdio";
677
+ }
678
+ const previousIsRemote = Boolean(previous.url && previous.url.length > 0);
679
+ if (previousIsRemote) {
680
+ if (incoming.command && !incoming.url) {
681
+ return "switch-to-stdio";
682
+ }
683
+ return "merge-remote";
684
+ }
685
+ if (incoming.url && !incoming.command) {
686
+ return "switch-to-remote";
687
+ }
688
+ return "merge-stdio";
689
+ };
690
+ var sanitizeUpdatedServerConfig = (incoming, previous) => {
691
+ const transition = detectUpdateTransition(incoming, previous);
692
+ const targetTransport = incoming.type ?? previous?.type ?? "http";
693
+ switch (transition) {
694
+ case "switch-to-remote": {
695
+ const cleanBase = previous ? toRemoteServerConfig(previous, targetTransport) : {};
696
+ return toRemoteServerConfig({ ...cleanBase, ...incoming }, targetTransport);
697
+ }
698
+ case "switch-to-stdio": {
699
+ const cleanBase = previous ? toStdioServerConfig(previous) : {};
700
+ return toStdioServerConfig({ ...cleanBase, ...incoming });
701
+ }
702
+ case "merge-remote": {
703
+ return toRemoteServerConfig({ ...previous, ...incoming }, targetTransport);
704
+ }
705
+ case "merge-stdio":
706
+ default: {
707
+ return toStdioServerConfig({ ...previous, ...incoming });
708
+ }
709
+ }
710
+ };
711
+ var applyServerConfigDelta = (previousConfig, options) => {
712
+ if (options.url !== void 0 && options.command !== void 0) {
713
+ throw new Error('Cannot specify both "--url" (remote) and "--command" (stdio) simultaneously.');
714
+ }
715
+ const isCurrentRemote = Boolean(previousConfig?.url && previousConfig.url.length > 0);
716
+ const willBeRemote = options.url !== void 0 ? true : options.command !== void 0 ? false : isCurrentRemote;
717
+ const protocol = willBeRemote ? "remote" : "stdio";
718
+ const ignoredFlags = [];
719
+ if (willBeRemote) {
720
+ if (options.env !== void 0) ignoredFlags.push("--env");
721
+ if (options.clearEnv) ignoredFlags.push("--clear-env");
722
+ if (options.args !== void 0) ignoredFlags.push("--args");
723
+ if (options.clearArgs) ignoredFlags.push("--clear-args");
724
+ } else {
725
+ if (options.headers !== void 0) ignoredFlags.push("--header");
726
+ if (options.clearHeaders) ignoredFlags.push("--clear-headers");
727
+ if (options.transport !== void 0) ignoredFlags.push("--transport");
728
+ }
729
+ const incomingDelta = {};
730
+ if (options.command !== void 0) {
731
+ incomingDelta.command = options.command;
732
+ }
733
+ if (options.clearArgs) {
734
+ incomingDelta.args = void 0;
735
+ }
736
+ if (options.args !== void 0) {
737
+ incomingDelta.args = options.args;
738
+ }
739
+ if (options.url !== void 0) {
740
+ incomingDelta.url = options.url;
741
+ }
742
+ if (options.transport !== void 0) {
743
+ incomingDelta.type = options.transport;
744
+ }
745
+ if (options.clearEnv) {
746
+ incomingDelta.env = void 0;
747
+ }
748
+ if (options.env !== void 0) {
749
+ const baseEnv = options.clearEnv ? {} : previousConfig?.env ?? {};
750
+ incomingDelta.env = { ...baseEnv, ...options.env };
751
+ }
752
+ if (options.clearHeaders) {
753
+ incomingDelta.headers = void 0;
754
+ }
755
+ if (options.headers !== void 0) {
756
+ const baseHeaders = options.clearHeaders ? {} : previousConfig?.headers ?? {};
757
+ incomingDelta.headers = { ...baseHeaders, ...options.headers };
758
+ }
759
+ const transition = detectUpdateTransition(incomingDelta, previousConfig);
760
+ const cleanConfig = sanitizeUpdatedServerConfig(incomingDelta, previousConfig);
761
+ return {
762
+ config: cleanConfig,
763
+ transition,
764
+ protocol,
765
+ ignoredFlags
766
+ };
767
+ };
768
+
769
+ // src/source-parser.ts
770
+ var REMOTE_URL_REGEX = /^https?:\/\//i;
771
+ var HAS_WHITESPACE_REGEX = /\s/;
772
+ var PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(?:@[^\s]+)?$/;
773
+ var PATH_SEPARATOR_REGEX = /[/\\]/;
774
+ var stripVersionSuffix = (input) => {
775
+ if (input.startsWith("@")) {
776
+ const secondAtIndex = input.indexOf("@", 1);
777
+ if (secondAtIndex > 0) return input.slice(0, secondAtIndex);
778
+ return input;
779
+ }
780
+ const atIndex = input.lastIndexOf("@");
781
+ if (atIndex > 0) return input.slice(0, atIndex);
782
+ return input;
783
+ };
784
+ var stripScopePrefix = (input) => {
785
+ if (!input.startsWith("@") || !input.includes("/")) return input;
786
+ const parts = input.split("/");
787
+ return parts[1] || input;
788
+ };
789
+ var stripPathPrefix = (input) => {
790
+ if (!PATH_SEPARATOR_REGEX.test(input)) return input;
791
+ const segments = input.split(PATH_SEPARATOR_REGEX);
792
+ const basename = segments[segments.length - 1];
793
+ return basename || input;
794
+ };
795
+ var extractPackageName = (input) => {
796
+ let name = stripVersionSuffix(input);
797
+ name = stripScopePrefix(name);
798
+ name = stripPathPrefix(name);
799
+ name = name.replace(SCRIPT_EXTENSION_REGEX, "");
800
+ for (const prefix of PACKAGE_NAME_PREFIX_STRIP) {
801
+ if (name.startsWith(prefix)) {
802
+ name = name.slice(prefix.length);
803
+ break;
804
+ }
805
+ }
806
+ for (const suffix of PACKAGE_NAME_SUFFIX_STRIP) {
807
+ if (name.endsWith(suffix)) {
808
+ name = name.slice(0, -suffix.length);
809
+ break;
810
+ }
811
+ }
812
+ return name || MCP_DEFAULT_SERVER_NAME;
813
+ };
814
+ var inferNameFromUrl = (input) => {
815
+ try {
816
+ const url = new URL(input);
817
+ const host = url.hostname;
818
+ const labels = host.split(".").filter((segment) => segment.length > 0);
819
+ if (labels.length === 0) return MCP_DEFAULT_SERVER_NAME;
820
+ const meaningfulLabels = labels.filter((label) => {
821
+ const lower = label.toLowerCase();
822
+ if (COMMON_TLD_LABELS.has(lower)) return false;
823
+ if (GENERIC_HOST_PREFIXES.has(lower)) return false;
824
+ return true;
825
+ });
826
+ if (meaningfulLabels.length > 0) return meaningfulLabels[0];
827
+ if (labels.length >= 2) return labels[labels.length - 2];
828
+ return labels[labels.length - 1] || MCP_DEFAULT_SERVER_NAME;
829
+ } catch {
830
+ return MCP_DEFAULT_SERVER_NAME;
831
+ }
832
+ };
833
+ var inferNameFromCommand = (command) => {
834
+ const tokens = command.trim().split(/\s+/);
835
+ const runnerBase = tokens[0]?.split(PATH_SEPARATOR_REGEX).pop() ?? "";
836
+ const startIndex = KNOWN_COMMAND_RUNNERS.has(runnerBase) ? 1 : 0;
837
+ for (let tokenIndex = startIndex; tokenIndex < tokens.length; tokenIndex += 1) {
838
+ const token = tokens[tokenIndex];
839
+ if (!token || token.startsWith("-")) continue;
840
+ return extractPackageName(token);
841
+ }
842
+ const firstNonFlag = tokens.find((token) => !token.startsWith("-"));
843
+ return firstNonFlag ? extractPackageName(firstNonFlag) : MCP_DEFAULT_SERVER_NAME;
844
+ };
845
+ var parseMcpSource = (input) => {
846
+ const trimmed = input.trim();
847
+ if (trimmed.length === 0) {
848
+ throw new Error(
849
+ "Invalid MCP source: input is empty. Expected a remote URL, an npm package, or a command line."
850
+ );
851
+ }
852
+ if (REMOTE_URL_REGEX.test(trimmed)) {
853
+ return {
854
+ type: "remote",
855
+ value: trimmed,
856
+ inferredName: inferNameFromUrl(trimmed)
857
+ };
858
+ }
859
+ if (HAS_WHITESPACE_REGEX.test(trimmed)) {
860
+ return {
861
+ type: "command",
862
+ value: trimmed,
863
+ inferredName: inferNameFromCommand(trimmed)
864
+ };
865
+ }
866
+ if (PACKAGE_NAME_REGEX.test(trimmed)) {
867
+ return {
868
+ type: "package",
869
+ value: trimmed,
870
+ inferredName: extractPackageName(trimmed)
871
+ };
872
+ }
873
+ return {
874
+ type: "command",
875
+ value: trimmed,
876
+ inferredName: inferNameFromCommand(trimmed)
877
+ };
878
+ };
879
+ var isRemoteMcpSource = (parsed) => parsed.type === "remote";
880
+
881
+ // src/config-store.ts
882
+ var import_node_fs6 = require("fs");
883
+ var import_node_path3 = require("path");
681
884
 
682
885
  // src/utils/is-plain-object.ts
683
886
  var isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
@@ -962,113 +1165,6 @@ var listServersInConfigFile = (filePath, format, dottedKey) => {
962
1165
  return isPlainObject(entries) ? entries : {};
963
1166
  };
964
1167
 
965
- // src/config-store.ts
966
- var import_node_fs6 = require("fs");
967
-
968
- // src/resolve-config-target.ts
969
- var import_node_path3 = require("path");
970
- var resolveMcpConfigTarget = (agent, options = {}) => {
971
- const isGlobal = options.global ?? false;
972
- const cwd = options.cwd ?? process.cwd();
973
- const configPath = agent.resolveConfigPath ? agent.resolveConfigPath({ global: isGlobal, cwd }) : !isGlobal && agent.projectConfigPath ? (0, import_node_path3.join)(cwd, agent.projectConfigPath) : agent.globalConfigPath;
974
- const configKey = !isGlobal && agent.projectConfigKey ? agent.projectConfigKey : agent.configKey;
975
- return { configPath, configKey };
976
- };
977
-
978
- // src/config-store.ts
979
- var FsConfigStoreAdapter = class {
980
- exists(filePath) {
981
- return (0, import_node_fs6.existsSync)(filePath);
982
- }
983
- read(target) {
984
- return readConfigFile(target.filePath, target.format);
985
- }
986
- writeServer(target, serverName, serverConfig) {
987
- if (!target.dottedKey) {
988
- throw new Error(`Cannot write server: missing dottedKey for ${target.filePath}`);
989
- }
990
- writeServerToConfigFile(
991
- target.filePath,
992
- target.format,
993
- target.dottedKey,
994
- serverName,
995
- serverConfig
996
- );
997
- }
998
- removeServer(target, serverName) {
999
- if (!target.dottedKey) return false;
1000
- return removeServerFromConfigFile(
1001
- target.filePath,
1002
- target.format,
1003
- target.dottedKey,
1004
- serverName
1005
- );
1006
- }
1007
- listServers(target) {
1008
- if (!target.dottedKey) return {};
1009
- return listServersInConfigFile(target.filePath, target.format, target.dottedKey);
1010
- }
1011
- };
1012
- var AgentConfigStore = class {
1013
- constructor(adapter = new FsConfigStoreAdapter()) {
1014
- this.adapter = adapter;
1015
- }
1016
- adapter;
1017
- getAdapter() {
1018
- return this.adapter;
1019
- }
1020
- resolveTarget(agent, options = {}) {
1021
- const agentConfig = typeof agent === "string" ? getMcpAgentConfig(agent) : agent;
1022
- const target = resolveMcpConfigTarget(agentConfig, options);
1023
- return { agent: agentConfig, target };
1024
- }
1025
- resolveDescriptor(agent, options = {}) {
1026
- const { agent: agentConfig, target } = this.resolveTarget(agent, options);
1027
- return {
1028
- filePath: target.configPath,
1029
- format: agentConfig.format,
1030
- dottedKey: target.configKey
1031
- };
1032
- }
1033
- writeServer(agent, serverName, serverConfig, options = {}) {
1034
- const descriptor = this.resolveDescriptor(agent, options);
1035
- this.adapter.writeServer(descriptor, serverName, serverConfig);
1036
- return { path: descriptor.filePath };
1037
- }
1038
- removeServer(agent, serverName, options = {}) {
1039
- const descriptor = this.resolveDescriptor(agent, options);
1040
- if (!this.adapter.exists(descriptor.filePath)) {
1041
- return { path: descriptor.filePath, removed: false };
1042
- }
1043
- const removed = this.adapter.removeServer(descriptor, serverName);
1044
- return { path: descriptor.filePath, removed };
1045
- }
1046
- listServers(agent, options = {}) {
1047
- const descriptor = this.resolveDescriptor(agent, options);
1048
- if (!this.adapter.exists(descriptor.filePath)) {
1049
- return { path: descriptor.filePath, exists: false, servers: {} };
1050
- }
1051
- const servers = this.adapter.listServers(descriptor);
1052
- return { path: descriptor.filePath, exists: true, servers };
1053
- }
1054
- read(agent, options = {}) {
1055
- const descriptor = this.resolveDescriptor(agent, options);
1056
- if (!this.adapter.exists(descriptor.filePath)) {
1057
- return {};
1058
- }
1059
- return this.adapter.read(descriptor);
1060
- }
1061
- readServer(agent, serverName, options = {}) {
1062
- const { exists, servers } = this.listServers(agent, options);
1063
- if (!exists) return void 0;
1064
- return servers[serverName];
1065
- }
1066
- };
1067
- var agentConfigStore = new AgentConfigStore();
1068
-
1069
- // src/utils/to-error-message.ts
1070
- var toErrorMessage = (error, fallback = "Unknown error") => error instanceof Error ? error.message : fallback;
1071
-
1072
1168
  // src/transforms/index.ts
1073
1169
  var DIALECT_PRESETS = {
1074
1170
  vscode: {
@@ -1279,47 +1375,314 @@ var transformServerConfigForAgent = (agent, serverName, config, context = { glob
1279
1375
  return config;
1280
1376
  };
1281
1377
 
1282
- // src/installer.ts
1283
- var installMcpServerForAgent = (serverName, serverConfig, agentType, options = {}) => {
1284
- const agent = getMcpAgentConfig(agentType);
1378
+ // src/utils/to-error-message.ts
1379
+ var toErrorMessage = (error, fallback = "Unknown error") => error instanceof Error ? error.message : fallback;
1380
+
1381
+ // src/config-store.ts
1382
+ var resolveMcpConfigTarget = (agent, options = {}) => {
1285
1383
  const isGlobal = options.global ?? false;
1286
- const { target } = agentConfigStore.resolveTarget(agent, options);
1287
- try {
1288
- const transformed = transformServerConfigForAgent(agent, serverName, serverConfig, {
1289
- global: isGlobal
1290
- });
1291
- agentConfigStore.writeServer(agent, serverName, transformed, options);
1292
- return { agent: agentType, success: true, path: target.configPath };
1293
- } catch (error) {
1294
- return {
1295
- agent: agentType,
1296
- success: false,
1297
- path: target.configPath,
1298
- error: toErrorMessage(error)
1299
- };
1300
- }
1384
+ const cwd = options.cwd ?? process.cwd();
1385
+ const configPath = agent.resolveConfigPath ? agent.resolveConfigPath({ global: isGlobal, cwd }) : !isGlobal && agent.projectConfigPath ? (0, import_node_path3.join)(cwd, agent.projectConfigPath) : agent.globalConfigPath;
1386
+ const configKey = !isGlobal && agent.projectConfigKey ? agent.projectConfigKey : agent.configKey;
1387
+ return { configPath, configKey };
1301
1388
  };
1302
- var installMcpServerForAgents = (serverName, serverConfig, agentTypes, options = {}) => agentTypes.map(
1303
- (agentType) => installMcpServerForAgent(serverName, serverConfig, agentType, options)
1304
- );
1305
-
1306
- // src/utils/parse-mcp-agent-list.ts
1307
- var parseMcpAgentList = (input7) => {
1308
- if (!input7 || input7.length === 0) return void 0;
1309
- if (input7.includes("*")) return getMcpAgentTypes();
1310
- const resolved = [];
1311
- for (const value of input7) {
1312
- const agentType = resolveMcpAgentAlias(value);
1313
- if (!agentType) throw new Error(`Unknown MCP agent "${value}"`);
1314
- resolved.push(agentType);
1315
- }
1316
- return resolved;
1389
+ var getCandidateAgentsForScope = (options = {}) => {
1390
+ return options.global ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
1317
1391
  };
1318
-
1319
- // src/resolve-target-agents.ts
1320
- var normalizeRequestedAgents = (input7) => {
1321
- if (!input7 || input7.length === 0) return void 0;
1322
- const rawList = [...input7];
1392
+ var FsConfigStoreAdapter = class {
1393
+ exists(filePath) {
1394
+ return (0, import_node_fs6.existsSync)(filePath);
1395
+ }
1396
+ read(target) {
1397
+ return readConfigFile(target.filePath, target.format);
1398
+ }
1399
+ writeServer(target, serverName, serverConfig) {
1400
+ if (!target.dottedKey) {
1401
+ throw new Error(`Cannot write server: missing dottedKey for ${target.filePath}`);
1402
+ }
1403
+ writeServerToConfigFile(
1404
+ target.filePath,
1405
+ target.format,
1406
+ target.dottedKey,
1407
+ serverName,
1408
+ serverConfig
1409
+ );
1410
+ }
1411
+ removeServer(target, serverName) {
1412
+ if (!target.dottedKey) return false;
1413
+ return removeServerFromConfigFile(
1414
+ target.filePath,
1415
+ target.format,
1416
+ target.dottedKey,
1417
+ serverName
1418
+ );
1419
+ }
1420
+ listServers(target) {
1421
+ if (!target.dottedKey) return {};
1422
+ return listServersInConfigFile(target.filePath, target.format, target.dottedKey);
1423
+ }
1424
+ };
1425
+ var AgentConfigStore = class {
1426
+ constructor(adapter = new FsConfigStoreAdapter()) {
1427
+ this.adapter = adapter;
1428
+ }
1429
+ adapter;
1430
+ getAdapter() {
1431
+ return this.adapter;
1432
+ }
1433
+ // ---- Single-agent primitives ----
1434
+ resolveTarget(agent, options = {}) {
1435
+ const agentConfig = typeof agent === "string" ? getMcpAgentConfig(agent) : agent;
1436
+ const target = resolveMcpConfigTarget(agentConfig, options);
1437
+ return { agent: agentConfig, target };
1438
+ }
1439
+ resolveDescriptor(agent, options = {}) {
1440
+ const { agent: agentConfig, target } = this.resolveTarget(agent, options);
1441
+ return {
1442
+ filePath: target.configPath,
1443
+ format: agentConfig.format,
1444
+ dottedKey: target.configKey
1445
+ };
1446
+ }
1447
+ writeServer(agent, serverName, serverConfig, options = {}) {
1448
+ const descriptor = this.resolveDescriptor(agent, options);
1449
+ this.adapter.writeServer(descriptor, serverName, serverConfig);
1450
+ return { path: descriptor.filePath };
1451
+ }
1452
+ removeServer(agent, serverName, options = {}) {
1453
+ const descriptor = this.resolveDescriptor(agent, options);
1454
+ if (!this.adapter.exists(descriptor.filePath)) {
1455
+ return { path: descriptor.filePath, removed: false };
1456
+ }
1457
+ const removed = this.adapter.removeServer(descriptor, serverName);
1458
+ return { path: descriptor.filePath, removed };
1459
+ }
1460
+ listServers(agent, options = {}) {
1461
+ const descriptor = this.resolveDescriptor(agent, options);
1462
+ if (!this.adapter.exists(descriptor.filePath)) {
1463
+ return { path: descriptor.filePath, exists: false, servers: {} };
1464
+ }
1465
+ const servers = this.adapter.listServers(descriptor);
1466
+ return { path: descriptor.filePath, exists: true, servers };
1467
+ }
1468
+ /**
1469
+ * Batch lists servers for multiple agents, caching adapter reads for co-hosted
1470
+ * agents that share the exact same physical configuration file and dotted key.
1471
+ */
1472
+ listServersForAgents(agents, options = {}) {
1473
+ const readCache = /* @__PURE__ */ new Map();
1474
+ return agents.map((agentType) => {
1475
+ const descriptor = this.resolveDescriptor(agentType, options);
1476
+ const cacheKey = `${descriptor.filePath}::${descriptor.dottedKey ?? ""}`;
1477
+ let cached = readCache.get(cacheKey);
1478
+ if (!cached) {
1479
+ cached = this.listServers(agentType, options);
1480
+ readCache.set(cacheKey, cached);
1481
+ }
1482
+ return {
1483
+ agent: agentType,
1484
+ path: cached.path,
1485
+ exists: cached.exists,
1486
+ servers: cached.servers
1487
+ };
1488
+ });
1489
+ }
1490
+ read(agent, options = {}) {
1491
+ const descriptor = this.resolveDescriptor(agent, options);
1492
+ if (!this.adapter.exists(descriptor.filePath)) {
1493
+ return {};
1494
+ }
1495
+ return this.adapter.read(descriptor);
1496
+ }
1497
+ readServer(agent, serverName, options = {}) {
1498
+ const { exists, servers } = this.listServers(agent, options);
1499
+ if (!exists) return void 0;
1500
+ return servers[serverName];
1501
+ }
1502
+ // ---- Cluster & co-hosted awareness ----
1503
+ /**
1504
+ * Returns all other agents sharing the exact same physical configuration target
1505
+ * (same configPath and configKey) for the given scope.
1506
+ */
1507
+ getCoHostedAgents(agentType, options = {}) {
1508
+ const currentAgent = getMcpAgentConfig(agentType);
1509
+ const currentTarget = resolveMcpConfigTarget(currentAgent, options);
1510
+ const candidates = getCandidateAgentsForScope(options);
1511
+ const coHosted = [];
1512
+ for (const candidateType of candidates) {
1513
+ if (candidateType === agentType) continue;
1514
+ const candidateConfig = getMcpAgentConfig(candidateType);
1515
+ const candidateTarget = resolveMcpConfigTarget(candidateConfig, options);
1516
+ if (candidateTarget.configPath === currentTarget.configPath && candidateTarget.configKey === currentTarget.configKey) {
1517
+ coHosted.push(candidateType);
1518
+ }
1519
+ }
1520
+ return coHosted;
1521
+ }
1522
+ /**
1523
+ * Resolves configuration clusters for a given list of requested agents.
1524
+ * Groups requested agents sharing the same physical config path, and tracks
1525
+ * any remaining co-hosted agents sharing the same target that were not in the request.
1526
+ */
1527
+ resolveConfigClusters(agentTypes, options = {}) {
1528
+ const clustersByPath = /* @__PURE__ */ new Map();
1529
+ for (const agentType of agentTypes) {
1530
+ const agentConfig = getMcpAgentConfig(agentType);
1531
+ const target = resolveMcpConfigTarget(agentConfig, options);
1532
+ let keyMap = clustersByPath.get(target.configPath);
1533
+ if (!keyMap) {
1534
+ keyMap = /* @__PURE__ */ new Map();
1535
+ clustersByPath.set(target.configPath, keyMap);
1536
+ }
1537
+ let cluster = keyMap.get(target.configKey);
1538
+ if (!cluster) {
1539
+ const allCoHosted = this.getCoHostedAgents(agentType, options);
1540
+ cluster = {
1541
+ configPath: target.configPath,
1542
+ configKey: target.configKey,
1543
+ targetAgents: [],
1544
+ coHostedAgents: allCoHosted
1545
+ };
1546
+ keyMap.set(target.configKey, cluster);
1547
+ }
1548
+ if (!cluster.targetAgents.includes(agentType)) {
1549
+ cluster.targetAgents.push(agentType);
1550
+ }
1551
+ }
1552
+ const clusters = [];
1553
+ for (const keyMap of clustersByPath.values()) {
1554
+ for (const cluster of keyMap.values()) {
1555
+ cluster.coHostedAgents = cluster.coHostedAgents.filter(
1556
+ (co) => !cluster.targetAgents.includes(co)
1557
+ );
1558
+ clusters.push(cluster);
1559
+ }
1560
+ }
1561
+ return clusters;
1562
+ }
1563
+ /**
1564
+ * Sorts agent types so that agents sharing the same physical config target
1565
+ * appear adjacent to each other in the returned array.
1566
+ */
1567
+ sortAgentsByClusters(agentTypes, options = {}) {
1568
+ const clusters = this.resolveConfigClusters(agentTypes, options);
1569
+ const sorted = [];
1570
+ for (const cluster of clusters) {
1571
+ for (const agent of cluster.targetAgents) {
1572
+ if (!sorted.includes(agent)) {
1573
+ sorted.push(agent);
1574
+ }
1575
+ }
1576
+ }
1577
+ return sorted;
1578
+ }
1579
+ /**
1580
+ * Alias for sortAgentsByClusters for backward compatibility.
1581
+ */
1582
+ sortAgentsWithClusters(agentTypes, options = {}) {
1583
+ return this.sortAgentsByClusters(agentTypes, options);
1584
+ }
1585
+ // ---- Batch operations with automatic clustering & dialect transforms ----
1586
+ /**
1587
+ * Batch write: transforms and writes a server config to multiple agents,
1588
+ * with automatic cluster deduplication, dialect transform, and co-hosted awareness.
1589
+ * Callers pass standard McpServerConfig; the Store applies per-Agent dialect transforms
1590
+ * internally before persisting.
1591
+ */
1592
+ writeServers(agents, serverName, serverConfig, options = {}) {
1593
+ const clusters = this.resolveConfigClusters(agents, options);
1594
+ const resultsByAgent = /* @__PURE__ */ new Map();
1595
+ const isGlobal = options.global ?? false;
1596
+ for (const cluster of clusters) {
1597
+ const primaryAgentType = cluster.targetAgents[0];
1598
+ const primaryAgent = getMcpAgentConfig(primaryAgentType);
1599
+ try {
1600
+ const transformed = transformServerConfigForAgent(primaryAgent, serverName, serverConfig, {
1601
+ global: isGlobal
1602
+ });
1603
+ const descriptor = this.resolveDescriptor(primaryAgent, options);
1604
+ this.adapter.writeServer(descriptor, serverName, transformed);
1605
+ for (const agentType of cluster.targetAgents) {
1606
+ resultsByAgent.set(agentType, {
1607
+ agent: agentType,
1608
+ success: true,
1609
+ path: cluster.configPath,
1610
+ coConfiguredAgents: cluster.coHostedAgents.length > 0 ? cluster.coHostedAgents : void 0
1611
+ });
1612
+ }
1613
+ } catch (error) {
1614
+ const errorMsg = toErrorMessage(error);
1615
+ for (const agentType of cluster.targetAgents) {
1616
+ resultsByAgent.set(agentType, {
1617
+ agent: agentType,
1618
+ success: false,
1619
+ path: cluster.configPath,
1620
+ error: errorMsg
1621
+ });
1622
+ }
1623
+ }
1624
+ }
1625
+ return agents.map((agentType) => resultsByAgent.get(agentType));
1626
+ }
1627
+ /**
1628
+ * Batch remove: removes a server from multiple agents' configs,
1629
+ * with automatic cluster deduplication and co-hosted awareness.
1630
+ */
1631
+ removeServers(agents, serverName, options = {}) {
1632
+ const clusters = this.resolveConfigClusters(agents, options);
1633
+ const resultsByAgent = /* @__PURE__ */ new Map();
1634
+ for (const cluster of clusters) {
1635
+ const primaryAgentType = cluster.targetAgents[0];
1636
+ const primaryAgent = getMcpAgentConfig(primaryAgentType);
1637
+ try {
1638
+ const { removed } = this.removeServer(primaryAgent, serverName, options);
1639
+ for (const agentType of cluster.targetAgents) {
1640
+ resultsByAgent.set(agentType, {
1641
+ agent: agentType,
1642
+ path: cluster.configPath,
1643
+ removed,
1644
+ coAffectedAgents: removed && cluster.coHostedAgents.length > 0 ? cluster.coHostedAgents : void 0
1645
+ });
1646
+ }
1647
+ } catch (error) {
1648
+ const errorMsg = toErrorMessage(error);
1649
+ for (const agentType of cluster.targetAgents) {
1650
+ resultsByAgent.set(agentType, {
1651
+ agent: agentType,
1652
+ path: cluster.configPath,
1653
+ removed: false,
1654
+ error: errorMsg
1655
+ });
1656
+ }
1657
+ }
1658
+ }
1659
+ return agents.map((agentType) => resultsByAgent.get(agentType));
1660
+ }
1661
+ };
1662
+ var agentConfigStore = new AgentConfigStore();
1663
+
1664
+ // src/resolve-config-clusters.ts
1665
+ var getCoHostedAgents = (agentType, options = {}) => agentConfigStore.getCoHostedAgents(agentType, options);
1666
+ var resolveConfigClusters = (agentTypes, options = {}) => agentConfigStore.resolveConfigClusters(agentTypes, options);
1667
+ var sortAgentsByClusters = (agentTypes, options = {}) => agentConfigStore.sortAgentsByClusters(agentTypes, options);
1668
+
1669
+ // src/utils/parse-mcp-agent-list.ts
1670
+ var parseMcpAgentList = (input) => {
1671
+ if (!input || input.length === 0) return void 0;
1672
+ if (input.includes("*")) return getMcpAgentTypes();
1673
+ const resolved = [];
1674
+ for (const value of input) {
1675
+ const agentType = resolveMcpAgentAlias(value);
1676
+ if (!agentType) throw new Error(`Unknown MCP agent "${value}"`);
1677
+ resolved.push(agentType);
1678
+ }
1679
+ return resolved;
1680
+ };
1681
+
1682
+ // src/resolve-target-agents.ts
1683
+ var normalizeRequestedAgents = (input) => {
1684
+ if (!input || input.length === 0) return void 0;
1685
+ const rawList = [...input];
1323
1686
  if (rawList.every((item) => isMcpAgentType(item))) {
1324
1687
  return rawList;
1325
1688
  }
@@ -1374,117 +1737,41 @@ var resolveTargetAgents = (query = {}) => {
1374
1737
  };
1375
1738
  };
1376
1739
 
1377
- // src/source-parser.ts
1378
- var REMOTE_URL_REGEX = /^https?:\/\//i;
1379
- var HAS_WHITESPACE_REGEX = /\s/;
1380
- var PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(?:@[^\s]+)?$/;
1381
- var PATH_SEPARATOR_REGEX = /[/\\]/;
1382
- var stripVersionSuffix = (input7) => {
1383
- if (input7.startsWith("@")) {
1384
- const secondAtIndex = input7.indexOf("@", 1);
1385
- if (secondAtIndex > 0) return input7.slice(0, secondAtIndex);
1386
- return input7;
1387
- }
1388
- const atIndex = input7.lastIndexOf("@");
1389
- if (atIndex > 0) return input7.slice(0, atIndex);
1390
- return input7;
1391
- };
1392
- var stripScopePrefix = (input7) => {
1393
- if (!input7.startsWith("@") || !input7.includes("/")) return input7;
1394
- const parts = input7.split("/");
1395
- return parts[1] || input7;
1396
- };
1397
- var stripPathPrefix = (input7) => {
1398
- if (!PATH_SEPARATOR_REGEX.test(input7)) return input7;
1399
- const segments = input7.split(PATH_SEPARATOR_REGEX);
1400
- const basename = segments[segments.length - 1];
1401
- return basename || input7;
1740
+ // src/install-compat.ts
1741
+ var installMcpServerForAgent = (serverName, serverConfig, agentType, options = {}) => {
1742
+ const results = agentConfigStore.writeServers([agentType], serverName, serverConfig, options);
1743
+ return results[0];
1402
1744
  };
1403
- var extractPackageName = (input7) => {
1404
- let name = stripVersionSuffix(input7);
1405
- name = stripScopePrefix(name);
1406
- name = stripPathPrefix(name);
1407
- name = name.replace(SCRIPT_EXTENSION_REGEX, "");
1408
- for (const prefix of PACKAGE_NAME_PREFIX_STRIP) {
1409
- if (name.startsWith(prefix)) {
1410
- name = name.slice(prefix.length);
1411
- break;
1745
+ var installMcpServerForAgents = (serverName, serverConfig, agentTypes, options = {}) => {
1746
+ return agentConfigStore.writeServers(agentTypes, serverName, serverConfig, options);
1747
+ };
1748
+ var installToCompatibleAgents = (serverName, serverConfig, options) => {
1749
+ const { allAgents, incompatible = [], global: isGlobal, cwd } = options;
1750
+ const incompatibleMap = new Map(incompatible.map((item) => [item.agent, item.reason]));
1751
+ const compatibleAgents = allAgents.filter((a) => !incompatibleMap.has(a));
1752
+ const installedResults = agentConfigStore.writeServers(
1753
+ compatibleAgents,
1754
+ serverName,
1755
+ serverConfig,
1756
+ {
1757
+ global: isGlobal,
1758
+ cwd
1412
1759
  }
1413
- }
1414
- for (const suffix of PACKAGE_NAME_SUFFIX_STRIP) {
1415
- if (name.endsWith(suffix)) {
1416
- name = name.slice(0, -suffix.length);
1417
- break;
1760
+ );
1761
+ const installedMap = new Map(installedResults.map((r) => [r.agent, r]));
1762
+ return allAgents.map((agentType) => {
1763
+ const incompatibleReason = incompatibleMap.get(agentType);
1764
+ if (incompatibleReason) {
1765
+ return {
1766
+ agent: agentType,
1767
+ success: false,
1768
+ path: "",
1769
+ error: incompatibleReason
1770
+ };
1418
1771
  }
1419
- }
1420
- return name || MCP_DEFAULT_SERVER_NAME;
1421
- };
1422
- var inferNameFromUrl = (input7) => {
1423
- try {
1424
- const url = new URL(input7);
1425
- const host = url.hostname;
1426
- const labels = host.split(".").filter((segment) => segment.length > 0);
1427
- if (labels.length === 0) return MCP_DEFAULT_SERVER_NAME;
1428
- const meaningfulLabels = labels.filter((label) => {
1429
- const lower = label.toLowerCase();
1430
- if (COMMON_TLD_LABELS.has(lower)) return false;
1431
- if (GENERIC_HOST_PREFIXES.has(lower)) return false;
1432
- return true;
1433
- });
1434
- if (meaningfulLabels.length > 0) return meaningfulLabels[0];
1435
- if (labels.length >= 2) return labels[labels.length - 2];
1436
- return labels[labels.length - 1] || MCP_DEFAULT_SERVER_NAME;
1437
- } catch {
1438
- return MCP_DEFAULT_SERVER_NAME;
1439
- }
1440
- };
1441
- var inferNameFromCommand = (command) => {
1442
- const tokens = command.trim().split(/\s+/);
1443
- const runnerBase = tokens[0]?.split(PATH_SEPARATOR_REGEX).pop() ?? "";
1444
- const startIndex = KNOWN_COMMAND_RUNNERS.has(runnerBase) ? 1 : 0;
1445
- for (let tokenIndex = startIndex; tokenIndex < tokens.length; tokenIndex += 1) {
1446
- const token = tokens[tokenIndex];
1447
- if (!token || token.startsWith("-")) continue;
1448
- return extractPackageName(token);
1449
- }
1450
- const firstNonFlag = tokens.find((token) => !token.startsWith("-"));
1451
- return firstNonFlag ? extractPackageName(firstNonFlag) : MCP_DEFAULT_SERVER_NAME;
1452
- };
1453
- var parseMcpSource = (input7) => {
1454
- const trimmed = input7.trim();
1455
- if (trimmed.length === 0) {
1456
- throw new Error(
1457
- "Invalid MCP source: input is empty. Expected a remote URL, an npm package, or a command line."
1458
- );
1459
- }
1460
- if (REMOTE_URL_REGEX.test(trimmed)) {
1461
- return {
1462
- type: "remote",
1463
- value: trimmed,
1464
- inferredName: inferNameFromUrl(trimmed)
1465
- };
1466
- }
1467
- if (HAS_WHITESPACE_REGEX.test(trimmed)) {
1468
- return {
1469
- type: "command",
1470
- value: trimmed,
1471
- inferredName: inferNameFromCommand(trimmed)
1472
- };
1473
- }
1474
- if (PACKAGE_NAME_REGEX.test(trimmed)) {
1475
- return {
1476
- type: "package",
1477
- value: trimmed,
1478
- inferredName: extractPackageName(trimmed)
1479
- };
1480
- }
1481
- return {
1482
- type: "command",
1483
- value: trimmed,
1484
- inferredName: inferNameFromCommand(trimmed)
1485
- };
1772
+ return installedMap.get(agentType);
1773
+ });
1486
1774
  };
1487
- var isRemoteMcpSource = (parsed) => parsed.type === "remote";
1488
1775
 
1489
1776
  // src/install-mcp-server.ts
1490
1777
  var installMcpServer = (options) => {
@@ -1505,18 +1792,11 @@ var installMcpServer = (options) => {
1505
1792
  cwd,
1506
1793
  transport: requestedTransport
1507
1794
  });
1508
- const incompatibleMap = new Map(incompatible.map((item) => [item.agent, item.reason]));
1509
- const results = allAgents.map((agentType) => {
1510
- const incompatibleReason = incompatibleMap.get(agentType);
1511
- if (incompatibleReason) {
1512
- return {
1513
- agent: agentType,
1514
- success: false,
1515
- path: "",
1516
- error: incompatibleReason
1517
- };
1518
- }
1519
- return installMcpServerForAgent(serverName, serverConfig, agentType, { global: isGlobal, cwd });
1795
+ const results = installToCompatibleAgents(serverName, serverConfig, {
1796
+ allAgents,
1797
+ incompatible,
1798
+ global: isGlobal,
1799
+ cwd
1520
1800
  });
1521
1801
  return { serverName, config: serverConfig, results };
1522
1802
  };
@@ -1525,15 +1805,14 @@ var installMcpServer = (options) => {
1525
1805
  var listInstalledMcpServers = (options = {}) => {
1526
1806
  const agentTypes = options.agents ?? getMcpAgentTypes();
1527
1807
  const collected = [];
1528
- for (const agentType of agentTypes) {
1529
- const agent = getMcpAgentConfig(agentType);
1530
- const { path, exists, servers } = agentConfigStore.listServers(agent, options);
1531
- if (!exists) continue;
1532
- for (const [serverName, rawConfig] of Object.entries(servers)) {
1808
+ const results = agentConfigStore.listServersForAgents(agentTypes, options);
1809
+ for (const item of results) {
1810
+ if (!item.exists) continue;
1811
+ for (const [serverName, rawConfig] of Object.entries(item.servers)) {
1533
1812
  collected.push({
1534
1813
  serverName,
1535
- agent: agentType,
1536
- path,
1814
+ agent: item.agent,
1815
+ path: item.path,
1537
1816
  config: rawConfig,
1538
1817
  serverConfig: parseServerConfig(rawConfig)
1539
1818
  });
@@ -1541,828 +1820,24 @@ var listInstalledMcpServers = (options = {}) => {
1541
1820
  }
1542
1821
  return collected;
1543
1822
  };
1544
-
1545
- // src/remove.ts
1546
- var removeMcpServerFromAgent = (serverName, agentType, options = {}) => {
1547
- const agent = getMcpAgentConfig(agentType);
1548
- const { target } = agentConfigStore.resolveTarget(agent, options);
1549
- try {
1550
- const { removed } = agentConfigStore.removeServer(agent, serverName, options);
1551
- return { agent: agentType, path: target.configPath, removed };
1552
- } catch (error) {
1553
- return {
1554
- agent: agentType,
1555
- path: target.configPath,
1556
- removed: false,
1557
- error: toErrorMessage(error)
1558
- };
1559
- }
1560
- };
1561
- var removeMcpServer = (options) => {
1562
- const { allAgents } = resolveTargetAgents({
1563
- requested: options.agents,
1564
- all: !options.agents,
1565
- global: options.global,
1566
- cwd: options.cwd
1567
- });
1568
- const results = [];
1569
- for (const agentType of allAgents) {
1570
- const result = removeMcpServerFromAgent(options.name, agentType, {
1571
- global: options.global,
1572
- cwd: options.cwd
1573
- });
1574
- if (result.removed || result.error) results.push(result);
1575
- }
1576
- return results;
1577
- };
1578
-
1579
- // src/interactive/main-menu.ts
1580
- var import_prompts11 = require("@inquirer/prompts");
1581
- var import_picocolors11 = __toESM(require("picocolors"), 1);
1582
-
1583
- // src/interactive/wizard-add.ts
1584
- var import_prompts8 = require("@inquirer/prompts");
1585
- var import_picocolors8 = __toESM(require("picocolors"), 1);
1586
-
1587
- // src/utils/logger.ts
1588
- var import_picocolors = __toESM(require("picocolors"), 1);
1589
- var logger = {
1590
- info: (message) => {
1591
- console.log(import_picocolors.default.cyan("i"), message);
1592
- },
1593
- success: (message) => {
1594
- console.log(import_picocolors.default.green("\u221A"), message);
1595
- },
1596
- warn: (message) => {
1597
- console.log(import_picocolors.default.yellow("!"), message);
1598
- },
1599
- error: (message) => {
1600
- console.error(import_picocolors.default.red("x"), message);
1601
- }
1602
- };
1603
-
1604
- // src/interactive/prompts/agents.ts
1605
- var import_prompts2 = require("@inquirer/prompts");
1606
- var import_picocolors3 = __toESM(require("picocolors"), 1);
1607
-
1608
- // src/interactive/prompts/scope.ts
1609
- var import_prompts = require("@inquirer/prompts");
1610
- var import_picocolors2 = __toESM(require("picocolors"), 1);
1611
- var promptScope = async (options = {}) => {
1612
- const initialGlobal = options.defaultGlobal ?? options.global;
1613
- if (initialGlobal !== void 0) {
1614
- return initialGlobal;
1615
- }
1616
- const cwd = options.cwd ?? process.cwd();
1617
- return (0, import_prompts.select)({
1618
- message: options.message ?? "Select MCP scope:",
1619
- choices: [
1620
- {
1621
- name: `Current Project - ${import_picocolors2.default.dim(cwd)}`,
1622
- value: false
1623
- },
1624
- {
1625
- name: `Global User Config - ${import_picocolors2.default.dim("applies across all projects")}`,
1626
- value: true
1627
- }
1628
- ]
1629
- });
1630
- };
1631
-
1632
- // src/interactive/prompts/agents.ts
1633
- var promptScopeAndAgents = async (options = {}) => {
1634
- const cwd = options.cwd ?? process.cwd();
1635
- const isGlobal = await promptScope({
1636
- cwd,
1637
- defaultGlobal: options.defaultGlobal,
1638
- message: "Select MCP installation scope:"
1639
- });
1640
- const resolution = resolveTargetAgents({
1641
- global: isGlobal,
1642
- cwd
1643
- });
1644
- const detected = resolution.detected;
1645
- const availableAgentTypes = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
1646
- if (detected.length > 0) {
1647
- logger.info(
1648
- `Detected configured agents: ${import_picocolors3.default.cyan(detected.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
1649
- );
1650
- } else {
1651
- logger.warn(`No active ${isGlobal ? "global" : "project"} agents detected`);
1652
- }
1653
- const defaultChecked = options.defaultAgents && options.defaultAgents.length > 0 ? options.defaultAgents : detected;
1654
- const choices = availableAgentTypes.map((agentType) => {
1655
- const config = getMcpAgentConfig(agentType);
1656
- const isDetected = detected.includes(agentType);
1657
- const label = `${config.displayName} ${import_picocolors3.default.dim(`(${agentType})`)}${isDetected ? import_picocolors3.default.green(" [detected]") : ""}`;
1658
- return {
1659
- name: label,
1660
- value: agentType,
1661
- checked: defaultChecked.includes(agentType)
1662
- };
1663
- });
1664
- const selectedAgents = await (0, import_prompts2.checkbox)({
1665
- message: "Select target agents (Space to select, Enter to confirm):",
1666
- choices,
1667
- validate: (chosen) => {
1668
- if (chosen.length === 0) {
1669
- return "Please select at least one agent";
1670
- }
1671
- return true;
1672
- }
1673
- });
1674
- return {
1675
- global: isGlobal,
1676
- agents: selectedAgents
1677
- };
1678
- };
1679
-
1680
- // src/interactive/prompts/args.ts
1681
- var import_prompts3 = require("@inquirer/prompts");
1682
- var parseArgsString = (rawText) => {
1683
- const matches = rawText.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
1684
- if (!matches) return [];
1685
- return matches.map((arg) => {
1686
- if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
1687
- return arg.slice(1, -1);
1688
- }
1689
- return arg;
1690
- });
1691
- };
1692
- var promptArgsConfig = async (initialArgs = []) => {
1693
- if (initialArgs.length > 0) {
1694
- return initialArgs;
1695
- }
1696
- const needArgs = await (0, import_prompts3.confirm)({
1697
- message: "Configure command arguments (e.g. file paths, connection strings)?",
1698
- default: false
1699
- });
1700
- if (!needArgs) {
1701
- return [];
1702
- }
1703
- const raw = await (0, import_prompts3.input)({
1704
- message: "Enter command arguments (space-separated, wrap paths with spaces in quotes):",
1705
- validate: (val) => val.trim() ? true : "Arguments cannot be empty"
1706
- });
1707
- return parseArgsString(raw.trim());
1708
- };
1709
- var formatArgsString = (args) => {
1710
- return args.map((arg) => arg.includes(" ") || arg.includes('"') ? `"${arg.replace(/"/g, '\\"')}"` : arg).join(" ");
1711
- };
1712
- var promptEditArgs = async (currentArgs = []) => {
1713
- const defaultStr = formatArgsString(currentArgs);
1714
- const raw = await (0, import_prompts3.input)({
1715
- message: "Edit command arguments (space-separated, wrap paths with spaces in quotes, leave empty to clear):",
1716
- default: defaultStr
1717
- });
1718
- const trimmed = raw.trim();
1719
- if (!trimmed) {
1720
- return [];
1721
- }
1722
- return parseArgsString(trimmed);
1723
- };
1724
-
1725
- // src/interactive/prompts/env.ts
1726
- var import_prompts6 = require("@inquirer/prompts");
1727
- var import_picocolors6 = __toESM(require("picocolors"), 1);
1728
-
1729
- // src/interactive/prompts/multiline.ts
1730
- var import_node_readline = require("readline");
1731
- var import_prompts4 = require("@inquirer/prompts");
1732
- var import_picocolors4 = __toESM(require("picocolors"), 1);
1733
- var readMultilineTextFromTerminal = async (message, endHint = "When done pasting, enter END on a new line or press Enter twice to finish") => {
1734
- console.log(import_picocolors4.default.cyan(`
1735
- ${message}`));
1736
- console.log(import_picocolors4.default.dim(` (Hint: ${endHint})
1737
- `));
1738
- return new Promise((resolve) => {
1739
- const rl = (0, import_node_readline.createInterface)({
1740
- input: process.stdin,
1741
- output: process.stdout
1742
- });
1743
- const lines = [];
1744
- let consecutiveEmpty = 0;
1745
- const cleanup = () => {
1746
- rl.removeAllListeners();
1747
- rl.close();
1748
- };
1749
- rl.on("line", (line) => {
1750
- const trimmed = line.trim();
1751
- if (trimmed === "END") {
1752
- cleanup();
1753
- resolve(lines.join("\n"));
1754
- return;
1755
- }
1756
- if (line === "") {
1757
- consecutiveEmpty++;
1758
- if (lines.length > 0) {
1759
- cleanup();
1760
- resolve(lines.join("\n"));
1761
- return;
1762
- }
1763
- if (consecutiveEmpty >= 2) {
1764
- cleanup();
1765
- resolve("");
1766
- return;
1767
- }
1768
- } else {
1769
- consecutiveEmpty = 0;
1770
- lines.push(line);
1771
- }
1772
- });
1773
- rl.on("close", () => {
1774
- resolve(lines.join("\n"));
1775
- });
1776
- });
1777
- };
1778
- var promptEditorText = async (options) => {
1779
- try {
1780
- return await (0, import_prompts4.editor)({
1781
- message: options.message,
1782
- default: options.defaultText ?? "",
1783
- postfix: options.postfix
1784
- });
1785
- } catch {
1786
- return readMultilineTextFromTerminal(options.message);
1787
- }
1788
- };
1789
-
1790
- // src/interactive/prompts/kv.ts
1791
- var import_prompts5 = require("@inquirer/prompts");
1792
- var import_picocolors5 = __toESM(require("picocolors"), 1);
1793
- var promptEditKeyValueConfig = async (currentItems = {}, options) => {
1794
- let items = { ...currentItems };
1795
- while (true) {
1796
- const keys = Object.keys(items);
1797
- console.log();
1798
- if (keys.length === 0) {
1799
- console.log(import_picocolors5.default.dim(` No ${options.itemsNoun} configured.`));
1800
- } else {
1801
- console.log(import_picocolors5.default.cyan(import_picocolors5.default.bold(` Configured ${options.title} (${keys.length}):`)));
1802
- for (const [k, v] of Object.entries(items)) {
1803
- const sep = options.separator === "=" ? "=" : ": ";
1804
- console.log(` ${import_picocolors5.default.bold(k)}${sep}${import_picocolors5.default.dim(options.maskValue(k, v))}`);
1805
- }
1806
- }
1807
- console.log();
1808
- const choice = await (0, import_prompts5.select)({
1809
- message: `Manage ${options.itemsNoun}:`,
1810
- choices: [
1811
- {
1812
- name: "Open in system default editor ($EDITOR)",
1813
- value: "editor"
1814
- },
1815
- {
1816
- name: `Add or modify a ${options.itemNoun}`,
1817
- value: "upsert"
1818
- },
1819
- ...keys.length > 0 ? [
1820
- {
1821
- name: `Delete a ${options.itemNoun}`,
1822
- value: "delete"
1823
- }
1824
- ] : [],
1825
- {
1826
- name: `Paste multiline ${options.itemsNoun} into terminal`,
1827
- value: "paste"
1828
- },
1829
- ...keys.length > 0 ? [
1830
- {
1831
- name: `Clear all ${options.itemsNoun}`,
1832
- value: "clear"
1833
- }
1834
- ] : [],
1835
- {
1836
- name: `Done (finish editing ${options.itemsNoun})`,
1837
- value: "done"
1838
- }
1839
- ]
1840
- });
1841
- if (choice === "done") {
1842
- return items;
1843
- }
1844
- if (choice === "editor") {
1845
- const defaultText = options.formatText(items);
1846
- const text = await promptEditorText({
1847
- message: options.editorMessage,
1848
- postfix: options.editorPostfix,
1849
- defaultText
1850
- });
1851
- const parsed = options.parseText(text);
1852
- items = parsed;
1853
- logger.success(`${options.title} updated (${Object.keys(items).length} total)`);
1854
- } else if (choice === "upsert") {
1855
- const key = await (0, import_prompts5.input)({
1856
- message: options.keyPromptMessage,
1857
- validate: (val) => {
1858
- const trimmed = val.trim();
1859
- if (!trimmed) return `${options.itemNoun} name cannot be empty`;
1860
- if (/\s/.test(trimmed)) return `${options.itemNoun} name cannot contain spaces`;
1861
- return true;
1862
- }
1863
- });
1864
- const trimmedKey = key.trim();
1865
- const existingVal = items[trimmedKey];
1866
- const isSecret = options.isSecretKey(trimmedKey);
1867
- let newVal;
1868
- if (isSecret) {
1869
- newVal = await (0, import_prompts5.password)({
1870
- message: existingVal !== void 0 ? `New value for (${trimmedKey}) [leave empty to keep current]:` : `${options.valuePromptMessage} for (${trimmedKey}) [sensitive content masked]:`,
1871
- mask: "*"
1872
- });
1873
- if (existingVal !== void 0 && newVal === "") {
1874
- newVal = existingVal;
1875
- }
1876
- } else {
1877
- newVal = await (0, import_prompts5.input)({
1878
- message: `${options.valuePromptMessage} for (${trimmedKey}):`,
1879
- default: existingVal
1880
- });
1881
- }
1882
- items[trimmedKey] = newVal;
1883
- logger.success(`${existingVal !== void 0 ? "Updated" : "Added"}: ${import_picocolors5.default.cyan(trimmedKey)}`);
1884
- } else if (choice === "delete") {
1885
- const toDelete = await (0, import_prompts5.select)({
1886
- message: `Select ${options.itemNoun} to delete:`,
1887
- choices: [
1888
- ...keys.map((k) => ({ name: k, value: k })),
1889
- { name: "Cancel", value: "__cancel__" }
1890
- ]
1891
- });
1892
- if (toDelete !== "__cancel__") {
1893
- delete items[toDelete];
1894
- logger.success(`Deleted: ${import_picocolors5.default.cyan(toDelete)}`);
1895
- }
1896
- } else if (choice === "paste") {
1897
- const pasted = await readMultilineTextFromTerminal(options.pasteMessage);
1898
- const parsed = options.parseText(pasted);
1899
- const count = Object.keys(parsed).length;
1900
- if (count === 0) {
1901
- logger.warn(`No valid ${options.itemsNoun} recognized`);
1902
- } else {
1903
- if (keys.length > 0) {
1904
- const pasteMode = await (0, import_prompts5.select)({
1905
- message: `How to apply pasted ${options.itemsNoun}?`,
1906
- choices: [
1907
- { name: `Merge with existing ${options.itemsNoun}`, value: "merge" },
1908
- { name: `Replace all existing ${options.itemsNoun}`, value: "replace" }
1909
- ]
1910
- });
1911
- if (pasteMode === "replace") {
1912
- items = parsed;
1913
- } else {
1914
- Object.assign(items, parsed);
1915
- }
1916
- } else {
1917
- items = parsed;
1918
- }
1919
- logger.success(`Successfully applied ${import_picocolors5.default.cyan(String(count))} ${options.itemsNoun}`);
1920
- }
1921
- } else if (choice === "clear") {
1922
- const confirmClear = await (0, import_prompts5.confirm)({
1923
- message: `Are you sure you want to clear all ${options.itemsNoun}?`,
1924
- default: false
1925
- });
1926
- if (confirmClear) {
1927
- items = {};
1928
- logger.success(`Cleared all ${options.itemsNoun}`);
1929
- }
1930
- }
1931
- }
1932
- };
1933
-
1934
- // src/interactive/prompts/env.ts
1935
- var SECRET_KEY_PATTERN = /(token|key|secret|password|passwd|auth|credential)/i;
1936
- var maskSecretValue = (key, value) => {
1937
- if (!SECRET_KEY_PATTERN.test(key) || value.length <= 4) {
1938
- return value;
1939
- }
1940
- return `${value.slice(0, 2)}***${value.slice(-2)}`;
1941
- };
1942
- var formatEnvText = (env) => {
1943
- return Object.entries(env).map(([key, value]) => {
1944
- if (/[\s"']/.test(value)) {
1945
- return `${key}="${value.replace(/"/g, '\\"')}"`;
1946
- }
1947
- return `${key}=${value}`;
1948
- }).join("\n");
1949
- };
1950
- var parseEnvText = (rawText) => {
1951
- const result = {};
1952
- const lines = rawText.split(/\r?\n/);
1953
- for (const rawLine of lines) {
1954
- let line = rawLine.trim();
1955
- if (!line || line.startsWith("#")) continue;
1956
- if (line.startsWith("export ")) {
1957
- line = line.slice(7).trim();
1958
- }
1959
- const eqIndex = line.indexOf("=");
1960
- if (eqIndex === -1) continue;
1961
- const key = line.slice(0, eqIndex).trim();
1962
- let value = line.slice(eqIndex + 1).trim();
1963
- if (!key) continue;
1964
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1965
- value = value.slice(1, -1);
1966
- }
1967
- result[key] = value;
1968
- }
1969
- return result;
1970
- };
1971
- var promptEnvConfig = async (initialEnv = {}) => {
1972
- const env = { ...initialEnv };
1973
- const initialCount = Object.keys(env).length;
1974
- if (initialCount > 0) {
1975
- logger.info(`Includes ${import_picocolors6.default.cyan(String(initialCount))} preset environment variables`);
1976
- }
1977
- const mode = await (0, import_prompts6.select)({
1978
- message: "Configure environment variables?",
1979
- choices: [
1980
- {
1981
- name: "Skip / None",
1982
- value: "skip"
1983
- },
1984
- {
1985
- name: "Paste multiline .env text into terminal",
1986
- value: "paste"
1987
- },
1988
- {
1989
- name: "Open in system default editor ($EDITOR)",
1990
- value: "editor"
1991
- },
1992
- {
1993
- name: "Enter key-value pairs one by one",
1994
- value: "manual"
1995
- }
1996
- ]
1997
- });
1998
- if (mode === "skip") {
1999
- return env;
2000
- }
2001
- if (mode === "paste" || mode === "editor") {
2002
- const pasted = mode === "editor" ? await promptEditorText({
2003
- message: "Paste or edit environment variables in editor, then save and exit:",
2004
- postfix: ".env",
2005
- defaultText: formatEnvText(env)
2006
- }) : await readMultilineTextFromTerminal("Paste .env formatted content (multiline supported):");
2007
- const parsed = parseEnvText(pasted);
2008
- const count = Object.keys(parsed).length;
2009
- if (count === 0) {
2010
- logger.warn("No valid KEY=VALUE pairs recognized");
2011
- } else {
2012
- Object.assign(env, parsed);
2013
- logger.success(`Successfully parsed ${import_picocolors6.default.cyan(String(count))} environment variables:`);
2014
- for (const [k, v] of Object.entries(parsed)) {
2015
- console.log(` ${import_picocolors6.default.bold(k)}=${import_picocolors6.default.dim(maskSecretValue(k, v))}`);
2016
- }
2017
- }
2018
- return env;
2019
- }
2020
- logger.info("Entering environment variables (leave key empty and press enter to finish):");
2021
- while (true) {
2022
- const key = await (0, import_prompts6.input)({
2023
- message: "Variable name (Key, leave empty to finish):",
2024
- validate: (val2) => {
2025
- const trimmed = val2.trim();
2026
- if (!trimmed) return true;
2027
- if (/\s/.test(trimmed)) return "Variable name cannot contain spaces";
2028
- return true;
2029
- }
2030
- });
2031
- const trimmedKey = key.trim();
2032
- if (!trimmedKey) break;
2033
- const isSecret = SECRET_KEY_PATTERN.test(trimmedKey);
2034
- let val;
2035
- if (isSecret) {
2036
- val = await (0, import_prompts6.password)({
2037
- message: `Value for (${trimmedKey}) [secret masked]:`,
2038
- mask: "*"
2039
- });
2040
- } else {
2041
- val = await (0, import_prompts6.input)({
2042
- message: `Value for (${trimmedKey}):`
2043
- });
2044
- }
2045
- env[trimmedKey] = val;
2046
- logger.success(`Added: ${import_picocolors6.default.cyan(trimmedKey)}`);
2047
- }
2048
- return env;
2049
- };
2050
- var promptEditEnvConfig = async (currentEnv = {}) => promptEditKeyValueConfig(currentEnv, {
2051
- title: "Environment Variables",
2052
- itemNoun: "variable",
2053
- itemsNoun: "environment variables",
2054
- separator: "=",
2055
- editorPostfix: ".env",
2056
- editorMessage: "Edit environment variables in editor, then save and exit:",
2057
- pasteMessage: "Paste .env formatted content (multiline supported):",
2058
- keyPromptMessage: "Variable name (Key):",
2059
- valuePromptMessage: "Value",
2060
- isSecretKey: (k) => SECRET_KEY_PATTERN.test(k),
2061
- maskValue: maskSecretValue,
2062
- formatText: formatEnvText,
2063
- parseText: parseEnvText
2064
- });
2065
-
2066
- // src/interactive/prompts/headers.ts
2067
- var import_prompts7 = require("@inquirer/prompts");
2068
- var import_picocolors7 = __toESM(require("picocolors"), 1);
2069
- var SECRET_HEADER_PATTERN = /(authorization|token|key|secret|auth)/i;
2070
- var maskSecretHeader = (key, value) => {
2071
- if (!SECRET_HEADER_PATTERN.test(key) || value.length <= 8) {
2072
- return value;
2073
- }
2074
- return `${value.slice(0, 4)}***${value.slice(-3)}`;
2075
- };
2076
- var formatHeadersText = (headers) => {
2077
- return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n");
2078
- };
2079
- var parseHeadersText = (rawText) => {
2080
- const result = {};
2081
- const lines = rawText.split(/\r?\n/);
2082
- for (const rawLine of lines) {
2083
- const line = rawLine.trim();
2084
- if (!line || line.startsWith("#")) continue;
2085
- const colonIndex = line.indexOf(":");
2086
- const equalIndex = line.indexOf("=");
2087
- let splitIndex = -1;
2088
- if (colonIndex !== -1 && equalIndex !== -1) {
2089
- splitIndex = Math.min(colonIndex, equalIndex);
2090
- } else if (colonIndex !== -1) {
2091
- splitIndex = colonIndex;
2092
- } else {
2093
- splitIndex = equalIndex;
2094
- }
2095
- if (splitIndex === -1) continue;
2096
- const key = line.slice(0, splitIndex).trim();
2097
- let value = line.slice(splitIndex + 1).trim();
2098
- if (!key) continue;
2099
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2100
- value = value.slice(1, -1);
2101
- }
2102
- result[key] = value;
2103
- }
2104
- return result;
2105
- };
2106
- var promptHeadersConfig = async (initialHeaders = {}) => {
2107
- const headers = { ...initialHeaders };
2108
- const mode = await (0, import_prompts7.select)({
2109
- message: "Select HTTP headers configuration method:",
2110
- choices: [
2111
- {
2112
- name: "Paste multiline headers into terminal (Key: Value format)",
2113
- value: "paste"
2114
- },
2115
- {
2116
- name: "Open in system default editor ($EDITOR)",
2117
- value: "editor"
2118
- },
2119
- {
2120
- name: "Enter headers one by one (e.g. Authorization: Bearer ...)",
2121
- value: "manual"
2122
- },
2123
- {
2124
- name: "Skip / None",
2125
- value: "skip"
2126
- }
2127
- ]
2128
- });
2129
- if (mode === "skip") {
2130
- return headers;
2131
- }
2132
- if (mode === "paste" || mode === "editor") {
2133
- const pasted = mode === "editor" ? await promptEditorText({
2134
- message: "Paste or edit HTTP headers in editor, then save and exit:",
2135
- defaultText: formatHeadersText(headers)
2136
- }) : await readMultilineTextFromTerminal(
2137
- "Paste HTTP headers content (multiline supported, e.g. Authorization: Bearer ...):"
2138
- );
2139
- const parsed = parseHeadersText(pasted);
2140
- const count = Object.keys(parsed).length;
2141
- if (count === 0) {
2142
- logger.warn("No valid Key: Value pairs recognized");
2143
- } else {
2144
- Object.assign(headers, parsed);
2145
- logger.success(`Successfully parsed ${import_picocolors7.default.cyan(String(count))} headers:`);
2146
- for (const [k, v] of Object.entries(parsed)) {
2147
- console.log(` ${import_picocolors7.default.bold(k)}: ${import_picocolors7.default.dim(maskSecretHeader(k, v))}`);
2148
- }
2149
- }
2150
- return headers;
2151
- }
2152
- logger.info("Entering HTTP headers (leave header name empty and press enter to finish):");
2153
- while (true) {
2154
- const name = await (0, import_prompts7.input)({
2155
- message: "Header name (e.g. Authorization, leave empty to finish):",
2156
- validate: (val2) => {
2157
- const trimmed = val2.trim();
2158
- if (!trimmed) return true;
2159
- if (/\s/.test(trimmed)) return "Header name cannot contain spaces";
2160
- return true;
2161
- }
2162
- });
2163
- const trimmedName = name.trim();
2164
- if (!trimmedName) break;
2165
- const isSecret = SECRET_HEADER_PATTERN.test(trimmedName);
2166
- let val;
2167
- if (isSecret) {
2168
- val = await (0, import_prompts7.password)({
2169
- message: `Header value for (${trimmedName}) [sensitive content masked]:`,
2170
- mask: "*"
2171
- });
2172
- } else {
2173
- val = await (0, import_prompts7.input)({
2174
- message: `Header value for (${trimmedName}):`
2175
- });
2176
- }
2177
- headers[trimmedName] = val;
2178
- logger.success(`Added: ${import_picocolors7.default.cyan(trimmedName)}`);
2179
- }
2180
- return headers;
2181
- };
2182
- var promptEditHeadersConfig = async (currentHeaders = {}) => promptEditKeyValueConfig(currentHeaders, {
2183
- title: "HTTP Headers",
2184
- itemNoun: "header",
2185
- itemsNoun: "HTTP headers",
2186
- separator: ":",
2187
- editorMessage: "Edit HTTP headers in editor, then save and exit:",
2188
- pasteMessage: "Paste HTTP headers content (multiline supported, e.g. Authorization: Bearer ...):",
2189
- keyPromptMessage: "Header name (e.g. Authorization):",
2190
- valuePromptMessage: "Header value",
2191
- isSecretKey: (k) => SECRET_HEADER_PATTERN.test(k),
2192
- maskValue: maskSecretHeader,
2193
- formatText: formatHeadersText,
2194
- parseText: parseHeadersText
2195
- });
2196
-
2197
- // src/interactive/wizard-add.ts
2198
- var wizardAdd = async (initial = {}) => {
2199
- const cwd = initial.cwd ?? process.cwd();
2200
- logger.info(import_picocolors8.default.bold("Welcome to the MCP interactive add wizard"));
2201
- let source = initial.source;
2202
- if (!source) {
2203
- const sourceType = await (0, import_prompts8.select)({
2204
- message: "Select MCP server type:",
2205
- choices: [
2206
- {
2207
- name: "npm package (run via npx)",
2208
- value: "npm"
2209
- },
2210
- {
2211
- name: "Remote MCP server (via HTTP / SSE URL)",
2212
- value: "remote"
2213
- },
2214
- {
2215
- name: "Local command / script / Docker (stdio)",
2216
- value: "command"
2217
- }
2218
- ]
2219
- });
2220
- if (sourceType === "npm") {
2221
- source = await (0, import_prompts8.input)({
2222
- message: "Enter npm package name (e.g. @modelcontextprotocol/server-postgres):",
2223
- validate: (val) => val.trim() ? true : "Package name cannot be empty"
2224
- });
2225
- } else if (sourceType === "remote") {
2226
- source = await (0, import_prompts8.input)({
2227
- message: "Enter remote server URL (e.g. https://mcp.example.com/sse):",
2228
- validate: (val) => {
2229
- const trimmed = val.trim();
2230
- if (!trimmed) return "URL cannot be empty";
2231
- if (!/^https?:\/\//i.test(trimmed)) return "Please enter a valid URL starting with http:// or https://";
2232
- return true;
2233
- }
2234
- });
2235
- } else {
2236
- source = await (0, import_prompts8.input)({
2237
- message: "Enter command and arguments (e.g. python -m my_mcp_server or docker run ...):",
2238
- validate: (val) => val.trim() ? true : "Command cannot be empty"
2239
- });
2240
- }
2241
- }
2242
- source = source.trim();
2243
- const parsed = parseMcpSource(source);
2244
- let serverName = initial.name;
2245
- if (!serverName) {
2246
- serverName = await (0, import_prompts8.input)({
2247
- message: "MCP server name:",
2248
- default: parsed.inferredName,
2249
- validate: (val) => val.trim() ? true : "Server name cannot be empty"
2250
- });
2251
- }
2252
- serverName = serverName.trim();
2253
- let transport = initial.transport;
2254
- let headers = initial.headers ?? {};
2255
- if (parsed.type === "remote") {
2256
- if (!transport) {
2257
- const isSseUrl = /\/sse\b/i.test(parsed.value);
2258
- transport = await (0, import_prompts8.select)({
2259
- message: "Select remote transport protocol:",
2260
- choices: [
2261
- { name: "HTTP", value: "http" },
2262
- { name: "SSE (Server-Sent Events)", value: "sse" }
2263
- ],
2264
- default: isSseUrl ? "sse" : "http"
2265
- });
2266
- }
2267
- if (Object.keys(headers).length === 0) {
2268
- const needHeader = await (0, import_prompts8.confirm)({
2269
- message: "Configure HTTP headers (e.g. Authorization Bearer token)?",
2270
- default: false
2271
- });
2272
- if (needHeader) {
2273
- headers = await promptHeadersConfig();
2274
- }
2275
- }
2276
- }
2277
- const { global: isGlobal, agents: selectedAgents } = await promptScopeAndAgents({
2278
- cwd,
2279
- defaultGlobal: initial.global,
2280
- defaultAgents: initial.agents
2281
- });
2282
- let args = initial.args ?? [];
2283
- if (parsed.type !== "remote") {
2284
- args = await promptArgsConfig(args);
2285
- }
2286
- let env = initial.env ?? {};
2287
- if (parsed.type !== "remote") {
2288
- env = await promptEnvConfig(env);
2289
- }
2290
- console.log("\n" + import_picocolors8.default.cyan(import_picocolors8.default.bold("Configuration Preview:")));
2291
- console.log(` ${import_picocolors8.default.bold("Server Name:")} ${import_picocolors8.default.green(serverName)}`);
2292
- console.log(` ${import_picocolors8.default.bold("Server Type:")} ${import_picocolors8.default.magenta(parsed.type)}`);
2293
- console.log(` ${import_picocolors8.default.bold("Source/Command:")} ${import_picocolors8.default.dim(source)}`);
2294
- console.log(` ${import_picocolors8.default.bold("Scope:")} ${isGlobal ? import_picocolors8.default.yellow("Global") : import_picocolors8.default.blue("Project")}`);
2295
- console.log(` ${import_picocolors8.default.bold("Target Agents:")} ${import_picocolors8.default.cyan(selectedAgents.join(", "))}`);
2296
- if (args.length > 0) {
2297
- console.log(` ${import_picocolors8.default.bold("Arguments:")} ${import_picocolors8.default.dim(args.join(" "))}`);
2298
- }
2299
- if (transport) {
2300
- console.log(` ${import_picocolors8.default.bold("Transport:")} ${import_picocolors8.default.magenta(transport)}`);
2301
- }
2302
- const envKeys = Object.keys(env);
2303
- if (envKeys.length > 0) {
2304
- console.log(` ${import_picocolors8.default.bold("Environment Variables:")} ${import_picocolors8.default.dim(envKeys.join(", "))} (${envKeys.length})`);
2305
- }
2306
- const headerKeys = Object.keys(headers);
2307
- if (headerKeys.length > 0) {
2308
- console.log(` ${import_picocolors8.default.bold("Headers:")} ${import_picocolors8.default.dim(headerKeys.join(", "))} (${headerKeys.length})`);
2309
- }
2310
- console.log();
2311
- const proceed = await (0, import_prompts8.confirm)({
2312
- message: "Confirm installation with this configuration?",
2313
- default: true
2314
- });
2315
- if (!proceed) {
2316
- logger.warn("Operation cancelled");
2317
- return false;
2318
- }
2319
- const result = installMcpServer({
2320
- source,
2321
- name: serverName,
2322
- agents: selectedAgents,
2323
- args,
2324
- global: isGlobal,
2325
- cwd,
2326
- transport,
2327
- headers,
2328
- env
2329
- });
2330
- logger.info(
2331
- `Writing ${import_picocolors8.default.bold(result.serverName)} to ${import_picocolors8.default.cyan(String(result.results.length))} agent config files...`
2332
- );
2333
- let allSuccess = true;
2334
- for (const record of result.results) {
2335
- if (record.success) {
2336
- logger.success(`${import_picocolors8.default.cyan(record.agent)}: Successfully written to ${import_picocolors8.default.dim(record.path)}`);
2337
- } else {
2338
- allSuccess = false;
2339
- logger.error(`${import_picocolors8.default.cyan(record.agent)}: Failed to write - ${record.error}`);
2340
- }
2341
- }
2342
- if (allSuccess) {
2343
- logger.success(import_picocolors8.default.bold(`MCP server "${serverName}" configured successfully!`));
2344
- }
2345
- return allSuccess;
2346
- };
2347
-
2348
- // src/interactive/wizard-manage.ts
2349
- var import_prompts9 = require("@inquirer/prompts");
2350
- var import_picocolors9 = __toESM(require("picocolors"), 1);
2351
-
2352
- // src/interactive/utils/group-installed-servers.ts
2353
- var normalizeServerConfig = parseServerConfig;
2354
1823
  var groupInstalledServersByName = (installed) => {
2355
1824
  const grouped = /* @__PURE__ */ new Map();
2356
1825
  for (const item of installed) {
1826
+ const itemConfig = item.serverConfig ?? parseServerConfig(item.config);
2357
1827
  let entry = grouped.get(item.serverName);
2358
1828
  if (!entry) {
2359
1829
  entry = {
2360
1830
  serverName: item.serverName,
2361
1831
  agents: [],
2362
1832
  paths: [],
2363
- config: normalizeServerConfig(item.config)
1833
+ config: itemConfig,
1834
+ hasDivergence: false
2364
1835
  };
2365
1836
  grouped.set(item.serverName, entry);
1837
+ } else if (!entry.hasDivergence) {
1838
+ if (JSON.stringify(entry.config) !== JSON.stringify(itemConfig)) {
1839
+ entry.hasDivergence = true;
1840
+ }
2366
1841
  }
2367
1842
  if (!entry.agents.includes(item.agent)) {
2368
1843
  entry.agents.push(item.agent);
@@ -2373,679 +1848,127 @@ var groupInstalledServersByName = (installed) => {
2373
1848
  }
2374
1849
  return grouped;
2375
1850
  };
2376
-
2377
- // src/interactive/wizard-manage.ts
2378
- var displayServerDetails = ({
2379
- serverName,
2380
- config,
2381
- agents,
2382
- isGlobal,
2383
- titlePrefix = "MCP Server Details"
2384
- }) => {
2385
- console.log("\n" + import_picocolors9.default.cyan(import_picocolors9.default.bold(`${titlePrefix}: [${serverName}]`)));
2386
- if (isGlobal !== void 0) {
2387
- console.log(` ${import_picocolors9.default.bold("Scope:")} ${isGlobal ? "Global" : "Project"}`);
2388
- }
2389
- if (agents && agents.length > 0) {
2390
- console.log(
2391
- ` ${import_picocolors9.default.bold("Configured Agents:")} ${import_picocolors9.default.green(agents.map((a) => getMcpAgentConfig(a).displayName).join(", "))}`
2392
- );
2393
- }
2394
- const isRemote = Boolean(config.url && config.url.length > 0);
2395
- if (isRemote) {
2396
- console.log(` ${import_picocolors9.default.bold("Transport:")} ${import_picocolors9.default.magenta(config.type ?? "http")}`);
2397
- console.log(` ${import_picocolors9.default.bold("URL:")} ${import_picocolors9.default.dim(config.url ?? "")}`);
2398
- const headerKeys = Object.keys(config.headers ?? {});
2399
- if (headerKeys.length > 0) {
2400
- console.log(` ${import_picocolors9.default.bold("Headers:")} ${import_picocolors9.default.cyan(String(headerKeys.length))}`);
2401
- for (const [k, v] of Object.entries(config.headers ?? {})) {
2402
- console.log(` ${import_picocolors9.default.bold(k)}: ${import_picocolors9.default.dim(maskSecretHeader(k, v))}`);
2403
- }
2404
- } else {
2405
- console.log(` ${import_picocolors9.default.bold("Headers:")} ${import_picocolors9.default.dim("(none)")}`);
2406
- }
2407
- } else {
2408
- console.log(` ${import_picocolors9.default.bold("Command:")} ${import_picocolors9.default.magenta(config.command ?? "")}`);
2409
- const argsStr = config.args && config.args.length > 0 ? config.args.join(" ") : "(none)";
2410
- console.log(` ${import_picocolors9.default.bold("Arguments:")} ${import_picocolors9.default.dim(argsStr)}`);
2411
- const envKeys = Object.keys(config.env ?? {});
2412
- if (envKeys.length > 0) {
2413
- console.log(` ${import_picocolors9.default.bold("Environment Variables:")} ${import_picocolors9.default.cyan(String(envKeys.length))}`);
2414
- for (const [k, v] of Object.entries(config.env ?? {})) {
2415
- console.log(` ${import_picocolors9.default.bold(k)}=${import_picocolors9.default.dim(maskSecretValue(k, v))}`);
2416
- }
2417
- } else {
2418
- console.log(` ${import_picocolors9.default.bold("Environment Variables:")} ${import_picocolors9.default.dim("(none)")}`);
2419
- }
2420
- }
2421
- console.log();
2422
- };
2423
- var handleEditServerConfig = async ({
2424
- targetGroup,
2425
- isGlobal,
2426
- cwd
2427
- }) => {
2428
- const serverName = targetGroup.serverName;
2429
- let workingConfig = {
2430
- ...targetGroup.config,
2431
- args: targetGroup.config.args ? [...targetGroup.config.args] : void 0,
2432
- env: targetGroup.config.env ? { ...targetGroup.config.env } : void 0,
2433
- headers: targetGroup.config.headers ? { ...targetGroup.config.headers } : void 0
2434
- };
2435
- while (true) {
2436
- const isRemote = Boolean(workingConfig.url && workingConfig.url.length > 0);
2437
- displayServerDetails({
2438
- serverName,
2439
- config: workingConfig,
2440
- titlePrefix: "Edit Server Configuration"
2441
- });
2442
- const editChoices = isRemote ? [
2443
- { name: "Edit HTTP Headers (headers)", value: "headers" },
2444
- { name: "Edit Remote URL (url)", value: "url" },
2445
- { name: "Edit Transport Protocol (type)", value: "transport" },
2446
- { name: "Reset changes to original", value: "reset" },
2447
- { name: "Save and apply changes", value: "save" },
2448
- { name: "Cancel (discard changes)", value: "cancel" }
2449
- ] : [
2450
- { name: "Edit Environment Variables (env)", value: "env" },
2451
- { name: "Edit Command Arguments (args)", value: "args" },
2452
- { name: "Edit Executable Command (command)", value: "command" },
2453
- { name: "Reset changes to original", value: "reset" },
2454
- { name: "Save and apply changes", value: "save" },
2455
- { name: "Cancel (discard changes)", value: "cancel" }
2456
- ];
2457
- const editAction = await (0, import_prompts9.select)({
2458
- message: `What would you like to modify in [${serverName}]?`,
2459
- choices: editChoices
2460
- });
2461
- if (editAction === "cancel") {
2462
- logger.info("Modification cancelled; changes discarded");
2463
- return;
2464
- }
2465
- if (editAction === "reset") {
2466
- workingConfig = {
2467
- ...targetGroup.config,
2468
- args: targetGroup.config.args ? [...targetGroup.config.args] : void 0,
2469
- env: targetGroup.config.env ? { ...targetGroup.config.env } : void 0,
2470
- headers: targetGroup.config.headers ? { ...targetGroup.config.headers } : void 0
2471
- };
2472
- logger.info("Configuration reset to original");
2473
- continue;
2474
- }
2475
- if (editAction === "env") {
2476
- workingConfig.env = await promptEditEnvConfig(workingConfig.env ?? {});
2477
- } else if (editAction === "args") {
2478
- workingConfig.args = await promptEditArgs(workingConfig.args ?? []);
2479
- } else if (editAction === "command") {
2480
- const newCmd = await (0, import_prompts9.input)({
2481
- message: "Executable command:",
2482
- default: workingConfig.command,
2483
- validate: (val) => val.trim() ? true : "Command cannot be empty"
2484
- });
2485
- workingConfig.command = newCmd.trim();
2486
- } else if (editAction === "headers") {
2487
- workingConfig.headers = await promptEditHeadersConfig(workingConfig.headers ?? {});
2488
- } else if (editAction === "url") {
2489
- const newUrl = await (0, import_prompts9.input)({
2490
- message: "Remote server URL:",
2491
- default: workingConfig.url,
2492
- validate: (val) => {
2493
- const trimmed = val.trim();
2494
- if (!trimmed) return "URL cannot be empty";
2495
- if (!/^https?:\/\//i.test(trimmed)) {
2496
- return "Please enter a valid URL starting with http:// or https://";
2497
- }
2498
- return true;
2499
- }
2500
- });
2501
- workingConfig.url = newUrl.trim();
2502
- } else if (editAction === "transport") {
2503
- workingConfig.type = await (0, import_prompts9.select)({
2504
- message: "Select remote transport protocol:",
2505
- choices: [
2506
- { name: "HTTP", value: "http" },
2507
- { name: "SSE (Server-Sent Events)", value: "sse" }
2508
- ],
2509
- default: workingConfig.type === "sse" ? "sse" : "http"
2510
- });
2511
- } else if (editAction === "save") {
2512
- let targetAgents = targetGroup.agents;
2513
- if (targetGroup.agents.length > 1) {
2514
- targetAgents = await (0, import_prompts9.checkbox)({
2515
- message: "Select agents to update configuration (Space to toggle):",
2516
- choices: targetGroup.agents.map((a) => ({
2517
- name: `${getMcpAgentConfig(a).displayName} (${a})`,
2518
- value: a,
2519
- checked: true
2520
- })),
2521
- loop: false,
2522
- validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
2523
- });
2524
- }
2525
- const requestedTransport = workingConfig.url ? workingConfig.type ?? "http" : "stdio";
2526
- const compatibleAgents = [];
2527
- const incompatibleAgents = [];
2528
- for (const agent of targetAgents) {
2529
- const agentConfig = getMcpAgentConfig(agent);
2530
- if (isMcpTransportSupported(agentConfig, requestedTransport)) {
2531
- compatibleAgents.push(agent);
2532
- } else {
2533
- const reason = agentConfig.unsupportedTransportMessage ?? `Agent does not support ${requestedTransport} transport`;
2534
- incompatibleAgents.push({ agent, reason });
2535
- }
2536
- }
2537
- if (incompatibleAgents.length > 0) {
2538
- for (const item of incompatibleAgents) {
2539
- logger.warn(`Skipping ${import_picocolors9.default.cyan(item.agent)}: ${item.reason}`);
2540
- }
2541
- }
2542
- if (compatibleAgents.length === 0) {
2543
- logger.error(
2544
- `None of the selected agents support ${requestedTransport} transport. Cannot update.`
2545
- );
2546
- continue;
2547
- }
2548
- const agentNames = compatibleAgents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
2549
- const confirmed = await (0, import_prompts9.confirm)({
2550
- message: `Confirm updating configuration for [${serverName}] across: ${agentNames}?`,
2551
- default: true
2552
- });
2553
- if (!confirmed) {
2554
- logger.warn("Update cancelled");
2555
- continue;
2556
- }
2557
- for (const targetAgent of compatibleAgents) {
2558
- const res = installMcpServerForAgent(serverName, workingConfig, targetAgent, {
2559
- global: isGlobal,
2560
- cwd
2561
- });
2562
- if (res.success) {
2563
- logger.success(
2564
- `${import_picocolors9.default.cyan(targetAgent)}: Successfully updated configuration in ${import_picocolors9.default.dim(res.path)}`
2565
- );
2566
- } else {
2567
- logger.error(`${import_picocolors9.default.cyan(targetAgent)}: Update failed - ${res.error}`);
2568
- }
2569
- }
2570
- targetGroup.config = workingConfig;
2571
- logger.success(`Configuration for [${serverName}] updated successfully!`);
2572
- return;
2573
- }
2574
- }
2575
- };
2576
- var wizardManage = async (options = {}) => {
2577
- const cwd = options.cwd ?? process.cwd();
2578
- const isGlobal = await promptScope({
2579
- cwd,
2580
- defaultGlobal: options.global,
2581
- message: "Select MCP scope to inspect and manage:"
2582
- });
2583
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2584
- if (installed.length === 0) {
2585
- logger.warn(`No configured MCP servers found in ${isGlobal ? "global" : "project"} scope`);
2586
- return;
2587
- }
2588
- const grouped = groupInstalledServersByName(installed);
2589
- let pendingServerName = options.serverName;
2590
- while (true) {
2591
- let chosenServerName;
2592
- if (pendingServerName && grouped.has(pendingServerName)) {
2593
- chosenServerName = pendingServerName;
2594
- pendingServerName = void 0;
2595
- } else {
2596
- pendingServerName = void 0;
2597
- const choices = Array.from(grouped.values()).map((g) => {
2598
- const agentNames = g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ");
2599
- return {
2600
- name: `${import_picocolors9.default.bold(g.serverName)} ${import_picocolors9.default.dim(`(configured in: ${agentNames})`)}`,
2601
- value: g.serverName
2602
- };
2603
- });
2604
- choices.push({
2605
- name: `Back`,
2606
- value: "__back__"
2607
- });
2608
- chosenServerName = await (0, import_prompts9.select)({
2609
- message: "Select MCP server to manage or sync:",
2610
- choices
2611
- });
2612
- if (chosenServerName === "__back__") {
2613
- return;
2614
- }
2615
- }
2616
- const targetGroup = grouped.get(chosenServerName);
2617
- if (!targetGroup) continue;
2618
- displayServerDetails({
2619
- serverName: chosenServerName,
2620
- config: targetGroup.config,
2621
- agents: targetGroup.agents,
2622
- isGlobal
2623
- });
2624
- const action = await (0, import_prompts9.select)({
2625
- message: `What would you like to do with [${chosenServerName}]?`,
2626
- choices: [
2627
- {
2628
- name: "Edit server configuration",
2629
- value: "edit"
2630
- },
2631
- {
2632
- name: "Sync / clone to other agents",
2633
- value: "sync"
2634
- },
2635
- {
2636
- name: "Back to list",
2637
- value: "back"
2638
- }
2639
- ]
2640
- });
2641
- if (action === "back") continue;
2642
- if (action === "edit") {
2643
- await handleEditServerConfig({
2644
- targetGroup,
2645
- isGlobal,
2646
- cwd
2647
- });
2648
- continue;
2649
- }
2650
- if (action === "sync") {
2651
- const allAllowedAgents = isGlobal ? getMcpAgentTypes() : getMcpAgentsSupportingProjectScope();
2652
- const candidateAgents = allAllowedAgents.filter((a) => !targetGroup.agents.includes(a));
2653
- if (candidateAgents.length === 0) {
2654
- logger.info(
2655
- "All supported agents in this scope already have this MCP server configured; no sync needed"
2656
- );
2657
- continue;
2658
- }
2659
- const selectedToSync = await (0, import_prompts9.checkbox)({
2660
- message: "Select target agents to sync to (Space to select):",
2661
- choices: candidateAgents.map((a) => ({
2662
- name: `${getMcpAgentConfig(a).displayName} (${a})`,
2663
- value: a,
2664
- checked: false
2665
- })),
2666
- loop: false,
2667
- validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
2668
- });
2669
- const confirmed = await (0, import_prompts9.confirm)({
2670
- message: `Confirm syncing configuration of [${chosenServerName}] to: ${selectedToSync.join(", ")}?`,
2671
- default: true
2672
- });
2673
- if (!confirmed) {
2674
- logger.warn("Sync cancelled");
2675
- continue;
2676
- }
2677
- for (const targetAgent of selectedToSync) {
2678
- const res = installMcpServerForAgent(chosenServerName, targetGroup.config, targetAgent, {
2679
- global: isGlobal,
2680
- cwd
2681
- });
2682
- if (res.success) {
2683
- logger.success(`${import_picocolors9.default.cyan(targetAgent)}: Successfully synced to ${import_picocolors9.default.dim(res.path)}`);
2684
- targetGroup.agents.push(targetAgent);
2685
- } else {
2686
- logger.error(`${import_picocolors9.default.cyan(targetAgent)}: Sync failed - ${res.error}`);
2687
- }
2688
- }
2689
- }
2690
- }
1851
+ var queryGroupedInstalledServers = (options = {}) => {
1852
+ const installed = listInstalledMcpServers(options);
1853
+ return groupInstalledServersByName(installed);
2691
1854
  };
2692
1855
 
2693
- // src/interactive/wizard-remove.ts
2694
- var import_prompts10 = require("@inquirer/prompts");
2695
- var import_picocolors10 = __toESM(require("picocolors"), 1);
2696
- var wizardRemove = async (options = {}) => {
1856
+ // src/update-mcp-server.ts
1857
+ var updateMcpServer = (options) => {
1858
+ const isGlobal = options.global ?? false;
2697
1859
  const cwd = options.cwd ?? process.cwd();
2698
- const isGlobal = await promptScope({
2699
- cwd,
2700
- defaultGlobal: options.global,
2701
- message: "Select scope to remove MCP server from:"
2702
- });
2703
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2704
- if (installed.length === 0) {
2705
- logger.warn(`No installed MCP servers found in ${isGlobal ? "global" : "project"} scope`);
2706
- return false;
2707
- }
2708
- const serverMap = groupInstalledServersByName(installed);
2709
- let serverName = options.name;
2710
- if (!serverName) {
2711
- const choices = Array.from(serverMap.values()).map((g) => ({
2712
- name: `${import_picocolors10.default.bold(g.serverName)} ${import_picocolors10.default.dim(`(installed in: ${g.agents.map((a) => getMcpAgentConfig(a).displayName).join(", ")})`)}`,
2713
- value: g.serverName
2714
- }));
2715
- serverName = await (0, import_prompts10.select)({
2716
- message: "Select MCP server to remove:",
2717
- choices
1860
+ let previousConfig = options.previousConfig;
1861
+ if (!previousConfig) {
1862
+ const existing = listInstalledMcpServers({
1863
+ global: isGlobal,
1864
+ cwd,
1865
+ agents: options.agents
2718
1866
  });
1867
+ const found = existing.find((s) => s.serverName === options.serverName && s.serverConfig);
1868
+ if (found) {
1869
+ previousConfig = found.serverConfig;
1870
+ }
2719
1871
  }
2720
- const installedAgents = serverMap.get(serverName)?.agents || [];
2721
- if (installedAgents.length === 0) {
2722
- logger.warn(`No agents found with [${serverName}] installed`);
2723
- return false;
2724
- }
1872
+ const serverConfig = sanitizeUpdatedServerConfig(options.config, previousConfig);
2725
1873
  let targetAgents = options.agents;
2726
1874
  if (!targetAgents || targetAgents.length === 0) {
2727
- targetAgents = await (0, import_prompts10.checkbox)({
2728
- message: `Select agents to remove [${serverName}] from:`,
2729
- choices: installedAgents.map((agent) => ({
2730
- name: `${getMcpAgentConfig(agent)?.displayName ?? agent} (${agent})`,
2731
- value: agent,
2732
- checked: true
2733
- })),
2734
- loop: false,
2735
- validate: (ans) => ans.length === 0 ? "Please select at least one agent" : true
2736
- });
2737
- } else {
2738
- const validAgents = targetAgents.filter((agent) => installedAgents.includes(agent));
2739
- if (validAgents.length === 0) {
2740
- logger.warn(`None of the specified agents (${targetAgents.join(", ")}) have [${serverName}] installed`);
2741
- return false;
2742
- }
2743
- targetAgents = validAgents;
1875
+ const existing = listInstalledMcpServers({ global: isGlobal, cwd });
1876
+ targetAgents = existing.filter((s) => s.serverName === options.serverName).map((s) => s.agent);
2744
1877
  }
2745
- const confirmed = await (0, import_prompts10.confirm)({
2746
- message: `Confirm removing MCP server [${serverName}] from ${targetAgents.join(", ")}?`,
2747
- default: true
1878
+ const requestedTransport = serverConfig.url ? serverConfig.type ?? "http" : "stdio";
1879
+ const { allAgents, incompatible } = resolveTargetAgents({
1880
+ requested: targetAgents,
1881
+ global: isGlobal,
1882
+ cwd,
1883
+ transport: requestedTransport
2748
1884
  });
2749
- if (!confirmed) {
2750
- logger.warn("Operation cancelled");
2751
- return false;
2752
- }
2753
- const results = removeMcpServer({
2754
- name: serverName,
2755
- agents: targetAgents,
1885
+ const results = installToCompatibleAgents(options.serverName, serverConfig, {
1886
+ allAgents,
1887
+ incompatible,
2756
1888
  global: isGlobal,
2757
1889
  cwd
2758
1890
  });
2759
- let removedCount = 0;
2760
- for (const res of results) {
2761
- if (res.removed) {
2762
- logger.success(`${import_picocolors10.default.cyan(res.agent)}: Successfully removed from ${import_picocolors10.default.dim(res.path)}`);
2763
- removedCount++;
2764
- } else if (res.error) {
2765
- logger.error(`${import_picocolors10.default.cyan(res.agent)}: Failed to remove - ${res.error}`);
2766
- }
2767
- }
2768
- if (removedCount > 0) {
2769
- logger.success(`Successfully removed [${serverName}] from ${removedCount} agent(s)`);
2770
- return true;
2771
- }
2772
- logger.warn(`Failed to remove [${serverName}] from specified agents`);
2773
- return false;
1891
+ return {
1892
+ serverName: options.serverName,
1893
+ config: serverConfig,
1894
+ results,
1895
+ incompatible
1896
+ };
2774
1897
  };
2775
1898
 
2776
- // src/interactive/main-menu.ts
2777
- var mainMenu = async () => {
2778
- console.log();
2779
- console.log(import_picocolors11.default.bold(import_picocolors11.default.cyan("mcps - Cross-Platform MCP Manager for AI Coding Agents")));
2780
- console.log(import_picocolors11.default.dim("Cross-platform MCP server configuration & synchronization tool"));
2781
- console.log();
2782
- while (true) {
2783
- try {
2784
- const action = await (0, import_prompts11.select)({
2785
- message: "Select an action:",
2786
- choices: [
2787
- {
2788
- name: "Add MCP Server",
2789
- value: "add"
2790
- },
2791
- {
2792
- name: "Manage & Sync Installed MCP Servers",
2793
- value: "manage"
2794
- },
2795
- {
2796
- name: "Remove MCP Server",
2797
- value: "remove"
2798
- },
2799
- {
2800
- name: "Exit",
2801
- value: "exit"
2802
- }
2803
- ]
2804
- });
2805
- if (action === "exit") {
2806
- console.log(import_picocolors11.default.dim("Goodbye!"));
2807
- break;
2808
- }
2809
- if (action === "add") {
2810
- await wizardAdd();
2811
- } else if (action === "manage") {
2812
- await wizardManage();
2813
- } else if (action === "remove") {
2814
- await wizardRemove();
2815
- }
2816
- console.log();
2817
- } catch (error) {
2818
- if (error?.name === "ExitPromptError") {
2819
- console.log("\n" + import_picocolors11.default.dim("Exited."));
2820
- break;
2821
- }
2822
- throw error;
2823
- }
2824
- }
1899
+ // src/remove.ts
1900
+ var removeMcpServer = (options) => {
1901
+ const { allAgents } = resolveTargetAgents({
1902
+ requested: options.agents,
1903
+ all: !options.agents,
1904
+ global: options.global,
1905
+ cwd: options.cwd
1906
+ });
1907
+ const storeResults = agentConfigStore.removeServers(allAgents, options.name, {
1908
+ global: options.global,
1909
+ cwd: options.cwd
1910
+ });
1911
+ return storeResults.filter((r) => r.removed || Boolean(r.error));
2825
1912
  };
2826
1913
 
2827
- // src/cli/manage.ts
2828
- var import_commander = require("commander");
2829
- var import_picocolors12 = __toESM(require("picocolors"), 1);
2830
-
2831
- // src/utils/parse-key-value-list.ts
2832
- var parseKeyValueList = (entries, separator) => {
2833
- if (!entries || entries.length === 0) return {};
2834
- const result = {};
2835
- for (const entry of entries) {
2836
- const splitIndex = entry.indexOf(separator);
2837
- if (splitIndex === -1) {
2838
- throw new Error(`Invalid entry "${entry}": expected "${separator}" separator`);
2839
- }
2840
- const key = entry.slice(0, splitIndex).trim();
2841
- const value = entry.slice(splitIndex + separator.length).trim();
2842
- if (!key) throw new Error(`Invalid entry "${entry}": empty key`);
2843
- result[key] = value;
1914
+ // src/utils/mask-secret.ts
1915
+ var SECRET_KEY_PATTERN = /(token|key|secret|password|passwd|auth|credential)/i;
1916
+ var maskSecretValue = (key, value) => {
1917
+ if (!SECRET_KEY_PATTERN.test(key) || value.length <= 4) {
1918
+ return value;
2844
1919
  }
2845
- return result;
2846
- };
2847
-
2848
- // src/cli/manage.ts
2849
- var resolveTransport = (input7) => {
2850
- if (!input7) return void 0;
2851
- if (input7 === "http" || input7 === "sse") return input7;
2852
- throw new Error(`Unsupported transport "${input7}" (expected: http, sse)`);
1920
+ return `${value.slice(0, 2)}***${value.slice(-2)}`;
2853
1921
  };
2854
- var mcpManageCommand = new import_commander.Command("manage").description("Inspect, modify, and sync installed MCP servers across coding agents").argument("[server-name]", "Optional server name to inspect or manage").option("-a, --agent <agents...>", "Target specific agents for update").option("-g, --global", "Manage global scope servers instead of project").option("-t, --transport <type>", "Transport type for remote servers (http or sse)").option("--header <header...>", "HTTP header (Header: Value), repeatable").option("--env <env...>", "Env var for stdio servers (KEY=VALUE), repeatable").option("--args <args...>", "CLI arguments for stdio/package servers").option("--command <command>", "Executable command for stdio servers").option("--url <url>", "Remote endpoint URL").option("-y, --yes", "Skip interactive prompts").action(async (serverName, options) => {
2855
- try {
2856
- const cwd = process.cwd();
2857
- const isGlobal = Boolean(options.global);
2858
- const hasModifications = options.command !== void 0 || options.args !== void 0 || options.env !== void 0 || options.header !== void 0 || options.url !== void 0 || options.transport !== void 0;
2859
- const isInteractive = Boolean(process.stdin.isTTY && !options.yes);
2860
- if (hasModifications) {
2861
- if (!serverName) {
2862
- logger.error('Missing required argument: "server-name" when passing modification flags.');
2863
- process.exitCode = 1;
2864
- return;
2865
- }
2866
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2867
- const grouped = groupInstalledServersByName(installed);
2868
- const targetGroup = grouped.get(serverName);
2869
- if (!targetGroup) {
2870
- logger.error(
2871
- `MCP server "${serverName}" is not configured in ${isGlobal ? "global" : "project"} scope.`
2872
- );
2873
- process.exitCode = 1;
2874
- return;
2875
- }
2876
- const updatedConfig = {
2877
- ...targetGroup.config,
2878
- args: targetGroup.config.args ? [...targetGroup.config.args] : void 0,
2879
- env: targetGroup.config.env ? { ...targetGroup.config.env } : void 0,
2880
- headers: targetGroup.config.headers ? { ...targetGroup.config.headers } : void 0
2881
- };
2882
- if (options.command !== void 0) {
2883
- updatedConfig.command = options.command;
2884
- }
2885
- if (options.args !== void 0) {
2886
- updatedConfig.args = options.args;
2887
- }
2888
- if (options.url !== void 0) {
2889
- updatedConfig.url = options.url;
2890
- }
2891
- if (options.transport !== void 0) {
2892
- updatedConfig.type = resolveTransport(options.transport);
2893
- }
2894
- if (options.env !== void 0) {
2895
- const parsedEnv = parseKeyValueList(options.env, "=");
2896
- updatedConfig.env = { ...updatedConfig.env ?? {}, ...parsedEnv };
2897
- }
2898
- if (options.header !== void 0) {
2899
- const parsedHeaders = parseKeyValueList(options.header, ":");
2900
- updatedConfig.headers = { ...updatedConfig.headers ?? {}, ...parsedHeaders };
2901
- }
2902
- const targetAgents = options.agent ? parseMcpAgentList(options.agent) ?? targetGroup.agents : targetGroup.agents;
2903
- const requestedTransport = updatedConfig.url ? updatedConfig.type ?? "http" : "stdio";
2904
- const compatibleAgents = [];
2905
- for (const agent of targetAgents) {
2906
- const agentConfig = getMcpAgentConfig(agent);
2907
- if (isMcpTransportSupported(agentConfig, requestedTransport)) {
2908
- compatibleAgents.push(agent);
2909
- } else {
2910
- const reason = agentConfig.unsupportedTransportMessage ?? `Agent does not support ${requestedTransport} transport`;
2911
- logger.warn(`Skipping ${import_picocolors12.default.cyan(agent)}: ${reason}`);
2912
- }
2913
- }
2914
- if (compatibleAgents.length === 0) {
2915
- logger.error(
2916
- `None of the target agents support ${requestedTransport} transport. Update aborted.`
2917
- );
2918
- process.exitCode = 1;
2919
- return;
2920
- }
2921
- logger.info(
2922
- `Updating ${import_picocolors12.default.bold(serverName)} across ${import_picocolors12.default.cyan(String(compatibleAgents.length))} agent(s)...`
2923
- );
2924
- let allSuccess = true;
2925
- for (const agent of compatibleAgents) {
2926
- const res = installMcpServerForAgent(serverName, updatedConfig, agent, {
2927
- global: isGlobal,
2928
- cwd
2929
- });
2930
- if (res.success) {
2931
- logger.success(`${import_picocolors12.default.cyan(agent)}: Successfully updated in ${import_picocolors12.default.dim(res.path)}`);
2932
- } else {
2933
- allSuccess = false;
2934
- logger.error(`${import_picocolors12.default.cyan(agent)}: Update failed - ${res.error}`);
2935
- }
2936
- }
2937
- if (!allSuccess) {
2938
- process.exitCode = 1;
2939
- }
2940
- return;
2941
- }
2942
- if (!isInteractive) {
2943
- if (!serverName) {
2944
- logger.error(
2945
- 'Missing required argument: "server-name" for non-interactive manage command. Specify a server name or use interactive terminal.'
2946
- );
2947
- process.exitCode = 1;
2948
- return;
2949
- }
2950
- const installed = listInstalledMcpServers({ global: isGlobal, cwd });
2951
- const grouped = groupInstalledServersByName(installed);
2952
- const targetGroup = grouped.get(serverName);
2953
- if (!targetGroup) {
2954
- logger.error(
2955
- `MCP server "${serverName}" is not configured in ${isGlobal ? "global" : "project"} scope.`
2956
- );
2957
- process.exitCode = 1;
2958
- return;
2959
- }
2960
- displayServerDetails({
2961
- serverName,
2962
- config: targetGroup.config,
2963
- agents: targetGroup.agents,
2964
- isGlobal
2965
- });
2966
- return;
2967
- }
2968
- await wizardManage({
2969
- global: options.global,
2970
- serverName
2971
- });
2972
- } catch (error) {
2973
- if (error && typeof error === "object" && "name" in error && error.name === "ExitPromptError") {
2974
- process.exit(0);
2975
- }
2976
- logger.error(toErrorMessage(error));
2977
- process.exitCode = 1;
1922
+ var SECRET_HEADER_PATTERN = /(authorization|token|key|secret|auth)/i;
1923
+ var maskSecretHeader = (key, value) => {
1924
+ if (!SECRET_HEADER_PATTERN.test(key) || value.length <= 8) {
1925
+ return value;
2978
1926
  }
2979
- });
1927
+ return `${value.slice(0, 4)}***${value.slice(-3)}`;
1928
+ };
2980
1929
  // Annotate the CommonJS export names for ESM import in node:
2981
1930
  0 && (module.exports = {
2982
1931
  AgentConfigStore,
2983
1932
  DEFAULT_REMOTE_TRANSPORT,
2984
- NPX_COMMAND,
2985
- NPX_DASH_Y,
2986
- add,
1933
+ SECRET_HEADER_PATTERN,
1934
+ SECRET_KEY_PATTERN,
2987
1935
  agentConfigStore,
1936
+ applyServerConfigDelta,
2988
1937
  buildMcpServerConfig,
2989
1938
  createAgentTransform,
2990
1939
  detectGloballyInstalledMcpAgents,
2991
1940
  detectProjectInstalledMcpAgents,
2992
- displayServerDetails,
1941
+ detectUpdateTransition,
2993
1942
  extractPackageName,
2994
- formatArgsString,
2995
- formatEnvText,
2996
- formatHeadersText,
1943
+ getCandidateAgentsForScope,
1944
+ getCoHostedAgents,
2997
1945
  getMcpAgentConfig,
2998
1946
  getMcpAgentTypes,
2999
1947
  getMcpAgentsSupportingProjectScope,
3000
- groupInstalledServersByName,
3001
- install,
3002
1948
  installMcpServer,
3003
1949
  installMcpServerForAgent,
3004
1950
  installMcpServerForAgents,
1951
+ installToCompatibleAgents,
3005
1952
  isMcpAgentType,
3006
1953
  isMcpTransportSupported,
3007
1954
  isRemoteMcpSource,
3008
1955
  isRemoteServerConfig,
3009
1956
  isStdioServerConfig,
3010
- list,
3011
1957
  listInstalledMcpServers,
3012
- listServersInConfigFile,
3013
- mainMenu,
3014
1958
  maskSecretHeader,
3015
1959
  maskSecretValue,
3016
- mcpAgentAliases,
3017
- mcpAgents,
3018
- mcpManageCommand,
3019
- normalizeServerConfig,
3020
- parseArgsString,
3021
- parseEnvText,
3022
- parseHeadersText,
3023
1960
  parseMcpSource,
3024
1961
  parseServerConfig,
3025
- parseSource,
3026
- promptArgsConfig,
3027
- promptEditArgs,
3028
- promptEditEnvConfig,
3029
- promptEditHeadersConfig,
3030
- promptEditKeyValueConfig,
3031
- promptEnvConfig,
3032
- promptHeadersConfig,
3033
- promptScope,
3034
- promptScopeAndAgents,
3035
- readConfigFile,
3036
- remove,
1962
+ queryGroupedInstalledServers,
3037
1963
  removeMcpServer,
3038
- removeMcpServerFromAgent,
3039
- removeServerFromConfigFile,
1964
+ resolveConfigClusters,
3040
1965
  resolveMcpAgentAlias,
3041
1966
  resolveMcpConfigTarget,
3042
1967
  resolveTargetAgents,
3043
- transformServerConfig,
1968
+ sanitizeUpdatedServerConfig,
1969
+ sortAgentsByClusters,
1970
+ toRemoteServerConfig,
1971
+ toStdioServerConfig,
3044
1972
  transformServerConfigForAgent,
3045
- updateMcpServerForAgent,
3046
- updateMcpServerForAgents,
3047
- wizardAdd,
3048
- wizardManage,
3049
- wizardRemove,
3050
- writeServerToConfigFile
1973
+ updateMcpServer
3051
1974
  });