@kwonye/mcpx 0.1.93 → 0.1.94

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 (47) hide show
  1. package/dist/adapters/cline.d.ts.map +1 -1
  2. package/dist/adapters/codex.d.ts.map +1 -1
  3. package/dist/adapters/kiro.d.ts.map +1 -1
  4. package/dist/adapters/opencode.d.ts.map +1 -1
  5. package/dist/adapters/utils/index.d.ts +11 -3
  6. package/dist/adapters/utils/index.d.ts.map +1 -1
  7. package/dist/{cli-y7ba5qvr.js → cli-154shqmt.js} +126 -68
  8. package/dist/{cli-qvkpwy9w.js → cli-9fmn9y72.js} +36 -17
  9. package/dist/{cli-np0wwh6m.js → cli-csfsg1yf.js} +78 -4
  10. package/dist/{cli-m2645syd.js → cli-mxmgxdm2.js} +127 -69
  11. package/dist/{cli-kwyp9ywh.js → cli-n8vt375e.js} +306 -172
  12. package/dist/{cli-8vxva0cb.js → cli-tk8bggg5.js} +81 -8
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +45 -21
  15. package/dist/{config-2s5nsm2g.js → config-dtc3c79c.js} +6 -2
  16. package/dist/core/config-repair.d.ts.map +1 -1
  17. package/dist/core/config-store.d.ts +3 -0
  18. package/dist/core/config-store.d.ts.map +1 -0
  19. package/dist/core/config.d.ts +13 -1
  20. package/dist/core/config.d.ts.map +1 -1
  21. package/dist/core/daemon.d.ts +1 -1
  22. package/dist/core/daemon.d.ts.map +1 -1
  23. package/dist/core/errors.d.ts +18 -0
  24. package/dist/core/errors.d.ts.map +1 -0
  25. package/dist/core/index.d.ts +5 -2
  26. package/dist/core/index.d.ts.map +1 -1
  27. package/dist/core/index.js +81 -6
  28. package/dist/core/oauth.d.ts.map +1 -1
  29. package/dist/core/plugin-manager.d.ts.map +1 -1
  30. package/dist/core/plugin-projections.d.ts +6 -2
  31. package/dist/core/plugin-projections.d.ts.map +1 -1
  32. package/dist/core/registry.d.ts +4 -1
  33. package/dist/core/registry.d.ts.map +1 -1
  34. package/dist/core/secrets.d.ts.map +1 -1
  35. package/dist/core/server-auth.d.ts.map +1 -1
  36. package/dist/core/status.d.ts +1 -0
  37. package/dist/core/status.d.ts.map +1 -1
  38. package/dist/core/sync.d.ts +19 -2
  39. package/dist/core/sync.d.ts.map +1 -1
  40. package/dist/gateway/server.d.ts +4 -1
  41. package/dist/gateway/server.d.ts.map +1 -1
  42. package/dist/{plugin-manager-1sc4b6am.js → plugin-manager-1sjwfnah.js} +4 -4
  43. package/dist/{registry-mztfy0cz.js → registry-n12thgf2.js} +1 -1
  44. package/dist/types.d.ts +2 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/version.d.ts +1 -1
  47. package/package.json +1 -1
@@ -317,6 +317,14 @@ function repairConfig(config) {
317
317
  servers[name] = repaired;
318
318
  }
319
319
  }
320
+ const existingNames = new Set(Object.keys(servers));
321
+ if (config.projects) {
322
+ for (const project of Object.values(config.projects)) {
323
+ if (project.disabledServers) {
324
+ project.disabledServers = project.disabledServers.filter((s) => existingNames.has(s));
325
+ }
326
+ }
327
+ }
320
328
  return {
321
329
  ...config,
322
330
  servers
@@ -324,6 +332,17 @@ function repairConfig(config) {
324
332
  }
325
333
 
326
334
  // src/core/config.ts
335
+ class ConfigLoadError extends Error {
336
+ configPath;
337
+ backupPath;
338
+ issues;
339
+ constructor(configPath, message, issues) {
340
+ super(message);
341
+ this.name = "ConfigLoadError";
342
+ this.configPath = configPath;
343
+ this.issues = issues;
344
+ }
345
+ }
327
346
  var clientStateSchema = z.object({
328
347
  status: z.enum(["SYNCED", "UNSUPPORTED_HTTP", "ERROR", "SKIPPED"]),
329
348
  lastSyncAt: z.string().optional(),
@@ -436,10 +455,30 @@ function defaultConfig() {
436
455
  };
437
456
  }
438
457
  function loadConfig(configPath = getConfigPath()) {
439
- const raw = readJsonFile(configPath, defaultConfig());
458
+ if (!fs.existsSync(configPath)) {
459
+ return defaultConfig();
460
+ }
461
+ let raw;
462
+ try {
463
+ const content = fs.readFileSync(configPath, "utf8");
464
+ raw = JSON.parse(content);
465
+ } catch (err) {
466
+ const backupPath = `${configPath}.invalid-${Date.now()}`;
467
+ try {
468
+ fs.copyFileSync(configPath, backupPath);
469
+ } catch {}
470
+ const message = `Failed to parse config file at ${configPath}.`;
471
+ throw new ConfigLoadError(configPath, `${message} Backed up to ${backupPath}.`, [`JSON parse error: ${err.message}`]);
472
+ }
440
473
  const parsed = configSchema.safeParse(raw);
441
474
  if (!parsed.success) {
442
- return defaultConfig();
475
+ const backupPath = `${configPath}.invalid-${Date.now()}`;
476
+ try {
477
+ fs.copyFileSync(configPath, backupPath);
478
+ } catch {}
479
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
480
+ const message = `Config file at ${configPath} has validation errors.`;
481
+ throw new ConfigLoadError(configPath, `${message} Backed up to ${backupPath}.`, issues);
443
482
  }
444
483
  const clientEntries = {};
445
484
  for (const [key, value] of Object.entries(parsed.data.clients)) {
@@ -461,7 +500,18 @@ function saveConfig(config, configPath = getConfigPath()) {
461
500
  config.servers = Object.fromEntries(Object.entries(config.servers).map(([name, spec]) => [name, normalizeServerSpecEnabled(spec)]));
462
501
  writeJsonAtomic(configPath, config);
463
502
  }
464
- function loadProjectConfig(projectConfigPath) {
503
+ function loadProjectConfig(projectConfigPath, options) {
504
+ if (options?.strict) {
505
+ const raw2 = readJsonFile(projectConfigPath, null);
506
+ if (raw2 === null) {
507
+ return { servers: {} };
508
+ }
509
+ const parsed2 = projectConfigSchema.safeParse(raw2);
510
+ if (!parsed2.success) {
511
+ throw new ConfigLoadError(projectConfigPath, `Project config at ${projectConfigPath} has validation errors.`, parsed2.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`));
512
+ }
513
+ return parsed2.data;
514
+ }
465
515
  const raw = readJsonFile(projectConfigPath, { servers: {} });
466
516
  const parsed = projectConfigSchema.safeParse(raw);
467
517
  if (!parsed.success) {
@@ -498,6 +548,30 @@ function loadMergedConfig(configPath = getConfigPath()) {
498
548
  }
499
549
  return config;
500
550
  }
551
+ function migrateProjectServers(config, projectPath) {
552
+ const localConfigPath = path.join(projectPath, ".mcpx.json");
553
+ if (!fs.existsSync(localConfigPath)) {
554
+ return { migrated: [], conflicts: [] };
555
+ }
556
+ const migrated = [];
557
+ const conflicts = [];
558
+ try {
559
+ const localConfig = loadProjectConfig(localConfigPath);
560
+ for (const [serverName, spec] of Object.entries(localConfig.servers)) {
561
+ if (config.servers[serverName]) {
562
+ conflicts.push(serverName);
563
+ continue;
564
+ }
565
+ const normalized = normalizeServerSpecEnabled(spec);
566
+ if (normalized.transport === "stdio" && !normalized.cwd) {
567
+ normalized.cwd = projectPath;
568
+ }
569
+ config.servers[serverName] = normalized;
570
+ migrated.push(serverName);
571
+ }
572
+ } catch {}
573
+ return { migrated, conflicts };
574
+ }
501
575
  function resolveActiveConfig(options = {}) {
502
576
  const globalPath = getConfigPath();
503
577
  const config = loadConfig(globalPath);
@@ -522,4 +596,4 @@ function resolveActiveConfig(options = {}) {
522
596
  };
523
597
  }
524
598
 
525
- export { tokenizeCommandLine, parseCliAddCommand, defaultConfig, loadConfig, saveConfig, loadProjectConfig, saveProjectConfig, loadMergedConfig, resolveActiveConfig };
599
+ export { tokenizeCommandLine, parseCliAddCommand, ConfigLoadError, defaultConfig, loadConfig, saveConfig, loadProjectConfig, saveProjectConfig, loadMergedConfig, migrateProjectServers, resolveActiveConfig };
@@ -1,16 +1,19 @@
1
1
  import {
2
2
  getGatewayUrl,
3
+ persistSyncState,
3
4
  syncAllClients
4
- } from "./cli-kwyp9ywh.js";
5
+ } from "./cli-n8vt375e.js";
5
6
  import {
6
7
  loadConfig,
7
8
  loadMergedConfig,
8
9
  saveConfig
9
- } from "./cli-np0wwh6m.js";
10
+ } from "./cli-csfsg1yf.js";
10
11
  import {
11
12
  SecretsManager,
13
+ UpstreamError,
14
+ classifyUpstreamError,
12
15
  ensureGatewayToken
13
- } from "./cli-8vxva0cb.js";
16
+ } from "./cli-tk8bggg5.js";
14
17
  import {
15
18
  ensureDir,
16
19
  ensureParentDir,
@@ -344,6 +347,7 @@ function listAuthBindings(spec) {
344
347
  value
345
348
  }));
346
349
  }
350
+ var TOKEN68 = /^[A-Za-z0-9\-._~+/]+=*$/;
347
351
  function maybePrefixBearer(target, value, raw = false) {
348
352
  if (raw || target.kind !== "header" || target.key.toLowerCase() !== "authorization") {
349
353
  return value;
@@ -351,7 +355,10 @@ function maybePrefixBearer(target, value, raw = false) {
351
355
  if (/^\S+\s+\S+/.test(value)) {
352
356
  return value;
353
357
  }
354
- return `Bearer ${value}`;
358
+ if (TOKEN68.test(value)) {
359
+ return `Bearer ${value}`;
360
+ }
361
+ return value;
355
362
  }
356
363
 
357
364
  // src/core/spawn-env.ts
@@ -605,7 +612,12 @@ function bindOAuthReference(config, serverName) {
605
612
  };
606
613
  }
607
614
  async function runOAuthLogin(serverName, spec, secrets, openUrl, configPath, codeReceiver) {
608
- new McpxOAuthProvider(serverName, secrets).invalidateCredentials("all");
615
+ const oauthName = oauthSecretName(serverName, "tokens");
616
+ const clientName = oauthSecretName(serverName, "client");
617
+ const backupTokens = secrets.getSecret(oauthName);
618
+ const backupClient = secrets.getSecret(clientName);
619
+ new McpxOAuthProvider(serverName, secrets).invalidateCredentials("verifier");
620
+ new McpxOAuthProvider(serverName, secrets).invalidateCredentials("discovery");
609
621
  const state = crypto.randomUUID();
610
622
  let callbackServer;
611
623
  let redirectUrl;
@@ -638,8 +650,16 @@ async function runOAuthLogin(serverName, spec, secrets, openUrl, configPath, cod
638
650
  const config = loadConfig(configPath);
639
651
  bindOAuthReference(config, serverName);
640
652
  saveConfig(config, configPath);
641
- syncAllClients(config, secrets);
653
+ const summary = syncAllClients(config, secrets);
654
+ persistSyncState(summary, config);
655
+ saveConfig(config, configPath);
642
656
  return { serverName, authorized: true };
657
+ } catch (error) {
658
+ if (backupTokens)
659
+ secrets.setSecret(oauthName, backupTokens);
660
+ if (backupClient)
661
+ secrets.setSecret(clientName, backupClient);
662
+ throw error;
643
663
  } finally {
644
664
  if (callbackServer) {
645
665
  await closeServer(callbackServer);
@@ -649,36 +669,56 @@ async function runOAuthLogin(serverName, spec, secrets, openUrl, configPath, cod
649
669
  async function getOAuthAccessToken(serverName, spec, secrets, options = {}) {
650
670
  const stored = readJsonSecret(secrets, oauthSecretName(serverName, "tokens"));
651
671
  if (!stored) {
652
- throw new Error(`No OAuth tokens stored for "${serverName}". Run mcpx auth login ${serverName}.`);
653
- }
654
- if (!options.forceRefresh && !tokensAreExpiring(stored)) {
655
- return stored.tokens.access_token;
672
+ throw new UpstreamError(serverName, "auth_required", `No OAuth tokens stored for "${serverName}".`);
656
673
  }
657
- if (!stored.tokens.refresh_token) {
658
- if (!options.forceRefresh) {
659
- return stored.tokens.access_token;
674
+ if (options.forceRefresh || tokensAreExpiring(stored)) {
675
+ if (!stored.tokens.refresh_token) {
676
+ if (!options.forceRefresh) {
677
+ return stored.tokens.access_token;
678
+ }
679
+ throw new UpstreamError(serverName, "auth_expired", `No refresh token for "${serverName}". Re-authentication required.`);
680
+ }
681
+ const provider = new McpxOAuthProvider(serverName, secrets);
682
+ const clientInformation = provider.clientInformation();
683
+ if (!clientInformation) {
684
+ throw new UpstreamError(serverName, "auth_expired", `No OAuth client info for "${serverName}". Re-authentication required.`);
685
+ }
686
+ let serverInfo;
687
+ try {
688
+ const cachedDiscovery = provider.discoveryState();
689
+ serverInfo = cachedDiscovery ?? await discoverOAuthServerInfo(spec.url);
690
+ } catch (err) {
691
+ const message = err instanceof Error ? err.message : String(err);
692
+ if (/ECONNREFUSED|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(message)) {
693
+ throw new UpstreamError(serverName, "unreachable", `OAuth discovery failed: ${message}`);
694
+ }
695
+ throw new UpstreamError(serverName, "upstream_error", `OAuth discovery failed: ${message}`);
696
+ }
697
+ try {
698
+ const refreshed = await refreshAuthorization(serverInfo.authorizationServerUrl, {
699
+ metadata: serverInfo.authorizationServerMetadata,
700
+ clientInformation,
701
+ refreshToken: stored.tokens.refresh_token,
702
+ resource: serverInfo.resourceMetadata?.resource ? new URL(serverInfo.resourceMetadata.resource) : undefined
703
+ });
704
+ provider.saveTokens(refreshed);
705
+ return refreshed.access_token;
706
+ } catch (err) {
707
+ const message = err instanceof Error ? err.message : String(err);
708
+ if (/invalid_grant|invalid_client|4\d{2}/i.test(message)) {
709
+ throw new UpstreamError(serverName, "auth_expired", `OAuth refresh failed: ${message}`);
710
+ }
711
+ if (/ECONNREFUSED|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(message)) {
712
+ throw new UpstreamError(serverName, "unreachable", `OAuth refresh failed: ${message}`);
713
+ }
714
+ throw new UpstreamError(serverName, "upstream_error", `OAuth refresh failed: ${message}`);
660
715
  }
661
- throw new Error(`No OAuth refresh token stored for "${serverName}". Re-authentication required.`);
662
- }
663
- const provider = new McpxOAuthProvider(serverName, secrets);
664
- const clientInformation = provider.clientInformation();
665
- if (!clientInformation) {
666
- throw new Error(`No OAuth client information stored for "${serverName}". Re-authentication required.`);
667
716
  }
668
- const cachedDiscovery = provider.discoveryState();
669
- const serverInfo = cachedDiscovery ?? await discoverOAuthServerInfo(spec.url);
670
- const refreshed = await refreshAuthorization(serverInfo.authorizationServerUrl, {
671
- metadata: serverInfo.authorizationServerMetadata,
672
- clientInformation,
673
- refreshToken: stored.tokens.refresh_token,
674
- resource: serverInfo.resourceMetadata?.resource ? new URL(serverInfo.resourceMetadata.resource) : undefined
675
- });
676
- provider.saveTokens(refreshed);
677
- return refreshed.access_token;
717
+ return stored.tokens.access_token;
678
718
  }
679
719
 
680
720
  // src/version.ts
681
- var APP_VERSION = "0.1.93";
721
+ var APP_VERSION = "0.1.94";
682
722
 
683
723
  // src/core/update.ts
684
724
  import fs2 from "node:fs";
@@ -944,7 +984,7 @@ function performRollback() {
944
984
  // src/core/daemon.ts
945
985
  import fs4 from "node:fs";
946
986
  import net from "node:net";
947
- import { spawn as spawn2 } from "node:child_process";
987
+ import { spawn as spawn2, execFileSync as execFileSync2 } from "node:child_process";
948
988
 
949
989
  // src/gateway/server.ts
950
990
  import http2 from "node:http";
@@ -964,19 +1004,6 @@ var OAUTH_WELL_KNOWN_PREFIXES = [
964
1004
  "/.well-known/oauth-authorization-server",
965
1005
  "/.well-known/openid-configuration"
966
1006
  ];
967
-
968
- class UpstreamHttpError extends Error {
969
- status;
970
- wwwAuthenticate;
971
- bodyText;
972
- constructor(upstreamName, status, bodyText, wwwAuthenticate) {
973
- super(`Upstream ${upstreamName} returned HTTP ${status}: ${bodyText.slice(0, 400)}`);
974
- this.name = "UpstreamHttpError";
975
- this.status = status;
976
- this.bodyText = bodyText;
977
- this.wwwAuthenticate = wwwAuthenticate;
978
- }
979
- }
980
1007
  function makeError(id, code, message, data) {
981
1008
  return {
982
1009
  jsonrpc: "2.0",
@@ -1135,8 +1162,8 @@ async function getUpstreamTokenCounts(config, secrets, runtime) {
1135
1162
  const fingerprint = specFingerprint(upstream.spec);
1136
1163
  const cached = runtime.tokenCache.get(upstream.name);
1137
1164
  if (cached?.fingerprint === fingerprint) {
1138
- const runtimeError2 = runtime.upstreamErrors?.get(upstream.name);
1139
- results[upstream.name] = runtimeError2 ? { ...cached.count, runtimeError: runtimeError2 } : cached.count;
1165
+ const runtimeErr2 = runtime.upstreamErrors?.get(upstream.name);
1166
+ results[upstream.name] = runtimeErr2 ? { ...cached.count, runtimeError: runtimeErr2.message, runtimeErrorCode: runtimeErr2.code } : cached.count;
1140
1167
  continue;
1141
1168
  }
1142
1169
  let toolsCount = 0;
@@ -1178,8 +1205,8 @@ async function getUpstreamTokenCounts(config, secrets, runtime) {
1178
1205
  if (errors.length === 0) {
1179
1206
  runtime.tokenCache.set(upstream.name, { fingerprint, count: countObj });
1180
1207
  }
1181
- const runtimeError = runtime.upstreamErrors?.get(upstream.name);
1182
- results[upstream.name] = runtimeError ? { ...countObj, runtimeError } : countObj;
1208
+ const runtimeErr = runtime.upstreamErrors?.get(upstream.name);
1209
+ results[upstream.name] = runtimeErr ? { ...countObj, runtimeError: runtimeErr.message, runtimeErrorCode: runtimeErr.code } : countObj;
1183
1210
  }
1184
1211
  return results;
1185
1212
  }
@@ -1348,11 +1375,8 @@ async function callUpstreamOnce(upstream, method, params, secrets, runtime, pass
1348
1375
  if (!forceOAuthRefresh && hasOAuth && error instanceof StreamableHTTPError && (error.code === 401 || error.code === 403)) {
1349
1376
  return callUpstreamOnce(upstream, method, params, secrets, runtime, passthroughAuthorizationHeader, true);
1350
1377
  }
1351
- if (upstream.spec.transport === "http" && error instanceof StreamableHTTPError && error.code) {
1352
- const wwwAuthenticate = runtime.lastWwwAuthenticate?.get(upstream.name);
1353
- throw new UpstreamHttpError(upstream.name, error.code, error.message ?? "", wwwAuthenticate);
1354
- }
1355
- throw error;
1378
+ const wwwAuthenticate = runtime.lastWwwAuthenticate?.get(upstream.name);
1379
+ throw classifyUpstreamError(upstream.name, error, wwwAuthenticate);
1356
1380
  }
1357
1381
  }
1358
1382
  function authHeaderIsValid(request, expectedToken) {
@@ -1371,7 +1395,7 @@ function authHeaderIsValid(request, expectedToken) {
1371
1395
  return scheme === "Bearer" && token === expectedToken;
1372
1396
  }
1373
1397
  function isAuthChallenge(error) {
1374
- return error instanceof UpstreamHttpError && (error.status === 401 || error.status === 403);
1398
+ return error instanceof UpstreamError && (error.code === "auth_required" || error.code === "auth_expired");
1375
1399
  }
1376
1400
  function getClientAuthorizationForUpstream(request, expectedToken) {
1377
1401
  const authHeader = request.headers.authorization;
@@ -1412,7 +1436,8 @@ async function handleListTools(config, secrets, runtime, upstreamFilter, clientA
1412
1436
  if (upstreams.length === 1) {
1413
1437
  throw error;
1414
1438
  }
1415
- runtime.upstreamErrors?.set(upstream.name, error instanceof Error ? error.message : String(error));
1439
+ const code = error instanceof UpstreamError ? error.code : "upstream_error";
1440
+ runtime.upstreamErrors?.set(upstream.name, { code, message: error instanceof Error ? error.message : String(error) });
1416
1441
  }
1417
1442
  }
1418
1443
  return { tools };
@@ -1447,7 +1472,8 @@ async function handleListResources(config, secrets, runtime, upstreamFilter, cli
1447
1472
  if (upstreams.length === 1) {
1448
1473
  throw error;
1449
1474
  }
1450
- runtime.upstreamErrors?.set(upstream.name, error instanceof Error ? error.message : String(error));
1475
+ const code = error instanceof UpstreamError ? error.code : "upstream_error";
1476
+ runtime.upstreamErrors?.set(upstream.name, { code, message: error instanceof Error ? error.message : String(error) });
1451
1477
  }
1452
1478
  }
1453
1479
  return { resources };
@@ -1480,7 +1506,8 @@ async function handleListPrompts(config, secrets, runtime, upstreamFilter, clien
1480
1506
  if (upstreams.length === 1) {
1481
1507
  throw error;
1482
1508
  }
1483
- runtime.upstreamErrors?.set(upstream.name, error instanceof Error ? error.message : String(error));
1509
+ const code = error instanceof UpstreamError ? error.code : "upstream_error";
1510
+ runtime.upstreamErrors?.set(upstream.name, { code, message: error instanceof Error ? error.message : String(error) });
1484
1511
  }
1485
1512
  }
1486
1513
  return { prompts };
@@ -1521,8 +1548,9 @@ async function routeNamespacedCall(config, method, params, id, secrets, runtime,
1521
1548
  if (isAuthChallenge(error)) {
1522
1549
  throw error;
1523
1550
  }
1524
- runtime.upstreamErrors?.set(upstream2.name, error.message);
1525
- return makeError(id, -32000, error.message);
1551
+ const errCode = error instanceof UpstreamError ? error.code : "upstream_error";
1552
+ runtime.upstreamErrors?.set(upstream2.name, { code: errCode, message: error.message });
1553
+ return makeError(id, -32000, error.message, { mcpxCode: errCode, upstream: upstream2.name });
1526
1554
  }
1527
1555
  }
1528
1556
  if (method === "prompts/get") {
@@ -1554,8 +1582,9 @@ async function routeNamespacedCall(config, method, params, id, secrets, runtime,
1554
1582
  if (isAuthChallenge(error)) {
1555
1583
  throw error;
1556
1584
  }
1557
- runtime.upstreamErrors?.set(upstream2.name, error.message);
1558
- return makeError(id, -32000, error.message);
1585
+ const errCode = error instanceof UpstreamError ? error.code : "upstream_error";
1586
+ runtime.upstreamErrors?.set(upstream2.name, { code: errCode, message: error.message });
1587
+ return makeError(id, -32000, error.message, { mcpxCode: errCode, upstream: upstream2.name });
1559
1588
  }
1560
1589
  }
1561
1590
  const uri = typeof params.uri === "string" ? params.uri : "";
@@ -1586,8 +1615,9 @@ async function routeNamespacedCall(config, method, params, id, secrets, runtime,
1586
1615
  if (isAuthChallenge(error)) {
1587
1616
  throw error;
1588
1617
  }
1589
- runtime.upstreamErrors?.set(upstream.name, error.message);
1590
- return makeError(id, -32000, error.message);
1618
+ const errCode = error instanceof UpstreamError ? error.code : "upstream_error";
1619
+ runtime.upstreamErrors?.set(upstream.name, { code: errCode, message: error.message });
1620
+ return makeError(id, -32000, error.message, { mcpxCode: errCode, upstream: upstream.name });
1591
1621
  }
1592
1622
  }
1593
1623
  async function handleRequestObject(request, secrets, runtime, upstreamFilter, clientAuthorizationHeader) {
@@ -1858,16 +1888,16 @@ function createGatewayServer(options) {
1858
1888
  }
1859
1889
  } catch (error) {
1860
1890
  if (isAuthChallenge(error)) {
1861
- response.statusCode = error.status;
1891
+ response.statusCode = error.status ?? 401;
1862
1892
  response.setHeader("content-type", "application/json");
1863
1893
  if (error.wwwAuthenticate) {
1864
1894
  const localResourceMetadataUrl = appendUpstreamQuery(`${getLocalOriginFromRequest(request)}/.well-known/oauth-protected-resource`, getRequestedUpstream(requestUrl));
1865
1895
  response.setHeader("www-authenticate", rewriteWwwAuthenticateResourceMetadata(error.wwwAuthenticate, localResourceMetadataUrl));
1866
1896
  }
1867
1897
  response.end(JSON.stringify(makeError(null, -32001, "Upstream authentication required.", {
1898
+ mcpxCode: error.code,
1868
1899
  status: error.status,
1869
- body: error.bodyText,
1870
- wwwAuthenticate: error.wwwAuthenticate
1900
+ upstream: error.upstream
1871
1901
  })));
1872
1902
  if (debug) {
1873
1903
  console.error(`[mcpx gateway] -> ${error.status} upstream auth challenge`);
@@ -1927,7 +1957,7 @@ async function isPortAvailable(port) {
1927
1957
  tester.listen(port, "127.0.0.1");
1928
1958
  });
1929
1959
  }
1930
- async function resolveGatewayPort(config) {
1960
+ async function resolveGatewayPort(config, secrets) {
1931
1961
  if (await isPortAvailable(config.gateway.port)) {
1932
1962
  return config.gateway.port;
1933
1963
  }
@@ -1937,8 +1967,16 @@ async function resolveGatewayPort(config) {
1937
1967
  break;
1938
1968
  }
1939
1969
  if (await isPortAvailable(candidate)) {
1970
+ const oldPort = config.gateway.port;
1940
1971
  config.gateway.port = candidate;
1941
1972
  saveConfig(config);
1973
+ if (secrets) {
1974
+ try {
1975
+ const summary = syncAllClients(config, secrets);
1976
+ persistSyncState(summary, config);
1977
+ saveConfig(config);
1978
+ } catch {}
1979
+ }
1942
1980
  return candidate;
1943
1981
  }
1944
1982
  }
@@ -2002,7 +2040,7 @@ async function startDaemon(config, cliPath, secrets) {
2002
2040
  fs4.unlinkSync(getPidPath());
2003
2041
  } catch {}
2004
2042
  }
2005
- const port = await resolveGatewayPort(config);
2043
+ const port = await resolveGatewayPort(config, secrets);
2006
2044
  const token = ensureGatewayToken(config, secrets);
2007
2045
  const pidPath = getPidPath();
2008
2046
  const logPath = getLogPath();
@@ -2038,6 +2076,24 @@ function stopDaemon() {
2038
2076
  message: "mcpx daemon is not running."
2039
2077
  };
2040
2078
  }
2079
+ try {
2080
+ 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);
2083
+ return {
2084
+ stopped: false,
2085
+ message: `PID ${pid} is not a mcpx daemon (command: ${cmd.slice(0, 80)}). Pidfile cleaned up.`
2086
+ };
2087
+ }
2088
+ } catch {
2089
+ try {
2090
+ fs4.unlinkSync(pidPath);
2091
+ } catch {}
2092
+ return {
2093
+ stopped: false,
2094
+ message: `PID ${pid} not found. Pidfile cleaned up.`
2095
+ };
2096
+ }
2041
2097
  try {
2042
2098
  process.kill(pid, "SIGTERM");
2043
2099
  } catch {}
@@ -2165,6 +2221,7 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
2165
2221
  if (tokenCount?.error && buildAuthBindings(spec).length > 0 && tokenCount.total > 0) {
2166
2222
  tokenCount = { ...tokenCount, error: undefined };
2167
2223
  }
2224
+ const health = tokenCount ? tokenCount.error || tokenCount.runtimeError ? "error" : "ok" : "unknown";
2168
2225
  const serverEntry = {
2169
2226
  name,
2170
2227
  enabled: isServerEnabled(spec),
@@ -2172,7 +2229,8 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
2172
2229
  target: describeServerTarget(spec),
2173
2230
  authBindings: buildAuthBindings(spec),
2174
2231
  clients: buildClientMappings(config, managedIndex, name),
2175
- tokenCount
2232
+ tokenCount,
2233
+ health
2176
2234
  };
2177
2235
  if (serverEntry.enabled && tokenCount) {
2178
2236
  totalGlobalTokens += tokenCount.total;