@kwonye/mcpx 0.1.93 → 0.1.95

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.
Files changed (80) hide show
  1. package/dist/adapters/claude-desktop.d.ts.map +1 -1
  2. package/dist/adapters/claude.d.ts +2 -1
  3. package/dist/adapters/claude.d.ts.map +1 -1
  4. package/dist/adapters/cline.d.ts +3 -1
  5. package/dist/adapters/cline.d.ts.map +1 -1
  6. package/dist/adapters/codex.d.ts +2 -1
  7. package/dist/adapters/codex.d.ts.map +1 -1
  8. package/dist/adapters/cursor.d.ts +2 -1
  9. package/dist/adapters/cursor.d.ts.map +1 -1
  10. package/dist/adapters/hermes.d.ts +11 -0
  11. package/dist/adapters/hermes.d.ts.map +1 -0
  12. package/dist/adapters/index.d.ts.map +1 -1
  13. package/dist/adapters/kiro.d.ts.map +1 -1
  14. package/dist/adapters/openclaw.d.ts +11 -0
  15. package/dist/adapters/openclaw.d.ts.map +1 -0
  16. package/dist/adapters/opencode.d.ts +2 -1
  17. package/dist/adapters/opencode.d.ts.map +1 -1
  18. package/dist/adapters/qwen.d.ts.map +1 -1
  19. package/dist/adapters/utils/index.d.ts +18 -4
  20. package/dist/adapters/utils/index.d.ts.map +1 -1
  21. package/dist/adapters/vscode.d.ts +2 -1
  22. package/dist/adapters/vscode.d.ts.map +1 -1
  23. package/dist/{cli-y7ba5qvr.js → cli-28fsxhe3.js} +430 -156
  24. package/dist/{cli-qvkpwy9w.js → cli-njtbb11m.js} +138 -43
  25. package/dist/{cli-8vxva0cb.js → cli-tk8bggg5.js} +81 -8
  26. package/dist/{cli-np0wwh6m.js → cli-v27bt25d.js} +157 -11
  27. package/dist/{cli-kwyp9ywh.js → cli-wxw1t3rr.js} +941 -303
  28. package/dist/cli.d.ts.map +1 -1
  29. package/dist/cli.js +388 -77
  30. package/dist/compat/hermes.d.ts +7 -0
  31. package/dist/compat/hermes.d.ts.map +1 -0
  32. package/dist/compat/index.d.ts.map +1 -1
  33. package/dist/compat/openclaw.d.ts +7 -0
  34. package/dist/compat/openclaw.d.ts.map +1 -0
  35. package/dist/{config-2s5nsm2g.js → config-5p08cb0c.js} +6 -2
  36. package/dist/core/add-command.d.ts.map +1 -1
  37. package/dist/core/config-repair.d.ts.map +1 -1
  38. package/dist/core/config-store.d.ts +33 -0
  39. package/dist/core/config-store.d.ts.map +1 -0
  40. package/dist/core/config.d.ts +13 -1
  41. package/dist/core/config.d.ts.map +1 -1
  42. package/dist/core/daemon.d.ts +5 -1
  43. package/dist/core/daemon.d.ts.map +1 -1
  44. package/dist/core/errors.d.ts +18 -0
  45. package/dist/core/errors.d.ts.map +1 -0
  46. package/dist/core/index.d.ts +6 -3
  47. package/dist/core/index.d.ts.map +1 -1
  48. package/dist/core/index.js +30 -6
  49. package/dist/core/managed-index-lock.d.ts +2 -0
  50. package/dist/core/managed-index-lock.d.ts.map +1 -0
  51. package/dist/core/oauth.d.ts.map +1 -1
  52. package/dist/core/plugin-host.d.ts +2 -0
  53. package/dist/core/plugin-host.d.ts.map +1 -0
  54. package/dist/core/plugin-manager.d.ts +19 -6
  55. package/dist/core/plugin-manager.d.ts.map +1 -1
  56. package/dist/core/plugin-parse.d.ts.map +1 -1
  57. package/dist/core/plugin-projections.d.ts +6 -2
  58. package/dist/core/plugin-projections.d.ts.map +1 -1
  59. package/dist/core/registry.d.ts +4 -1
  60. package/dist/core/registry.d.ts.map +1 -1
  61. package/dist/core/secrets.d.ts.map +1 -1
  62. package/dist/core/server-auth.d.ts.map +1 -1
  63. package/dist/core/skill-projections.d.ts +3 -0
  64. package/dist/core/skill-projections.d.ts.map +1 -0
  65. package/dist/core/status.d.ts +1 -0
  66. package/dist/core/status.d.ts.map +1 -1
  67. package/dist/core/sync.d.ts +19 -2
  68. package/dist/core/sync.d.ts.map +1 -1
  69. package/dist/core/update-manager.d.ts.map +1 -1
  70. package/dist/gateway/server.d.ts +4 -1
  71. package/dist/gateway/server.d.ts.map +1 -1
  72. package/dist/plugin-host-954a7tf7.js +129 -0
  73. package/dist/{plugin-manager-1sc4b6am.js → plugin-manager-m8ar2271.js} +12 -4
  74. package/dist/{registry-mztfy0cz.js → registry-n12thgf2.js} +1 -1
  75. package/dist/types.d.ts +5 -1
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/version.d.ts +1 -1
  78. package/dist/version.d.ts.map +1 -1
  79. package/package.json +3 -2
  80. package/dist/cli-m2645syd.js +0 -2199
@@ -1,14 +1,14 @@
1
1
  import {
2
+ persistSyncState,
2
3
  syncAllClients
3
- } from "./cli-kwyp9ywh.js";
4
+ } from "./cli-wxw1t3rr.js";
4
5
  import {
5
6
  loadConfig,
6
- loadMergedConfig,
7
7
  saveConfig
8
- } from "./cli-np0wwh6m.js";
8
+ } from "./cli-v27bt25d.js";
9
9
  import {
10
10
  SecretsManager
11
- } from "./cli-8vxva0cb.js";
11
+ } from "./cli-tk8bggg5.js";
12
12
  import {
13
13
  ensureDir,
14
14
  getConfigPath,
@@ -468,6 +468,10 @@ var HOOKS_JSON_REL = path4.join("hooks", "hooks.json");
468
468
  var SKILLS_DIR = "skills";
469
469
  var COMMANDS_DIR = "commands";
470
470
  var AGENTS_DIR = "agents";
471
+ function sanitizeComponentId(id) {
472
+ const stripped = id.replace(/[\/\\]/g, "_").replace(/^\.+/, "");
473
+ return stripped.length > 0 ? stripped : "unnamed";
474
+ }
471
475
  function readManifest(pluginRoot) {
472
476
  const manifestPath = path4.join(pluginRoot, PLUGIN_JSON_REL);
473
477
  if (!fs4.existsSync(manifestPath)) {
@@ -528,7 +532,7 @@ function discoverSkills(pluginRoot) {
528
532
  const content = fs4.readFileSync(skillPath, "utf8");
529
533
  const { frontmatter } = parseFrontmatter(content);
530
534
  results.push({
531
- id: frontmatter.name ?? path4.basename(entry.name, ".md"),
535
+ id: sanitizeComponentId(frontmatter.name ?? path4.basename(entry.name, ".md")),
532
536
  type: "skills",
533
537
  path: skillPath,
534
538
  description: frontmatter.description
@@ -541,7 +545,7 @@ function discoverSkills(pluginRoot) {
541
545
  const content = fs4.readFileSync(skillMdPath, "utf8");
542
546
  const { frontmatter } = parseFrontmatter(content);
543
547
  results.push({
544
- id: frontmatter.name ?? entry.name,
548
+ id: sanitizeComponentId(frontmatter.name ?? entry.name),
545
549
  type: "skills",
546
550
  path: skillMdPath,
547
551
  description: frontmatter.description
@@ -564,7 +568,7 @@ function discoverCommands(pluginRoot) {
564
568
  const content = fs4.readFileSync(cmdPath, "utf8");
565
569
  const { frontmatter } = parseFrontmatter(content);
566
570
  results.push({
567
- id: frontmatter.name ?? path4.basename(entry.name, ".md"),
571
+ id: sanitizeComponentId(frontmatter.name ?? path4.basename(entry.name, ".md")),
568
572
  type: "commands",
569
573
  path: cmdPath,
570
574
  description: frontmatter.description
@@ -587,7 +591,7 @@ function discoverAgents(pluginRoot) {
587
591
  const content = fs4.readFileSync(agentPath, "utf8");
588
592
  const { frontmatter } = parseFrontmatter(content);
589
593
  results.push({
590
- id: frontmatter.name ?? path4.basename(entry.name, ".md"),
594
+ id: sanitizeComponentId(frontmatter.name ?? path4.basename(entry.name, ".md")),
591
595
  type: "agents",
592
596
  path: agentPath,
593
597
  description: frontmatter.description
@@ -644,6 +648,9 @@ function discoverComponents(pluginRoot) {
644
648
  function hasManifest(pluginRoot) {
645
649
  return fs4.existsSync(path4.join(pluginRoot, PLUGIN_JSON_REL));
646
650
  }
651
+ function resolvePluginVars(value, pluginRoot, dataDir) {
652
+ return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRoot).replace(/\$\{DATA\}/g, dataDir).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, pluginRoot);
653
+ }
647
654
 
648
655
  // src/core/plugin-manager.ts
649
656
  class PluginManager {
@@ -691,11 +698,18 @@ class PluginManager {
691
698
  if (config.plugins[pluginId]) {
692
699
  throw new Error(`Plugin ${pluginId} is already installed`);
693
700
  }
701
+ const serverNames = info.components.mcpServers.map((s) => `${pluginName}__${s.id}`);
702
+ for (const existing of Object.values(config.plugins)) {
703
+ for (const sn of serverNames) {
704
+ if (existing.serverNames.includes(sn)) {
705
+ throw new Error(`Server name collision: "${sn}" is already claimed by plugin "${existing.name}". Use --name to specify a different plugin name.`);
706
+ }
707
+ }
708
+ }
694
709
  const dataDir = path5.join(getPluginDataRoot(), pluginId);
695
710
  ensureDir(dataDir);
696
711
  const enabled = options?.enabled ?? true;
697
712
  const discovered = info.components;
698
- const serverNames = discovered.mcpServers.map((s) => `${pluginName}__${s.id}`);
699
713
  const plugin = {
700
714
  id: pluginId,
701
715
  name: pluginName,
@@ -727,17 +741,17 @@ class PluginManager {
727
741
  const approved = plugin.approvals?.mcpServers !== false;
728
742
  config.servers[nsName] = {
729
743
  transport: "stdio",
730
- command: mcp.command,
731
- args: mcp.args,
732
- env: mcp.env,
733
- cwd: mcp.cwd || plugin.root,
744
+ command: "npx",
745
+ args: ["-y", "@kwonye/mcpx@latest", "plugin-host", pluginId, mcp.id],
734
746
  enabled: enabled && approved
735
747
  };
736
748
  }
737
749
  saveConfig(config, this.configPath);
738
750
  this.lifecycle.initData(pluginId);
739
- const mergedConfig = loadMergedConfig(this.configPath);
740
- syncAllClients(mergedConfig, this.secrets);
751
+ const syncConfig = loadConfig(this.configPath);
752
+ const summary = syncAllClients(syncConfig, this.secrets);
753
+ persistSyncState(summary, syncConfig);
754
+ saveConfig(syncConfig, this.configPath);
741
755
  return plugin;
742
756
  }
743
757
  async updatePlugin(pluginId) {
@@ -762,18 +776,18 @@ class PluginManager {
762
776
  const nsName = `${pluginName}__${mcp.id}`;
763
777
  config.servers[nsName] = {
764
778
  transport: "stdio",
765
- command: mcp.command,
766
- args: mcp.args,
767
- env: mcp.env,
768
- cwd: mcp.cwd || current.root,
779
+ command: "npx",
780
+ args: ["-y", "@kwonye/mcpx@latest", "plugin-host", pluginId, mcp.id],
769
781
  enabled: current.enabled && approved
770
782
  };
771
783
  }
772
784
  current.approvals = {};
773
785
  config.plugins[pluginId] = current;
774
786
  saveConfig(config, this.configPath);
775
- const mergedConfig = loadMergedConfig(this.configPath);
776
- syncAllClients(mergedConfig, this.secrets);
787
+ const syncConfig = loadConfig(this.configPath);
788
+ const summary = syncAllClients(syncConfig, this.secrets);
789
+ persistSyncState(summary, syncConfig);
790
+ saveConfig(syncConfig, this.configPath);
777
791
  return current;
778
792
  }
779
793
  async uninstallPlugin(pluginId, keepData = false) {
@@ -791,8 +805,10 @@ class PluginManager {
791
805
  if (!keepData) {
792
806
  await this.lifecycle.removeData(pluginId);
793
807
  }
794
- const mergedConfig = loadMergedConfig(this.configPath);
795
- syncAllClients(mergedConfig, this.secrets);
808
+ const syncConfig = loadConfig(this.configPath);
809
+ const summary = syncAllClients(syncConfig, this.secrets);
810
+ persistSyncState(summary, syncConfig);
811
+ saveConfig(syncConfig, this.configPath);
796
812
  }
797
813
  async enablePlugin(pluginId) {
798
814
  const config = loadConfig(this.configPath);
@@ -803,8 +819,10 @@ class PluginManager {
803
819
  throw new Error(`Plugin ${pluginId} not found`);
804
820
  plugin.enabled = true;
805
821
  saveConfig(config, this.configPath);
806
- const mergedConfig = loadMergedConfig(this.configPath);
807
- syncAllClients(mergedConfig, this.secrets);
822
+ const syncConfig = loadConfig(this.configPath);
823
+ const summary = syncAllClients(syncConfig, this.secrets);
824
+ persistSyncState(summary, syncConfig);
825
+ saveConfig(syncConfig, this.configPath);
808
826
  }
809
827
  async disablePlugin(pluginId) {
810
828
  const config = loadConfig(this.configPath);
@@ -815,8 +833,10 @@ class PluginManager {
815
833
  throw new Error(`Plugin ${pluginId} not found`);
816
834
  plugin.enabled = false;
817
835
  saveConfig(config, this.configPath);
818
- const mergedConfig = loadMergedConfig(this.configPath);
819
- syncAllClients(mergedConfig, this.secrets);
836
+ const syncConfig = loadConfig(this.configPath);
837
+ const summary = syncAllClients(syncConfig, this.secrets);
838
+ persistSyncState(summary, syncConfig);
839
+ saveConfig(syncConfig, this.configPath);
820
840
  }
821
841
  async approveComponent(pluginId, component) {
822
842
  const config = loadConfig(this.configPath);
@@ -836,8 +856,10 @@ class PluginManager {
836
856
  }
837
857
  }
838
858
  saveConfig(config, this.configPath);
839
- const mergedConfig = loadMergedConfig(this.configPath);
840
- syncAllClients(mergedConfig, this.secrets);
859
+ const syncConfig = loadConfig(this.configPath);
860
+ const summary = syncAllClients(syncConfig, this.secrets);
861
+ persistSyncState(summary, syncConfig);
862
+ saveConfig(syncConfig, this.configPath);
841
863
  }
842
864
  async getPluginStatus(pluginId) {
843
865
  const config = loadConfig(this.configPath);
@@ -865,30 +887,62 @@ async function installPlugin(source, options) {
865
887
  const manager = new PluginManager;
866
888
  return manager.installPlugin(source, options);
867
889
  }
868
- async function updatePlugin(name) {
890
+ async function updatePlugin(nameOrId) {
869
891
  const manager = new PluginManager;
870
- return manager.updatePlugin(name);
892
+ const config = loadConfig(manager["configPath"]);
893
+ const id = resolvePluginId(config, nameOrId);
894
+ return manager.updatePlugin(id);
871
895
  }
872
- async function uninstallPlugin(name, keepData) {
896
+ async function uninstallPlugin(nameOrId, keepData) {
873
897
  const manager = new PluginManager;
874
- await manager.uninstallPlugin(name, keepData);
898
+ const config = loadConfig(manager["configPath"]);
899
+ const id = resolvePluginId(config, nameOrId);
900
+ await manager.uninstallPlugin(id, keepData);
875
901
  }
876
- async function enablePlugin(name) {
902
+ async function enablePlugin(nameOrId) {
877
903
  const manager = new PluginManager;
878
- await manager.enablePlugin(name);
904
+ const config = loadConfig(manager["configPath"]);
905
+ const id = resolvePluginId(config, nameOrId);
906
+ await manager.enablePlugin(id);
879
907
  }
880
- async function disablePlugin(name) {
908
+ async function disablePlugin(nameOrId) {
881
909
  const manager = new PluginManager;
882
- await manager.disablePlugin(name);
910
+ const config = loadConfig(manager["configPath"]);
911
+ const id = resolvePluginId(config, nameOrId);
912
+ await manager.disablePlugin(id);
913
+ }
914
+ async function setPluginProjectOverride(nameOrId, projectPath, override) {
915
+ const config = loadConfig();
916
+ const id = resolvePluginId(config, nameOrId);
917
+ const plugin = config.plugins?.[id];
918
+ if (!plugin)
919
+ throw new Error(`Plugin ${id} not found`);
920
+ if (!config.projects?.[projectPath]) {
921
+ throw new Error(`Project "${projectPath}" is not registered. Run "mcpx project init" there first.`);
922
+ }
923
+ if (!plugin.projectOverrides)
924
+ plugin.projectOverrides = {};
925
+ plugin.projectOverrides[projectPath] = { ...plugin.projectOverrides[projectPath], ...override };
926
+ saveConfig(config);
927
+ const secrets = new SecretsManager;
928
+ const syncConfig = loadConfig();
929
+ const summary = syncAllClients(syncConfig, secrets);
930
+ persistSyncState(summary, syncConfig);
931
+ saveConfig(syncConfig);
883
932
  }
884
- async function approvePluginComponent(name, component) {
933
+ async function approvePluginComponent(nameOrId, component) {
885
934
  const manager = new PluginManager;
886
- await manager.approveComponent(name, component);
935
+ const config = loadConfig(manager["configPath"]);
936
+ const id = resolvePluginId(config, nameOrId);
937
+ await manager.approveComponent(id, component);
887
938
  }
888
- async function getPluginStatus(name) {
939
+ async function getPluginStatus(nameOrId) {
889
940
  const manager = new PluginManager;
890
- if (name)
891
- return manager.getPluginStatus(name);
941
+ if (nameOrId) {
942
+ const config = loadConfig(manager["configPath"]);
943
+ const id = resolvePluginId(config, nameOrId);
944
+ return manager.getPluginStatus(id);
945
+ }
892
946
  const plugins = await manager.listPlugins();
893
947
  return plugins[0] || null;
894
948
  }
@@ -896,9 +950,50 @@ async function listPlugins() {
896
950
  const manager = new PluginManager;
897
951
  return manager.listPlugins();
898
952
  }
953
+ function resolvePluginId(config, nameOrId) {
954
+ const plugins = config.plugins ?? {};
955
+ if (plugins[nameOrId])
956
+ return nameOrId;
957
+ const matches = Object.keys(plugins).filter((id) => plugins[id].name === nameOrId);
958
+ if (matches.length === 1)
959
+ return matches[0];
960
+ if (matches.length === 0) {
961
+ throw new Error(`Plugin "${nameOrId}" not found. Available: ${Object.values(plugins).map((p) => `${p.name} (${p.id})`).join(", ") || "none"}`);
962
+ }
963
+ throw new Error(`Multiple plugins match "${nameOrId}": ${matches.join(", ")}. Use the full id (<name>@<sha8>).`);
964
+ }
965
+ async function pluginConfigSet(nameOrId, key, value, projectPath) {
966
+ const manager = new PluginManager;
967
+ const config = loadConfig(manager["configPath"]);
968
+ const id = resolvePluginId(config, nameOrId);
969
+ const plugin = config.plugins?.[id];
970
+ if (!plugin)
971
+ throw new Error(`Plugin ${id} not found`);
972
+ if (projectPath) {
973
+ if (!plugin.projectOverrides)
974
+ plugin.projectOverrides = {};
975
+ if (!plugin.projectOverrides[projectPath])
976
+ plugin.projectOverrides[projectPath] = {};
977
+ if (!plugin.projectOverrides[projectPath].config)
978
+ plugin.projectOverrides[projectPath].config = {};
979
+ plugin.projectOverrides[projectPath].config[key] = value;
980
+ } else {
981
+ if (!plugin.config)
982
+ plugin.config = {};
983
+ plugin.config[key] = value;
984
+ }
985
+ saveConfig(config, manager["configPath"]);
986
+ }
987
+ async function pluginSync() {
988
+ const config = loadConfig();
989
+ const secrets = new SecretsManager;
990
+ const summary = syncAllClients(config, secrets);
991
+ persistSyncState(summary, config);
992
+ saveConfig(config);
993
+ }
899
994
  async function preparePlugin(name) {
900
995
  const manager = new PluginManager;
901
996
  await manager.preparePlugin(name);
902
997
  }
903
998
 
904
- export { PluginCache, PluginDataManager, PluginLifecycle, parseSource, readManifest, discoverComponents, hasManifest, PluginManager, inspectPlugin, installPlugin, updatePlugin, uninstallPlugin, enablePlugin, disablePlugin, approvePluginComponent, getPluginStatus, listPlugins, preparePlugin };
999
+ export { PluginCache, PluginDataManager, PluginLifecycle, parseSource, readManifest, discoverComponents, hasManifest, resolvePluginVars, PluginManager, inspectPlugin, installPlugin, updatePlugin, uninstallPlugin, enablePlugin, disablePlugin, setPluginProjectOverride, approvePluginComponent, getPluginStatus, listPlugins, resolvePluginId, pluginConfigSet, pluginSync, preparePlugin };
@@ -32,6 +32,13 @@ function removeServer(config, name, force = false) {
32
32
  throw new Error(`Server "${name}" does not exist.`);
33
33
  }
34
34
  delete config.servers[name];
35
+ if (config.projects) {
36
+ for (const project of Object.values(config.projects)) {
37
+ if (project.disabledServers) {
38
+ project.disabledServers = project.disabledServers.filter((s) => s !== name);
39
+ }
40
+ }
41
+ }
35
42
  }
36
43
  function updateServer(config, name, spec) {
37
44
  validateServerName(name);
@@ -66,14 +73,18 @@ function ensureGatewayToken(config, secrets) {
66
73
  try {
67
74
  const fileValue = fs.readFileSync(tokenPath, "utf8").trim();
68
75
  if (fileValue) {
69
- secrets.setSecret(secretName, fileValue);
70
76
  return fileValue;
71
77
  }
72
78
  } catch {}
79
+ const storeValue = secrets.getSecret(secretName);
80
+ if (storeValue) {
81
+ ensureParentDir(tokenPath);
82
+ fs.writeFileSync(tokenPath, storeValue, { mode: 384 });
83
+ return storeValue;
84
+ }
73
85
  const token = crypto.randomBytes(32).toString("base64url");
74
86
  ensureParentDir(tokenPath);
75
87
  fs.writeFileSync(tokenPath, token, { mode: 384 });
76
- secrets.setSecret(secretName, token);
77
88
  return token;
78
89
  }
79
90
  function rotateGatewayToken(config, secrets) {
@@ -82,7 +93,7 @@ function rotateGatewayToken(config, secrets) {
82
93
  const tokenPath = getGatewayTokenPath(secretName);
83
94
  ensureParentDir(tokenPath);
84
95
  fs.writeFileSync(tokenPath, token, { mode: 384 });
85
- secrets.setSecret(secretName, token);
96
+ secrets.removeSecret(secretName);
86
97
  return token;
87
98
  }
88
99
  function registerProject(globalConfig, projectPath, name) {
@@ -111,20 +122,82 @@ function setProjectServerEnabled(globalConfig, projectPath, serverName, enabled)
111
122
  if (!project) {
112
123
  throw new Error(`Project "${resolvedPath}" is not registered.`);
113
124
  }
125
+ const serverSpec = globalConfig.servers[serverName];
126
+ if (!serverSpec) {
127
+ throw new Error(`Server "${serverName}" does not exist.`);
128
+ }
114
129
  if (!project.disabledServers) {
115
130
  project.disabledServers = [];
116
131
  }
117
132
  if (enabled) {
118
133
  project.disabledServers = project.disabledServers.filter((s) => s !== serverName);
119
- const serverSpec = globalConfig.servers[serverName];
120
- if (serverSpec && !isServerEnabled(serverSpec)) {
121
- globalConfig.servers[serverName] = { ...serverSpec, enabled: true };
134
+ if (!isServerEnabled(serverSpec)) {
135
+ return { effective: false, reason: "globally_disabled" };
122
136
  }
123
137
  } else {
124
138
  if (!project.disabledServers.includes(serverName)) {
125
139
  project.disabledServers.push(serverName);
126
140
  }
127
141
  }
142
+ return { effective: true };
143
+ }
144
+
145
+ // src/core/errors.ts
146
+ class UpstreamError extends Error {
147
+ code;
148
+ upstream;
149
+ status;
150
+ wwwAuthenticate;
151
+ constructor(upstream, code, message, options) {
152
+ super(message);
153
+ this.name = "UpstreamError";
154
+ this.code = code;
155
+ this.upstream = upstream;
156
+ this.status = options?.status;
157
+ this.wwwAuthenticate = options?.wwwAuthenticate;
158
+ }
159
+ }
160
+
161
+ class SecretNotFoundError extends Error {
162
+ code = "secret_missing";
163
+ secretName;
164
+ constructor(secretName) {
165
+ super(`Secret not found: ${secretName}. Run: mcpx secret set ${secretName}`);
166
+ this.name = "SecretNotFoundError";
167
+ this.secretName = secretName;
168
+ }
169
+ }
170
+ var TIMEOUT_RE = /timed out after \d+ms/;
171
+ var UNREACHABLE_PATTERNS = [
172
+ /fetch failed/i,
173
+ /ECONNREFUSED/,
174
+ /ENOTFOUND/,
175
+ /EAI_AGAIN/,
176
+ /socket hang up/,
177
+ /ENOENT/,
178
+ /connect econnrefused/i
179
+ ];
180
+ function classifyUpstreamError(upstream, error, wwwAuthenticate) {
181
+ if (error instanceof UpstreamError)
182
+ return error;
183
+ if (error instanceof SecretNotFoundError) {
184
+ return new UpstreamError(upstream, "secret_missing", error.message);
185
+ }
186
+ const message = error instanceof Error ? error.message : String(error);
187
+ const maybeHttpError = error;
188
+ if (maybeHttpError?.code && (maybeHttpError.code === 401 || maybeHttpError.code === 403)) {
189
+ return new UpstreamError(upstream, "auth_required", message, {
190
+ status: maybeHttpError.code,
191
+ wwwAuthenticate
192
+ });
193
+ }
194
+ if (TIMEOUT_RE.test(message)) {
195
+ return new UpstreamError(upstream, "timeout", message);
196
+ }
197
+ if (UNREACHABLE_PATTERNS.some((p) => p.test(message))) {
198
+ return new UpstreamError(upstream, "unreachable", message);
199
+ }
200
+ return new UpstreamError(upstream, "upstream_error", message);
128
201
  }
129
202
 
130
203
  // src/core/secrets.ts
@@ -240,7 +313,7 @@ class SecretsManager {
240
313
  return value;
241
314
  const secret = this.getSecret(secretName);
242
315
  if (!secret)
243
- throw new Error(`Secret not found: ${secretName}`);
316
+ throw new SecretNotFoundError(secretName);
244
317
  return secret;
245
318
  }
246
319
  rotateLocalToken(secretName = "local_gateway_token") {
@@ -260,4 +333,4 @@ function readSecretValueFromStdin() {
260
333
  return content;
261
334
  }
262
335
 
263
- export { SecretsManager, readSecretValueFromStdin, validateServerName, addServer, removeServer, updateServer, setServerEnabled, getGatewayTokenSecretName, ensureGatewayToken, rotateGatewayToken, registerProject, unregisterProject, setProjectServerEnabled };
336
+ export { UpstreamError, SecretNotFoundError, classifyUpstreamError, SecretsManager, readSecretValueFromStdin, validateServerName, addServer, removeServer, updateServer, setServerEnabled, getGatewayTokenSecretName, ensureGatewayToken, rotateGatewayToken, registerProject, unregisterProject, setProjectServerEnabled };