@kwonye/mcpx 0.1.94 → 0.1.96

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 (69) 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 +7 -1
  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-n8vt375e.js → cli-2vg0te30.js} +715 -211
  24. package/dist/{cli-154shqmt.js → cli-ap2jhwnz.js} +311 -95
  25. package/dist/{cli-tk8bggg5.js → cli-remv48t3.js} +4 -2
  26. package/dist/{cli-csfsg1yf.js → cli-v27bt25d.js} +79 -7
  27. package/dist/{cli-9fmn9y72.js → cli-z1fv3ra8.js} +105 -29
  28. package/dist/cli.d.ts.map +1 -1
  29. package/dist/cli.js +362 -75
  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-dtc3c79c.js → config-5p08cb0c.js} +1 -1
  36. package/dist/core/add-command.d.ts.map +1 -1
  37. package/dist/core/config-store.d.ts +30 -0
  38. package/dist/core/config-store.d.ts.map +1 -1
  39. package/dist/core/config.d.ts.map +1 -1
  40. package/dist/core/daemon.d.ts +5 -1
  41. package/dist/core/daemon.d.ts.map +1 -1
  42. package/dist/core/errors.d.ts.map +1 -1
  43. package/dist/core/index.d.ts +2 -2
  44. package/dist/core/index.d.ts.map +1 -1
  45. package/dist/core/index.js +16 -67
  46. package/dist/core/managed-index-lock.d.ts +2 -0
  47. package/dist/core/managed-index-lock.d.ts.map +1 -0
  48. package/dist/core/oauth.d.ts.map +1 -1
  49. package/dist/core/plugin-host.d.ts +2 -0
  50. package/dist/core/plugin-host.d.ts.map +1 -0
  51. package/dist/core/plugin-manager.d.ts +19 -6
  52. package/dist/core/plugin-manager.d.ts.map +1 -1
  53. package/dist/core/plugin-parse.d.ts.map +1 -1
  54. package/dist/core/plugin-projections.d.ts.map +1 -1
  55. package/dist/core/skill-projections.d.ts +3 -0
  56. package/dist/core/skill-projections.d.ts.map +1 -0
  57. package/dist/core/status.d.ts.map +1 -1
  58. package/dist/core/sync.d.ts.map +1 -1
  59. package/dist/core/update-manager.d.ts.map +1 -1
  60. package/dist/gateway/server.d.ts.map +1 -1
  61. package/dist/plugin-host-73dvcen6.js +129 -0
  62. package/dist/{plugin-manager-1sjwfnah.js → plugin-manager-y5txkse1.js} +12 -4
  63. package/dist/{registry-n12thgf2.js → registry-bnjb2345.js} +1 -1
  64. package/dist/types.d.ts +3 -1
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.d.ts.map +1 -1
  68. package/package.json +3 -2
  69. package/dist/cli-mxmgxdm2.js +0 -2257
@@ -1,22 +1,27 @@
1
1
  import {
2
+ APP_VERSION,
2
3
  getGatewayUrl,
3
4
  persistSyncState,
4
5
  syncAllClients
5
- } from "./cli-n8vt375e.js";
6
+ } from "./cli-2vg0te30.js";
6
7
  import {
7
8
  loadConfig,
8
9
  loadMergedConfig,
9
- saveConfig
10
- } from "./cli-csfsg1yf.js";
10
+ loadProjectConfig,
11
+ saveConfig,
12
+ saveProjectConfig
13
+ } from "./cli-v27bt25d.js";
11
14
  import {
12
15
  SecretsManager,
13
16
  UpstreamError,
14
17
  classifyUpstreamError,
15
18
  ensureGatewayToken
16
- } from "./cli-tk8bggg5.js";
19
+ } from "./cli-remv48t3.js";
17
20
  import {
18
21
  ensureDir,
19
22
  ensureParentDir,
23
+ findProjectConfigPath,
24
+ getConfigPath,
20
25
  getLogPath,
21
26
  getPidPath,
22
27
  getStagedVersionPath,
@@ -27,6 +32,114 @@ import {
27
32
  isServerEnabled
28
33
  } from "./cli-s4gspqdf.js";
29
34
 
35
+ // src/core/config-store.ts
36
+ import fs from "node:fs";
37
+ import path from "node:path";
38
+ var LOCK_STALE_MS = 5000;
39
+ var LOCK_RETRY_MS = 50;
40
+ var LOCK_MAX_RETRIES = 40;
41
+ var inProcessQueues = new Map;
42
+ function getLockPath(filePath) {
43
+ return `${filePath}.lock`;
44
+ }
45
+ function acquireLock(filePath) {
46
+ const lockPath = getLockPath(filePath);
47
+ try {
48
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
49
+ const fd = fs.openSync(lockPath, "wx");
50
+ fs.writeSync(fd, String(process.pid));
51
+ fs.closeSync(fd);
52
+ return true;
53
+ } catch (err) {
54
+ if (err.code !== "EEXIST") {
55
+ return false;
56
+ }
57
+ try {
58
+ const stat = fs.statSync(lockPath);
59
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
60
+ fs.unlinkSync(lockPath);
61
+ return acquireLock(filePath);
62
+ }
63
+ } catch {
64
+ return acquireLock(filePath);
65
+ }
66
+ return false;
67
+ }
68
+ }
69
+ function releaseLock(filePath) {
70
+ try {
71
+ fs.unlinkSync(getLockPath(filePath));
72
+ } catch {}
73
+ }
74
+ function mutateFile(filePath, load, save, fn) {
75
+ const prevQueue = inProcessQueues.get(filePath) ?? Promise.resolve();
76
+ const nextQueue = prevQueue.then(() => executeWithLock(filePath, load, save, fn), () => executeWithLock(filePath, load, save, fn));
77
+ inProcessQueues.set(filePath, nextQueue.catch(() => {
78
+ return;
79
+ }));
80
+ return nextQueue;
81
+ }
82
+ async function executeWithLock(filePath, load, save, fn) {
83
+ let acquired = false;
84
+ for (let attempt = 0;attempt < LOCK_MAX_RETRIES; attempt++) {
85
+ acquired = acquireLock(filePath);
86
+ if (acquired)
87
+ break;
88
+ await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
89
+ }
90
+ if (!acquired) {
91
+ throw new Error(`Could not acquire lock for ${filePath}.`);
92
+ }
93
+ try {
94
+ const data = load(filePath);
95
+ const result = await fn(data);
96
+ save(data, filePath);
97
+ return result;
98
+ } finally {
99
+ releaseLock(filePath);
100
+ }
101
+ }
102
+ function mutateConfig(fn, configPath) {
103
+ return mutateFile(configPath ?? getConfigPath(), loadConfig, saveConfig, fn);
104
+ }
105
+ function mutateProjectConfig(projectConfigPath, fn) {
106
+ return mutateFile(projectConfigPath, (p) => loadProjectConfig(p), saveProjectConfig, fn);
107
+ }
108
+ async function mutateActiveConfig(options, mutator) {
109
+ const globalPath = getConfigPath();
110
+ if (!options.global) {
111
+ const projectConfigPath = findProjectConfigPath();
112
+ if (options.local) {
113
+ const projectPath = projectConfigPath ? path.dirname(projectConfigPath) : process.cwd();
114
+ const localPath = path.join(projectPath, ".mcpx.json");
115
+ const globalConfig = loadConfig(globalPath);
116
+ const result2 = await mutateProjectConfig(localPath, async (projectConfig) => {
117
+ const mergedConfig = {
118
+ ...globalConfig,
119
+ servers: { ...globalConfig.servers, ...projectConfig.servers }
120
+ };
121
+ const mutatorResult = await mutator(mergedConfig, { type: "project", projectPath });
122
+ const localServers = {};
123
+ for (const [name, spec] of Object.entries(mergedConfig.servers)) {
124
+ if (!(name in globalConfig.servers)) {
125
+ localServers[name] = spec;
126
+ }
127
+ }
128
+ projectConfig.servers = localServers;
129
+ return mutatorResult;
130
+ });
131
+ return { type: "project", configPath: localPath, projectPath, result: result2 };
132
+ }
133
+ if (projectConfigPath) {
134
+ const projectPath = path.dirname(projectConfigPath);
135
+ const result2 = await mutateConfig((config) => mutator(config, { type: "project", projectPath }), globalPath);
136
+ return { type: "project", configPath: globalPath, projectPath, result: result2 };
137
+ }
138
+ }
139
+ const result = await mutateConfig((config) => mutator(config, { type: "global" }), globalPath);
140
+ return { type: "global", configPath: globalPath, result };
141
+ }
142
+
30
143
  // src/core/auth-probe.ts
31
144
  var JSON_RPC_VERSION = "2.0";
32
145
  var DEFAULT_AUTH_PROBE_TIMEOUT_MS = 8000;
@@ -363,20 +476,20 @@ function maybePrefixBearer(target, value, raw = false) {
363
476
 
364
477
  // src/core/spawn-env.ts
365
478
  import os from "node:os";
366
- import path from "node:path";
367
- import fs from "node:fs";
479
+ import path2 from "node:path";
480
+ import fs2 from "node:fs";
368
481
  function existingNvmCurrentBin() {
369
- const nvmDir = process.env.NVM_DIR ?? path.join(os.homedir(), ".nvm");
370
- const current = path.join(nvmDir, "current", "bin");
371
- return fs.existsSync(current) ? current : null;
482
+ const nvmDir = process.env.NVM_DIR ?? path2.join(os.homedir(), ".nvm");
483
+ const current = path2.join(nvmDir, "current", "bin");
484
+ return fs2.existsSync(current) ? current : null;
372
485
  }
373
486
  function buildEnrichedPath(current = process.env.PATH ?? "") {
374
487
  const candidates = [
375
488
  ...current.split(":"),
376
489
  "/opt/homebrew/bin",
377
490
  "/usr/local/bin",
378
- path.join(os.homedir(), ".bun", "bin"),
379
- path.join(os.homedir(), ".local", "bin"),
491
+ path2.join(os.homedir(), ".bun", "bin"),
492
+ path2.join(os.homedir(), ".local", "bin"),
380
493
  existingNvmCurrentBin()
381
494
  ].filter((entry) => Boolean(entry));
382
495
  const seen = new Set;
@@ -653,12 +766,14 @@ async function runOAuthLogin(serverName, spec, secrets, openUrl, configPath, cod
653
766
  const summary = syncAllClients(config, secrets);
654
767
  persistSyncState(summary, config);
655
768
  saveConfig(config, configPath);
769
+ new McpxOAuthProvider(serverName, secrets).invalidateCredentials("verifier");
656
770
  return { serverName, authorized: true };
657
771
  } catch (error) {
658
772
  if (backupTokens)
659
773
  secrets.setSecret(oauthName, backupTokens);
660
774
  if (backupClient)
661
775
  secrets.setSecret(clientName, backupClient);
776
+ new McpxOAuthProvider(serverName, secrets).invalidateCredentials("verifier");
662
777
  throw error;
663
778
  } finally {
664
779
  if (callbackServer) {
@@ -717,12 +832,9 @@ async function getOAuthAccessToken(serverName, spec, secrets, options = {}) {
717
832
  return stored.tokens.access_token;
718
833
  }
719
834
 
720
- // src/version.ts
721
- var APP_VERSION = "0.1.3";
722
-
723
835
  // src/core/update.ts
724
- import fs2 from "node:fs";
725
- import path2 from "node:path";
836
+ import fs3 from "node:fs";
837
+ import path3 from "node:path";
726
838
  import { execFileSync } from "node:child_process";
727
839
  function compareVersions(a, b) {
728
840
  const partsA = a.split(".").map(Number);
@@ -761,17 +873,17 @@ async function checkForUpdates() {
761
873
  }
762
874
  function getStagedUpdate() {
763
875
  const stagedPath = getStagedVersionPath();
764
- if (!fs2.existsSync(stagedPath)) {
876
+ if (!fs3.existsSync(stagedPath)) {
765
877
  return null;
766
878
  }
767
879
  try {
768
- const raw = fs2.readFileSync(stagedPath, "utf8");
880
+ const raw = fs3.readFileSync(stagedPath, "utf8");
769
881
  const info = JSON.parse(raw);
770
882
  if (!info.version || !info.cliPath) {
771
883
  return null;
772
884
  }
773
- const absoluteCliPath = path2.isAbsolute(info.cliPath) ? info.cliPath : path2.join(getUpdatesDir(), info.cliPath);
774
- if (!fs2.existsSync(absoluteCliPath)) {
885
+ const absoluteCliPath = path3.isAbsolute(info.cliPath) ? info.cliPath : path3.join(getUpdatesDir(), info.cliPath);
886
+ if (!fs3.existsSync(absoluteCliPath)) {
775
887
  return null;
776
888
  }
777
889
  return {
@@ -787,18 +899,18 @@ function stageUpdate(version, cliPath) {
787
899
  ensureDir(updatesDir);
788
900
  const stagedInfo = {
789
901
  version,
790
- cliPath: path2.isAbsolute(cliPath) ? path2.relative(updatesDir, cliPath) : cliPath,
902
+ cliPath: path3.isAbsolute(cliPath) ? path3.relative(updatesDir, cliPath) : cliPath,
791
903
  stagedAt: new Date().toISOString()
792
904
  };
793
- fs2.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
905
+ fs3.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
794
906
  }
795
907
  function shouldUseStagedCli(stagedVersion, currentVersion) {
796
908
  return compareVersions(stagedVersion, currentVersion) > 0;
797
909
  }
798
910
  function clearStagedUpdate() {
799
911
  const stagedPath = getStagedVersionPath();
800
- if (fs2.existsSync(stagedPath)) {
801
- fs2.unlinkSync(stagedPath);
912
+ if (fs3.existsSync(stagedPath)) {
913
+ fs3.unlinkSync(stagedPath);
802
914
  }
803
915
  }
804
916
  function getStagedCliPath() {
@@ -807,59 +919,59 @@ function getStagedCliPath() {
807
919
  }
808
920
  function removeOldVersions(keepCount = 2) {
809
921
  const updatesDir = getUpdatesDir();
810
- if (!fs2.existsSync(updatesDir)) {
922
+ if (!fs3.existsSync(updatesDir)) {
811
923
  return;
812
924
  }
813
- const entries = fs2.readdirSync(updatesDir, { withFileTypes: true });
925
+ const entries = fs3.readdirSync(updatesDir, { withFileTypes: true });
814
926
  const versionDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith("v")).map((entry) => entry.name).sort((a, b) => compareVersions(b.slice(1), a.slice(1)));
815
927
  if (versionDirs.length <= keepCount) {
816
928
  return;
817
929
  }
818
930
  for (const oldVersion of versionDirs.slice(keepCount)) {
819
- const oldPath = path2.join(updatesDir, oldVersion);
931
+ const oldPath = path3.join(updatesDir, oldVersion);
820
932
  try {
821
- fs2.rmSync(oldPath, { recursive: true, force: true });
933
+ fs3.rmSync(oldPath, { recursive: true, force: true });
822
934
  } catch {}
823
935
  }
824
936
  }
825
937
 
826
938
  // src/core/update-manager.ts
827
- import fs3 from "node:fs";
828
- import path3 from "node:path";
939
+ import fs4 from "node:fs";
940
+ import path4 from "node:path";
829
941
  import { execSync, spawn } from "node:child_process";
830
- function acquireLock() {
942
+ function acquireLock2() {
831
943
  const lockPath = getUpdateLockPath();
832
944
  try {
833
- ensureDir(path3.dirname(lockPath));
834
- fs3.writeFileSync(lockPath, `${process.pid}
945
+ ensureDir(path4.dirname(lockPath));
946
+ fs4.writeFileSync(lockPath, `${process.pid}
835
947
  `, { flag: "wx" });
836
948
  return true;
837
949
  } catch {
838
950
  return false;
839
951
  }
840
952
  }
841
- function releaseLock() {
953
+ function releaseLock2() {
842
954
  const lockPath = getUpdateLockPath();
843
955
  try {
844
- fs3.unlinkSync(lockPath);
956
+ fs4.unlinkSync(lockPath);
845
957
  } catch {}
846
958
  }
847
959
  function isUpdateInProgress() {
848
960
  const lockPath = getUpdateLockPath();
849
- if (!fs3.existsSync(lockPath)) {
961
+ if (!fs4.existsSync(lockPath)) {
850
962
  return false;
851
963
  }
852
964
  try {
853
- const raw = fs3.readFileSync(lockPath, "utf8").trim();
965
+ const raw = fs4.readFileSync(lockPath, "utf8").trim();
854
966
  const pid = Number(raw);
855
967
  if (!Number.isFinite(pid) || pid <= 0) {
856
- fs3.unlinkSync(lockPath);
968
+ fs4.unlinkSync(lockPath);
857
969
  return false;
858
970
  }
859
971
  process.kill(pid, 0);
860
972
  return true;
861
973
  } catch {
862
- fs3.unlinkSync(lockPath);
974
+ fs4.unlinkSync(lockPath);
863
975
  return false;
864
976
  }
865
977
  }
@@ -870,8 +982,8 @@ async function downloadAndStageUpdate() {
870
982
  }
871
983
  const targetVersion = updateStatus.latestVersion;
872
984
  const updatesDir = getUpdatesDir();
873
- const versionDir = path3.join(updatesDir, `v${targetVersion}`);
874
- if (fs3.existsSync(versionDir)) {
985
+ const versionDir = path4.join(updatesDir, `v${targetVersion}`);
986
+ if (fs4.existsSync(versionDir)) {
875
987
  const existingStaged = getStagedUpdate();
876
988
  if (existingStaged?.version === targetVersion) {
877
989
  return { success: false, error: `Version ${targetVersion} already staged` };
@@ -883,8 +995,8 @@ async function downloadAndStageUpdate() {
883
995
  cwd: versionDir,
884
996
  stdio: ["pipe", "pipe", "pipe"]
885
997
  });
886
- const tarballPath = path3.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
887
- if (!fs3.existsSync(tarballPath)) {
998
+ const tarballPath = path4.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
999
+ if (!fs4.existsSync(tarballPath)) {
888
1000
  throw new Error("Downloaded tarball not found");
889
1001
  }
890
1002
  execSync(`tar -xzf ${tarballPath} --strip-components=1`, {
@@ -895,16 +1007,24 @@ async function downloadAndStageUpdate() {
895
1007
  cwd: versionDir,
896
1008
  stdio: ["pipe", "pipe", "pipe"]
897
1009
  });
898
- const cliPath = path3.join(versionDir, "dist", "cli.js");
899
- if (!fs3.existsSync(cliPath)) {
1010
+ const cliPath = path4.join(versionDir, "dist", "cli.js");
1011
+ if (!fs4.existsSync(cliPath)) {
900
1012
  throw new Error("CLI entry point not found in downloaded package");
901
1013
  }
1014
+ try {
1015
+ execSync(`${process.execPath} ${JSON.stringify(cliPath)} --version`, {
1016
+ stdio: ["ignore", "pipe", "pipe"],
1017
+ timeout: 1e4
1018
+ });
1019
+ } catch (smokeTestError) {
1020
+ throw new Error(`Downloaded CLI failed smoke test (--version): ${smokeTestError instanceof Error ? smokeTestError.message : String(smokeTestError)}`);
1021
+ }
902
1022
  stageUpdate(targetVersion, cliPath);
903
1023
  removeOldVersions(2);
904
1024
  return { success: true, version: targetVersion };
905
1025
  } catch (error) {
906
1026
  try {
907
- fs3.rmSync(versionDir, { recursive: true, force: true });
1027
+ fs4.rmSync(versionDir, { recursive: true, force: true });
908
1028
  } catch {}
909
1029
  return {
910
1030
  success: false,
@@ -913,10 +1033,13 @@ async function downloadAndStageUpdate() {
913
1033
  }
914
1034
  }
915
1035
  function startBackgroundUpdateCheck() {
1036
+ if (process.env.MCPX_NO_UPDATE === "1") {
1037
+ return;
1038
+ }
916
1039
  if (isUpdateInProgress()) {
917
1040
  return;
918
1041
  }
919
- if (!acquireLock()) {
1042
+ if (!acquireLock2()) {
920
1043
  return;
921
1044
  }
922
1045
  const child = spawn(process.execPath, [
@@ -933,7 +1056,7 @@ function startBackgroundUpdateCheck() {
933
1056
  });
934
1057
  child.unref();
935
1058
  setTimeout(() => {
936
- releaseLock();
1059
+ releaseLock2();
937
1060
  }, 60000);
938
1061
  }
939
1062
  async function runBackgroundUpdate() {
@@ -947,27 +1070,34 @@ async function runBackgroundUpdate() {
947
1070
  } catch (error) {
948
1071
  console.error(`[update-manager] Background update failed: ${error instanceof Error ? error.message : String(error)}`);
949
1072
  } finally {
950
- releaseLock();
1073
+ releaseLock2();
951
1074
  }
952
1075
  }
953
1076
  async function performUpdate() {
954
1077
  if (isUpdateInProgress()) {
955
1078
  return { success: false, message: "Another update is already in progress." };
956
1079
  }
957
- const result = await downloadAndStageUpdate();
958
- if (result.success && result.version) {
1080
+ if (!acquireLock2()) {
1081
+ return { success: false, message: "Another update is already in progress." };
1082
+ }
1083
+ try {
1084
+ const result = await downloadAndStageUpdate();
1085
+ if (result.success && result.version) {
1086
+ return {
1087
+ success: true,
1088
+ message: `Update to v${result.version} ready! Will activate on next run.`
1089
+ };
1090
+ }
1091
+ if (result.error === "No update available") {
1092
+ return { success: true, message: "Already on latest version." };
1093
+ }
959
1094
  return {
960
- success: true,
961
- message: `Update to v${result.version} ready! Will activate on next run.`
1095
+ success: false,
1096
+ message: result.error ?? "Update failed"
962
1097
  };
1098
+ } finally {
1099
+ releaseLock2();
963
1100
  }
964
- if (result.error === "No update available") {
965
- return { success: true, message: "Already on latest version." };
966
- }
967
- return {
968
- success: false,
969
- message: result.error ?? "Update failed"
970
- };
971
1101
  }
972
1102
  function performRollback() {
973
1103
  const currentStaged = getStagedUpdate();
@@ -982,7 +1112,7 @@ function performRollback() {
982
1112
  }
983
1113
 
984
1114
  // src/core/daemon.ts
985
- import fs4 from "node:fs";
1115
+ import fs5 from "node:fs";
986
1116
  import net from "node:net";
987
1117
  import { spawn as spawn2, execFileSync as execFileSync2 } from "node:child_process";
988
1118
 
@@ -1226,11 +1356,41 @@ function withTimeout(work, timeoutMs, timeoutMessage) {
1226
1356
  });
1227
1357
  });
1228
1358
  }
1229
- function specFingerprint(spec) {
1230
- return JSON.stringify(spec);
1359
+ function specFingerprint(spec, secrets) {
1360
+ if (!secrets)
1361
+ return JSON.stringify(spec);
1362
+ const resolvedMarkers = {};
1363
+ const collect = (obj) => {
1364
+ if (!obj)
1365
+ return;
1366
+ for (const [key, value] of Object.entries(obj)) {
1367
+ if (value.startsWith("secret://") || value.startsWith("oauth://")) {
1368
+ try {
1369
+ resolvedMarkers[key] = secrets.resolveMaybeSecret(value);
1370
+ } catch {
1371
+ resolvedMarkers[key] = value;
1372
+ }
1373
+ }
1374
+ }
1375
+ };
1376
+ if (spec.transport === "stdio")
1377
+ collect(spec.env);
1378
+ if (spec.transport === "http")
1379
+ collect(spec.headers);
1380
+ return JSON.stringify({ spec, resolvedMarkers });
1231
1381
  }
1382
+ var EXTRA_INHERITED_ENV = ["TMPDIR", "LANG"];
1232
1383
  function resolveStdioEnv(spec, secrets) {
1233
1384
  const env = getDefaultEnvironment();
1385
+ for (const key of EXTRA_INHERITED_ENV) {
1386
+ const value = process.env[key];
1387
+ if (value)
1388
+ env[key] = value;
1389
+ }
1390
+ for (const [key, value] of Object.entries(process.env)) {
1391
+ if (key.startsWith("LC_") && value)
1392
+ env[key] = value;
1393
+ }
1234
1394
  env.PATH = buildEnrichedPath(env.PATH);
1235
1395
  for (const [key, value] of Object.entries(spec.env ?? {})) {
1236
1396
  env[key] = secrets.resolveMaybeSecret(value);
@@ -1440,7 +1600,11 @@ async function handleListTools(config, secrets, runtime, upstreamFilter, clientA
1440
1600
  runtime.upstreamErrors?.set(upstream.name, { code, message: error instanceof Error ? error.message : String(error) });
1441
1601
  }
1442
1602
  }
1443
- return { tools };
1603
+ const failedUpstreams = upstreams.filter((u) => runtime.upstreamErrors?.has(u.name)).map((u) => ({ name: u.name, ...runtime.upstreamErrors.get(u.name) }));
1604
+ return {
1605
+ tools,
1606
+ ...failedUpstreams.length > 0 ? { _meta: { mcpxUpstreamErrors: failedUpstreams } } : {}
1607
+ };
1444
1608
  }
1445
1609
  async function handleListResources(config, secrets, runtime, upstreamFilter, clientAuthorizationHeader) {
1446
1610
  const resources = [];
@@ -1476,7 +1640,11 @@ async function handleListResources(config, secrets, runtime, upstreamFilter, cli
1476
1640
  runtime.upstreamErrors?.set(upstream.name, { code, message: error instanceof Error ? error.message : String(error) });
1477
1641
  }
1478
1642
  }
1479
- return { resources };
1643
+ const failedUpstreams = upstreams.filter((u) => runtime.upstreamErrors?.has(u.name)).map((u) => ({ name: u.name, ...runtime.upstreamErrors.get(u.name) }));
1644
+ return {
1645
+ resources,
1646
+ ...failedUpstreams.length > 0 ? { _meta: { mcpxUpstreamErrors: failedUpstreams } } : {}
1647
+ };
1480
1648
  }
1481
1649
  async function handleListPrompts(config, secrets, runtime, upstreamFilter, clientAuthorizationHeader) {
1482
1650
  const prompts = [];
@@ -1510,7 +1678,11 @@ async function handleListPrompts(config, secrets, runtime, upstreamFilter, clien
1510
1678
  runtime.upstreamErrors?.set(upstream.name, { code, message: error instanceof Error ? error.message : String(error) });
1511
1679
  }
1512
1680
  }
1513
- return { prompts };
1681
+ const failedUpstreams = upstreams.filter((u) => runtime.upstreamErrors?.has(u.name)).map((u) => ({ name: u.name, ...runtime.upstreamErrors.get(u.name) }));
1682
+ return {
1683
+ prompts,
1684
+ ...failedUpstreams.length > 0 ? { _meta: { mcpxUpstreamErrors: failedUpstreams } } : {}
1685
+ };
1514
1686
  }
1515
1687
  async function routeNamespacedCall(config, method, params, id, secrets, runtime, upstreamFilter, clientAuthorizationHeader) {
1516
1688
  if (!params || typeof params !== "object") {
@@ -1784,6 +1956,16 @@ function createGatewayServer(options) {
1784
1956
  }
1785
1957
  return;
1786
1958
  }
1959
+ const wantsStream = (request.headers.accept ?? "").includes("text/event-stream");
1960
+ if (wantsStream && request.headers["mcp-session-id"]) {
1961
+ response.statusCode = 405;
1962
+ response.setHeader("content-type", "application/json");
1963
+ response.end(JSON.stringify({ error: "get_stream_not_supported", message: "mcpx does not support server-initiated SSE streams over GET; use POST for all requests." }));
1964
+ if (debug) {
1965
+ console.error(`[mcpx gateway] -> 405 (GET stream not supported)`);
1966
+ }
1967
+ return;
1968
+ }
1787
1969
  response.statusCode = 200;
1788
1970
  response.setHeader("content-type", "application/json");
1789
1971
  response.end(JSON.stringify({ ok: true, server: "mcpx" }));
@@ -1923,19 +2105,24 @@ function createGatewayServer(options) {
1923
2105
  }
1924
2106
 
1925
2107
  // src/core/daemon.ts
1926
- function readPidFromFile(pidPath = getPidPath()) {
1927
- if (!fs4.existsSync(pidPath)) {
2108
+ function readPidRecordFromFile(pidPath = getPidPath()) {
2109
+ if (!fs5.existsSync(pidPath)) {
1928
2110
  return null;
1929
2111
  }
1930
- const raw = fs4.readFileSync(pidPath, "utf8").trim();
2112
+ const raw = fs5.readFileSync(pidPath, "utf8").trim();
1931
2113
  if (!raw) {
1932
2114
  return null;
1933
2115
  }
1934
- const pid = Number(raw);
2116
+ const [pidRaw, portRaw] = raw.split(":");
2117
+ const pid = Number(pidRaw);
1935
2118
  if (!Number.isFinite(pid) || pid <= 0) {
1936
2119
  return null;
1937
2120
  }
1938
- return pid;
2121
+ const port = portRaw ? Number(portRaw) : null;
2122
+ return { pid, port: Number.isFinite(port) && port > 0 ? port : null };
2123
+ }
2124
+ function readPidFromFile(pidPath = getPidPath()) {
2125
+ return readPidRecordFromFile(pidPath)?.pid ?? null;
1939
2126
  }
1940
2127
  function processExists(pid) {
1941
2128
  try {
@@ -1959,7 +2146,7 @@ async function isPortAvailable(port) {
1959
2146
  }
1960
2147
  async function resolveGatewayPort(config, secrets) {
1961
2148
  if (await isPortAvailable(config.gateway.port)) {
1962
- return config.gateway.port;
2149
+ return { port: config.gateway.port };
1963
2150
  }
1964
2151
  for (let offset = 1;offset <= 20; offset += 1) {
1965
2152
  const candidate = config.gateway.port + offset;
@@ -1977,7 +2164,7 @@ async function resolveGatewayPort(config, secrets) {
1977
2164
  saveConfig(config);
1978
2165
  } catch {}
1979
2166
  }
1980
- return candidate;
2167
+ return { port: candidate, fellBackFrom: oldPort };
1981
2168
  }
1982
2169
  }
1983
2170
  throw new Error(`No available local port found near ${config.gateway.port}.`);
@@ -2007,8 +2194,8 @@ async function waitForGatewayReady(port, token, timeoutMs = 5000) {
2007
2194
  }
2008
2195
  function getDaemonStatus(config) {
2009
2196
  const pidPath = getPidPath();
2010
- const pid = readPidFromFile(pidPath);
2011
- if (!pid) {
2197
+ const record = readPidRecordFromFile(pidPath);
2198
+ if (!record) {
2012
2199
  return {
2013
2200
  running: false,
2014
2201
  pidFile: pidPath,
@@ -2016,12 +2203,15 @@ function getDaemonStatus(config) {
2016
2203
  port: config.gateway.port
2017
2204
  };
2018
2205
  }
2206
+ const running = processExists(record.pid);
2207
+ const port = record.port ?? config.gateway.port;
2019
2208
  return {
2020
- running: processExists(pid),
2021
- pid,
2209
+ running,
2210
+ pid: record.pid,
2022
2211
  pidFile: pidPath,
2023
2212
  logFile: getLogPath(),
2024
- port: config.gateway.port
2213
+ port,
2214
+ portMismatch: running && record.port !== null && record.port !== config.gateway.port
2025
2215
  };
2026
2216
  }
2027
2217
  async function startDaemon(config, cliPath, secrets) {
@@ -2037,16 +2227,38 @@ async function startDaemon(config, cliPath, secrets) {
2037
2227
  };
2038
2228
  }
2039
2229
  try {
2040
- fs4.unlinkSync(getPidPath());
2230
+ fs5.unlinkSync(getPidPath());
2041
2231
  } catch {}
2042
2232
  }
2043
- const port = await resolveGatewayPort(config, secrets);
2233
+ const { port, fellBackFrom } = await resolveGatewayPort(config, secrets);
2234
+ if (fellBackFrom) {
2235
+ process.stderr.write(`mcpx: port ${fellBackFrom} was unavailable; gateway started on ${port} instead. All client configs were re-synced to the new port.
2236
+ `);
2237
+ }
2044
2238
  const token = ensureGatewayToken(config, secrets);
2045
2239
  const pidPath = getPidPath();
2046
2240
  const logPath = getLogPath();
2047
2241
  ensureParentDir(pidPath);
2048
2242
  ensureParentDir(logPath);
2049
- const logFd = fs4.openSync(logPath, "a", 384);
2243
+ let logFd = fs5.openSync(logPath, "a", 384);
2244
+ const logStat = fs5.fstatSync(logFd);
2245
+ if (logStat.size > 10 * 1024 * 1024) {
2246
+ fs5.closeSync(logFd);
2247
+ const log1 = `${logPath}.1`;
2248
+ const log2 = `${logPath}.2`;
2249
+ try {
2250
+ if (fs5.existsSync(log2))
2251
+ fs5.unlinkSync(log2);
2252
+ } catch {}
2253
+ try {
2254
+ if (fs5.existsSync(log1))
2255
+ fs5.renameSync(log1, log2);
2256
+ } catch {}
2257
+ try {
2258
+ fs5.renameSync(logPath, log1);
2259
+ } catch {}
2260
+ logFd = fs5.openSync(logPath, "a", 384);
2261
+ }
2050
2262
  const child = spawn2(process.execPath, [cliPath, "daemon", "run", "--port", String(port)], {
2051
2263
  detached: true,
2052
2264
  stdio: ["ignore", logFd, logFd],
@@ -2056,7 +2268,7 @@ async function startDaemon(config, cliPath, secrets) {
2056
2268
  }
2057
2269
  });
2058
2270
  child.unref();
2059
- fs4.writeFileSync(pidPath, `${child.pid}
2271
+ fs5.writeFileSync(pidPath, `${child.pid}:${port}
2060
2272
  `, { mode: 384 });
2061
2273
  startBackgroundUpdateCheck();
2062
2274
  await waitForGatewayReady(port, token);
@@ -2078,8 +2290,9 @@ function stopDaemon() {
2078
2290
  }
2079
2291
  try {
2080
2292
  const cmd = execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 2000 }).trim();
2081
- if (!cmd.includes("mcpx") && !cmd.includes("daemon") && !cmd.includes("cli.js")) {
2082
- fs4.unlinkSync(pidPath);
2293
+ const looksLikeMcpxDaemon = cmd.includes("daemon run") && (cmd.includes("mcpx") || cmd.includes("cli.js"));
2294
+ if (!looksLikeMcpxDaemon) {
2295
+ fs5.unlinkSync(pidPath);
2083
2296
  return {
2084
2297
  stopped: false,
2085
2298
  message: `PID ${pid} is not a mcpx daemon (command: ${cmd.slice(0, 80)}). Pidfile cleaned up.`
@@ -2087,7 +2300,7 @@ function stopDaemon() {
2087
2300
  }
2088
2301
  } catch {
2089
2302
  try {
2090
- fs4.unlinkSync(pidPath);
2303
+ fs5.unlinkSync(pidPath);
2091
2304
  } catch {}
2092
2305
  return {
2093
2306
  stopped: false,
@@ -2098,7 +2311,7 @@ function stopDaemon() {
2098
2311
  process.kill(pid, "SIGTERM");
2099
2312
  } catch {}
2100
2313
  try {
2101
- fs4.unlinkSync(pidPath);
2314
+ fs5.unlinkSync(pidPath);
2102
2315
  } catch {}
2103
2316
  return {
2104
2317
  stopped: true,
@@ -2111,10 +2324,10 @@ async function restartDaemon(config, cliPath, secrets) {
2111
2324
  }
2112
2325
  function readDaemonLogs(maxLines = 200) {
2113
2326
  const logPath = getLogPath();
2114
- if (!fs4.existsSync(logPath)) {
2327
+ if (!fs5.existsSync(logPath)) {
2115
2328
  return "";
2116
2329
  }
2117
- const lines = fs4.readFileSync(logPath, "utf8").split(`
2330
+ const lines = fs5.readFileSync(logPath, "utf8").split(`
2118
2331
  `);
2119
2332
  return lines.slice(Math.max(lines.length - maxLines, 0)).join(`
2120
2333
  `);
@@ -2139,7 +2352,7 @@ function runDaemonForeground(config, port, secrets) {
2139
2352
  }
2140
2353
 
2141
2354
  // src/core/status.ts
2142
- var STATUS_CLIENTS = ["claude", "claude-desktop", "codex", "cursor", "cline", "opencode", "kiro", "vscode", "qwen"];
2355
+ var STATUS_CLIENTS = ["claude", "claude-desktop", "codex", "cursor", "cline", "opencode", "kiro", "vscode", "qwen", "openclaw", "hermes"];
2143
2356
  function describeServerTarget(spec) {
2144
2357
  if (spec.transport === "http") {
2145
2358
  return spec.url;
@@ -2192,15 +2405,15 @@ async function fetchTokenCounts(gatewayUrl, token) {
2192
2405
  method: "custom/tokenCounts",
2193
2406
  params: {}
2194
2407
  }),
2195
- signal: AbortSignal.timeout(4000)
2408
+ signal: AbortSignal.timeout(65000)
2196
2409
  });
2197
2410
  if (!res.ok) {
2198
- return {};
2411
+ return { counts: {}, fetchError: `HTTP ${res.status}` };
2199
2412
  }
2200
2413
  const data = await res.json();
2201
- return data?.result ?? {};
2414
+ return { counts: data?.result ?? {} };
2202
2415
  } catch (error) {
2203
- return {};
2416
+ return { counts: {}, fetchError: error instanceof Error ? error.message : String(error) };
2204
2417
  }
2205
2418
  }
2206
2419
  async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(config)) {
@@ -2208,8 +2421,11 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
2208
2421
  const token = ensureGatewayToken(config, secrets);
2209
2422
  const gatewayUrl = getGatewayUrl(config);
2210
2423
  let tokenCounts = {};
2424
+ let tokenCountFetchError;
2211
2425
  if (daemon.running) {
2212
- tokenCounts = await fetchTokenCounts(gatewayUrl, token);
2426
+ const result = await fetchTokenCounts(gatewayUrl, token);
2427
+ tokenCounts = result.counts;
2428
+ tokenCountFetchError = result.fetchError;
2213
2429
  }
2214
2430
  const projectEntries = Object.values(config.projects ?? {});
2215
2431
  let totalGlobalTokens = 0;
@@ -2254,4 +2470,4 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
2254
2470
  };
2255
2471
  }
2256
2472
 
2257
- export { probeHttpAuthRequirement, fetchRegistryServerDetail, selectBestPackage, extractRequiredInputs, mapRegistryToSpec, toSecretRef, secretRefName, defaultAuthSecretName, resolveAuthTarget, applyAuthReference, removeAuthReference, listAuthBindings, maybePrefixBearer, buildEnrichedPath, isOAuthReference, oauthReferenceServerName, runOAuthLogin, getOAuthAccessToken, APP_VERSION, compareVersions, checkForUpdates, getStagedUpdate, shouldUseStagedCli, clearStagedUpdate, getStagedCliPath, startBackgroundUpdateCheck, runBackgroundUpdate, performUpdate, performRollback, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs, runDaemonForeground, STATUS_CLIENTS, buildStatusReport };
2473
+ export { mutateConfig, mutateProjectConfig, mutateActiveConfig, probeHttpAuthRequirement, fetchRegistryServerDetail, selectBestPackage, extractRequiredInputs, mapRegistryToSpec, toSecretRef, secretRefName, defaultAuthSecretName, resolveAuthTarget, applyAuthReference, removeAuthReference, listAuthBindings, maybePrefixBearer, buildEnrichedPath, isOAuthReference, oauthReferenceServerName, runOAuthLogin, getOAuthAccessToken, compareVersions, checkForUpdates, getStagedUpdate, shouldUseStagedCli, clearStagedUpdate, getStagedCliPath, startBackgroundUpdateCheck, runBackgroundUpdate, performUpdate, performRollback, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs, runDaemonForeground, STATUS_CLIENTS, buildStatusReport };