@kwonye/mcpx 0.1.75 → 0.1.79

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 (48) hide show
  1. package/dist/adapters/claude-desktop.d.ts.map +1 -1
  2. package/dist/adapters/claude.d.ts.map +1 -1
  3. package/dist/adapters/cline.d.ts.map +1 -1
  4. package/dist/adapters/cursor.d.ts.map +1 -1
  5. package/dist/adapters/vscode.d.ts.map +1 -1
  6. package/dist/cli-7edpy4nk.js +514 -0
  7. package/dist/cli-fsf33sgp.js +105 -0
  8. package/dist/{cli-q8de7zb0.js → cli-s4gspqdf.js} +4 -1
  9. package/dist/cli-s4py8ckf.js +187 -0
  10. package/dist/{cli-e9hdd44n.js → cli-w4m7471h.js} +933 -542
  11. package/dist/{cli-fh6jh7fs.js → cli-ydr75jw4.js} +934 -543
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +381 -56
  14. package/dist/config-79j3jpra.js +20 -0
  15. package/dist/core/add-command.d.ts +7 -0
  16. package/dist/core/add-command.d.ts.map +1 -0
  17. package/dist/core/auth-probe.d.ts +1 -0
  18. package/dist/core/auth-probe.d.ts.map +1 -1
  19. package/dist/core/config-repair.d.ts +3 -0
  20. package/dist/core/config-repair.d.ts.map +1 -0
  21. package/dist/core/config.d.ts +19 -1
  22. package/dist/core/config.d.ts.map +1 -1
  23. package/dist/core/daemon.d.ts.map +1 -1
  24. package/dist/core/index.d.ts +7 -2
  25. package/dist/core/index.d.ts.map +1 -1
  26. package/dist/core/index.js +50 -12
  27. package/dist/core/oauth.d.ts +12 -0
  28. package/dist/core/oauth.d.ts.map +1 -0
  29. package/dist/core/paths.d.ts +2 -0
  30. package/dist/core/paths.d.ts.map +1 -1
  31. package/dist/core/registry-client.d.ts +76 -0
  32. package/dist/core/registry-client.d.ts.map +1 -0
  33. package/dist/core/registry.d.ts +2 -0
  34. package/dist/core/registry.d.ts.map +1 -1
  35. package/dist/core/secrets.d.ts.map +1 -1
  36. package/dist/core/spawn-env.d.ts +2 -0
  37. package/dist/core/spawn-env.d.ts.map +1 -0
  38. package/dist/core/status.d.ts +6 -2
  39. package/dist/core/status.d.ts.map +1 -1
  40. package/dist/core/sync.d.ts.map +1 -1
  41. package/dist/gateway/server.d.ts +21 -0
  42. package/dist/gateway/server.d.ts.map +1 -1
  43. package/dist/registry-1s14wd58.js +24 -0
  44. package/dist/types.d.ts +13 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js +1 -1
  47. package/dist/version.d.ts +1 -1
  48. package/package.json +2 -1
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAm/CA,wBAAsB,MAAM,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE/D"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAo2DA,wBAAsB,MAAM,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAmE/D"}
package/dist/cli.js CHANGED
@@ -2,54 +2,72 @@
2
2
  import {
3
3
  APP_VERSION,
4
4
  STATUS_CLIENTS,
5
- SecretsManager,
6
- addServer,
7
5
  applyAuthReference,
8
6
  buildStatusReport,
9
7
  checkForUpdates,
10
8
  defaultAuthSecretName,
11
9
  deleteSkill,
12
- ensureGatewayToken,
10
+ extractRequiredInputs,
11
+ fetchRegistryServerDetail,
13
12
  getAdapters,
14
- getConfigPath,
15
13
  getDaemonStatus,
16
14
  getGatewayUrl,
17
- getManagedIndexPath,
18
15
  getSkill,
19
16
  getStagedCliPath,
20
17
  getStagedUpdate,
21
18
  listAuthBindings,
22
19
  listSkills,
23
- loadConfig,
24
20
  loadManagedIndex,
21
+ mapRegistryToSpec,
25
22
  maybePrefixBearer,
26
23
  performRollback,
27
24
  performUpdate,
28
25
  probeHttpAuthRequirement,
29
26
  readDaemonLogs,
30
- readSecretValueFromStdin,
31
27
  removeAuthReference,
32
- removeServer,
33
28
  resolveAuthTarget,
34
29
  restartDaemon,
35
30
  runBackgroundUpdate,
36
31
  runDaemonForeground,
37
- saveConfig,
32
+ runOAuthLogin,
38
33
  saveSkill,
39
34
  secretRefName,
40
- setServerEnabled,
35
+ selectBestPackage,
41
36
  startDaemon,
42
37
  stopDaemon,
43
38
  syncAllClients,
44
39
  toSecretRef
45
- } from "./cli-fh6jh7fs.js";
40
+ } from "./cli-ydr75jw4.js";
46
41
  import {
42
+ loadConfig,
43
+ loadMergedConfig,
44
+ loadProjectConfig,
45
+ resolveActiveConfig,
46
+ saveConfig,
47
+ saveProjectConfig
48
+ } from "./cli-7edpy4nk.js";
49
+ import {
50
+ SecretsManager,
51
+ addServer,
52
+ ensureGatewayToken,
53
+ readSecretValueFromStdin,
54
+ registerProject,
55
+ removeServer,
56
+ setServerEnabled
57
+ } from "./cli-s4py8ckf.js";
58
+ import {
59
+ getConfigPath,
60
+ getManagedIndexPath
61
+ } from "./cli-fsf33sgp.js";
62
+ import {
63
+ __require,
47
64
  isServerEnabled
48
- } from "./cli-q8de7zb0.js";
65
+ } from "./cli-s4gspqdf.js";
49
66
 
50
67
  // src/cli.ts
51
68
  import { Command } from "commander";
52
69
  import fs from "node:fs";
70
+ import path from "node:path";
53
71
  import { execFileSync } from "node:child_process";
54
72
  import { pathToFileURL } from "node:url";
55
73
  import { emitKeypressEvents } from "node:readline";
@@ -880,6 +898,7 @@ async function runStdioProxy(serverName) {
880
898
  }
881
899
 
882
900
  // src/cli.ts
901
+ var __dirname = "/home/runner/work/mcpx/mcpx/cli/src";
883
902
  var VALID_CLIENTS = STATUS_CLIENTS;
884
903
  function parseKeyValueFlag(value, label) {
885
904
  const split = value.indexOf("=");
@@ -1049,7 +1068,8 @@ async function ensureDaemonIfEnabled(cliPath, secrets) {
1049
1068
  async function autoSyncManagedEntries(config, cliPath) {
1050
1069
  const secrets = new SecretsManager;
1051
1070
  await ensureDaemonIfEnabled(cliPath, secrets);
1052
- const summary = syncAllClients(config, secrets);
1071
+ const mergedConfig = loadMergedConfig();
1072
+ const summary = syncAllClients(mergedConfig, secrets);
1053
1073
  printSyncSummary(summary, false);
1054
1074
  ensureExitCodeForSyncFailures(summary.hasErrors);
1055
1075
  }
@@ -1058,9 +1078,13 @@ function isNegativeChoice(value) {
1058
1078
  return normalized === "n" || normalized === "no";
1059
1079
  }
1060
1080
  async function maybeAutoConfigureAuthForAddedServer(serverName, spec, secrets) {
1061
- if (spec.transport !== "http") {
1062
- return;
1081
+ if (spec.transport === "http") {
1082
+ await maybeConfigureHttpAuth(serverName, spec, secrets);
1083
+ } else {
1084
+ await maybeConfigureStdioAuth(serverName, spec, secrets);
1063
1085
  }
1086
+ }
1087
+ async function maybeConfigureHttpAuth(serverName, spec, secrets) {
1064
1088
  const probe = await probeHttpAuthRequirement(spec, secrets);
1065
1089
  if (!probe.authRequired) {
1066
1090
  return;
@@ -1133,9 +1157,48 @@ async function maybeAutoConfigureAuthForAddedServer(serverName, spec, secrets) {
1133
1157
  `);
1134
1158
  }
1135
1159
  }
1136
- function loadStatusReport() {
1137
- const config = loadConfig();
1138
- return buildStatusReport(config, loadManagedIndex(), getDaemonStatus(config));
1160
+ async function maybeConfigureStdioAuth(serverName, spec, secrets) {
1161
+ const binds = listAuthBindings(spec);
1162
+ const plainEnvVars = binds.filter((b) => b.kind === "env" && !secretRefName(b.value));
1163
+ if (plainEnvVars.length === 0) {
1164
+ return;
1165
+ }
1166
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1167
+ process.stdout.write(`Server "${serverName}" has plain-text env vars. Run \`mcpx auth set ${serverName} --env KEY=VALUE --value "<token>"\` to store them securely.
1168
+ `);
1169
+ return;
1170
+ }
1171
+ process.stdout.write(`Server "${serverName}" has ${plainEnvVars.length} env var(s) stored in plain text.
1172
+ `);
1173
+ const rl = createInterface({
1174
+ input: process.stdin,
1175
+ output: process.stdout
1176
+ });
1177
+ try {
1178
+ const shouldMigrate = await promptLine(rl, "Store them in the system keychain instead? (Y/n): ");
1179
+ if (isNegativeChoice(shouldMigrate)) {
1180
+ process.stdout.write(`Skipping keychain migration.
1181
+ `);
1182
+ return;
1183
+ }
1184
+ for (const binding of plainEnvVars) {
1185
+ const target = { kind: "env", key: binding.key };
1186
+ const secretName = defaultAuthSecretName(serverName, target);
1187
+ secrets.setSecret(secretName, binding.value);
1188
+ applyAuthReference(spec, target, toSecretRef(secretName));
1189
+ process.stdout.write(`Stored ${binding.key} as secret://${secretName}
1190
+ `);
1191
+ }
1192
+ } catch (error) {
1193
+ process.stdout.write(`Keychain migration skipped: ${error.message}
1194
+ `);
1195
+ } finally {
1196
+ rl.close();
1197
+ }
1198
+ }
1199
+ async function loadStatusReport() {
1200
+ const config = loadMergedConfig();
1201
+ return await buildStatusReport(config, loadManagedIndex(), getDaemonStatus(config));
1139
1202
  }
1140
1203
  function daemonEmoji(running) {
1141
1204
  return running ? "✅" : "⚠️";
@@ -1192,6 +1255,16 @@ function printSyncedConfigBullets(server, indent) {
1192
1255
  `);
1193
1256
  }
1194
1257
  }
1258
+ function formatTokenApprox(tokens) {
1259
+ if (tokens === 0)
1260
+ return "0";
1261
+ if (tokens > 0 && tokens < 500)
1262
+ return "<1k";
1263
+ const thousands = Math.round(tokens / 1000);
1264
+ if (thousands === 0)
1265
+ return "<1k";
1266
+ return `~${thousands}k`;
1267
+ }
1195
1268
  function printStatusReportText(report) {
1196
1269
  process.stdout.write(`Gateway URL: ${report.gatewayUrl}
1197
1270
  `);
@@ -1199,12 +1272,17 @@ function printStatusReportText(report) {
1199
1272
  `);
1200
1273
  process.stdout.write(`Upstream servers: ${report.upstreamCount}
1201
1274
  `);
1275
+ if (report.daemon.running) {
1276
+ process.stdout.write(`Global active tokens: ${formatTokenApprox(report.totalGlobalTokens ?? 0)}
1277
+ `);
1278
+ }
1202
1279
  if (report.servers.length === 0) {
1203
1280
  process.stdout.write(`⚠️ No upstream servers configured.
1204
1281
  `);
1205
1282
  } else {
1206
1283
  for (const server of report.servers) {
1207
- process.stdout.write(`- ${serverHealthEmoji(server)} ${server.name} (${server.transport})
1284
+ const tokenSuffix = server.enabled && server.tokenCount ? ` [tokens: ${formatTokenApprox(server.tokenCount.total)} (tools: ${server.tokenCount.tools}, resources: ${server.tokenCount.resources}, prompts: ${server.tokenCount.prompts})]` : "";
1285
+ process.stdout.write(`- ${serverHealthEmoji(server)} ${server.name} (${server.transport})${tokenSuffix}
1208
1286
  `);
1209
1287
  process.stdout.write(` state: ${server.enabled ? "enabled" : "disabled"}
1210
1288
  `);
@@ -1219,6 +1297,15 @@ function printStatusReportText(report) {
1219
1297
  printSyncedConfigBullets(server, " ");
1220
1298
  }
1221
1299
  }
1300
+ if (report.projects && Object.keys(report.projects).length > 0 && report.daemon.running) {
1301
+ process.stdout.write(`Project active tokens:
1302
+ `);
1303
+ for (const project of Object.values(report.projects)) {
1304
+ const projectTokens = report.totalProjectTokens?.[project.path] ?? 0;
1305
+ process.stdout.write(`- ${project.name}: ${formatTokenApprox(projectTokens)} tokens (${project.path})
1306
+ `);
1307
+ }
1308
+ }
1222
1309
  process.stdout.write(`Client sync states:
1223
1310
  `);
1224
1311
  for (const client of STATUS_CLIENTS) {
@@ -1486,6 +1573,31 @@ async function setServerEnabledInteractively(rl, serverName, enabled, cliPath) {
1486
1573
  `);
1487
1574
  return false;
1488
1575
  }
1576
+ if (serverName.includes(".")) {
1577
+ const parts = serverName.split(".");
1578
+ const projectName = parts[0];
1579
+ const baseServerName = parts.slice(1).join(".");
1580
+ const globalConfigForProjects = loadConfig();
1581
+ if (globalConfigForProjects.projects) {
1582
+ const projectEntry = Object.values(globalConfigForProjects.projects).find((p) => p.name === projectName);
1583
+ if (projectEntry) {
1584
+ const localPath = path.join(projectEntry.path, ".mcpx.json");
1585
+ if (fs.existsSync(localPath)) {
1586
+ const projectConfig = loadProjectConfig(localPath);
1587
+ if (projectConfig.servers[baseServerName]) {
1588
+ setServerEnabled(projectConfig, baseServerName, enabled);
1589
+ saveProjectConfig(projectConfig, localPath);
1590
+ process.stdout.write(`${enabled ? "Enabled" : "Disabled"} server: ${serverName}
1591
+ `);
1592
+ process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1593
+ `);
1594
+ await autoSyncManagedEntries(loadMergedConfig(), cliPath);
1595
+ return true;
1596
+ }
1597
+ }
1598
+ }
1599
+ }
1600
+ }
1489
1601
  const config = loadConfig();
1490
1602
  setServerEnabled(config, serverName, enabled);
1491
1603
  saveConfig(config);
@@ -1493,7 +1605,7 @@ async function setServerEnabledInteractively(rl, serverName, enabled, cliPath) {
1493
1605
  `);
1494
1606
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1495
1607
  `);
1496
- await autoSyncManagedEntries(config, cliPath);
1608
+ await autoSyncManagedEntries(loadMergedConfig(), cliPath);
1497
1609
  return true;
1498
1610
  }
1499
1611
  function buildServerActionTitle(server) {
@@ -1530,7 +1642,7 @@ function buildServerMenuDetail(server) {
1530
1642
  }
1531
1643
  async function runServerActionsMenu(rl, serverName, cliPath) {
1532
1644
  while (true) {
1533
- const report = loadStatusReport();
1645
+ const report = await loadStatusReport();
1534
1646
  const server = report.servers.find((entry) => entry.name === serverName);
1535
1647
  if (!server) {
1536
1648
  process.stdout.write(`Server "${serverName}" no longer exists.
@@ -1577,7 +1689,7 @@ async function runInteractiveStatusMenu(cliPath) {
1577
1689
  });
1578
1690
  try {
1579
1691
  while (true) {
1580
- const report = loadStatusReport();
1692
+ const report = await loadStatusReport();
1581
1693
  if (report.servers.length === 0) {
1582
1694
  clearTerminalScreen();
1583
1695
  process.stdout.write(`mcpx status
@@ -1614,47 +1726,170 @@ async function runInteractiveStatusMenu(cliPath) {
1614
1726
  }
1615
1727
  }
1616
1728
  function registerEnabledCommand(parent, enabled, cliPath) {
1617
- parent.command(enabled ? "enable <name>" : "disable <name>").description(`${enabled ? "Enable" : "Disable"} an upstream MCP server`).action(async (name) => {
1618
- const config = loadConfig();
1619
- setServerEnabled(config, name, enabled);
1620
- saveConfig(config);
1621
- process.stdout.write(`${enabled ? "Enabled" : "Disabled"} server: ${name}
1729
+ parent.command(enabled ? "enable <name>" : "disable <name>").option("-g, --global", "Force target the global configuration").option("-l, --local", "Force target (or initialize) a local `.mcpx.json` configuration").description(`${enabled ? "Enable" : "Disable"} an upstream MCP server`).action(async (name, options) => {
1730
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1731
+ setServerEnabled(context.config, name, enabled);
1732
+ context.save();
1733
+ if (context.type === "project" && context.projectPath) {
1734
+ const globalConfig = loadConfig();
1735
+ registerProject(globalConfig, context.projectPath);
1736
+ saveConfig(globalConfig);
1737
+ }
1738
+ process.stdout.write(`${enabled ? "Enabled" : "Disabled"} server: ${name} (${context.type} config)
1622
1739
  `);
1623
1740
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1624
1741
  `);
1625
- await autoSyncManagedEntries(config, cliPath);
1742
+ const mergedConfig = loadMergedConfig();
1743
+ await autoSyncManagedEntries(mergedConfig, cliPath);
1626
1744
  });
1627
1745
  }
1628
1746
  function registerAddCommand(parent, cliPath) {
1629
- parent.command("add [values...]").allowUnknownOption(true).option("--transport <transport>", "MCP transport (auto|http|stdio)", "auto").option("--header <header>", "Header entry in KEY=VALUE form", (value, previous = []) => [...previous, value], []).option("--env <env>", "Env entry in KEY=VALUE form for stdio upstreams", (value, previous = []) => [...previous, value], []).option("--cwd <cwd>", "Working directory for stdio upstream").option("--force", "Overwrite existing server with same name").description("Add an upstream MCP server").action(async (values, options) => {
1630
- const config = loadConfig();
1631
- const parsed = parseAddServerSpec(values ?? [], options);
1632
- addServer(config, parsed.name, parsed.spec, options.force ?? false);
1633
- await maybeAutoConfigureAuthForAddedServer(parsed.name, parsed.spec, new SecretsManager);
1634
- saveConfig(config);
1635
- process.stdout.write(`Added server: ${parsed.name} (${parsed.spec.transport})
1747
+ parent.command("add [values...]").allowUnknownOption(true).option("--transport <transport>", "MCP transport (auto|http|stdio)", "auto").option("--header <header>", "Header entry in KEY=VALUE form", (value, previous = []) => [...previous, value], []).option("--env <env>", "Env entry in KEY=VALUE form for stdio upstreams", (value, previous = []) => [...previous, value], []).option("--cwd <cwd>", "Working directory for stdio upstream").option("--force", "Overwrite existing server with same name").option("-g, --global", "Force saving to global configuration").option("-l, --local", "Force saving to local project configuration").description("Add an upstream MCP server").action(async (values, options) => {
1748
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1749
+ const safeValues = values ?? [];
1750
+ const secrets = new SecretsManager;
1751
+ if (safeValues.length === 1 && safeValues[0]?.includes("/")) {
1752
+ const registryName = safeValues[0];
1753
+ const shortName = registryName.split("/").pop() ?? registryName;
1754
+ process.stdout.write(`Fetching "${registryName}" from registry...
1755
+ `);
1756
+ const detail = await fetchRegistryServerDetail(registryName);
1757
+ const option = selectBestPackage(detail.packages, detail.remotes);
1758
+ const requiredInputs = extractRequiredInputs(option);
1759
+ const resolvedValues = {};
1760
+ if (requiredInputs.length > 0) {
1761
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1762
+ throw new Error(`Server "${registryName}" requires inputs: ${requiredInputs.map((i) => `${i.name} (${i.kind})`).join(", ")}. Run in an interactive terminal.`);
1763
+ }
1764
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1765
+ try {
1766
+ for (const input of requiredInputs) {
1767
+ const promptText = input.description ? `${input.name} (${input.kind}): ${input.description}
1768
+ Value: ` : `${input.name} (${input.kind}): `;
1769
+ const value = await promptLine(rl, promptText);
1770
+ if (!value) {
1771
+ process.stdout.write(`Skipping ${input.name} (empty value).
1772
+ `);
1773
+ continue;
1774
+ }
1775
+ resolvedValues[input.name] = value;
1776
+ }
1777
+ } finally {
1778
+ rl.close();
1779
+ }
1780
+ } else {
1781
+ process.stdout.write(`No required inputs.
1782
+ `);
1783
+ }
1784
+ let spec = mapRegistryToSpec(shortName, option, resolvedValues);
1785
+ for (const input of requiredInputs) {
1786
+ if (!input.isSecret)
1787
+ continue;
1788
+ const rawValue = resolvedValues[input.name];
1789
+ if (!rawValue)
1790
+ continue;
1791
+ const secretName = `auth_${shortName.toLowerCase().replace(/[^a-z0-9._-]/g, "_")}_${input.kind}_${input.name.toLowerCase().replace(/[^a-z0-9._-]/g, "_")}`;
1792
+ secrets.setSecret(secretName, rawValue);
1793
+ if (input.kind === "header" && spec.transport === "http") {
1794
+ const headers = { ...spec.headers ?? {} };
1795
+ headers[input.name] = `secret://${secretName}`;
1796
+ spec.headers = Object.keys(headers).length > 0 ? headers : undefined;
1797
+ } else if (input.kind === "env" && spec.transport === "stdio") {
1798
+ const env = { ...spec.env ?? {} };
1799
+ env[input.name] = `secret://${secretName}`;
1800
+ spec.env = Object.keys(env).length > 0 ? env : undefined;
1801
+ }
1802
+ }
1803
+ let resolvedServerName = shortName;
1804
+ if (context.type === "project") {
1805
+ const globalConfig = loadConfig();
1806
+ registerProject(globalConfig, context.projectPath, context.config.name);
1807
+ saveConfig(globalConfig);
1808
+ const projectName = context.config.name || path.basename(context.projectPath);
1809
+ resolvedServerName = `${projectName}.${shortName}`;
1810
+ }
1811
+ addServer(context.config, shortName, spec, options.force ?? false);
1812
+ context.save();
1813
+ process.stdout.write(`Added server: ${shortName} (${spec.transport}) from registry "${registryName}" ${context.type === "project" ? `to project: ${context.projectPath}` : "globally"}
1814
+ `);
1815
+ } else {
1816
+ const parsed = parseAddServerSpec(safeValues, options);
1817
+ let resolvedServerName = parsed.name;
1818
+ if (context.type === "project") {
1819
+ const globalConfig = loadConfig();
1820
+ registerProject(globalConfig, context.projectPath, context.config.name);
1821
+ saveConfig(globalConfig);
1822
+ const projectName = context.config.name || path.basename(context.projectPath);
1823
+ resolvedServerName = `${projectName}.${parsed.name}`;
1824
+ }
1825
+ addServer(context.config, parsed.name, parsed.spec, options.force ?? false);
1826
+ await maybeAutoConfigureAuthForAddedServer(resolvedServerName, parsed.spec, secrets);
1827
+ context.save();
1828
+ process.stdout.write(`Added server: ${parsed.name} (${parsed.spec.transport}) ${context.type === "project" ? `to project: ${context.projectPath}` : "globally"}
1636
1829
  `);
1830
+ }
1637
1831
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1638
1832
  `);
1639
- await autoSyncManagedEntries(config, cliPath);
1833
+ const mergedConfig = loadMergedConfig();
1834
+ await autoSyncManagedEntries(mergedConfig, cliPath);
1640
1835
  });
1641
1836
  }
1642
1837
  function registerRemoveCommand(parent, cliPath) {
1643
- parent.command("remove <name>").option("--force", "Do not error if server does not exist").description("Remove an upstream MCP server").action(async (name, options) => {
1644
- const config = loadConfig();
1645
- removeServer(config, name, options.force ?? false);
1646
- saveConfig(config);
1647
- process.stdout.write(`Removed server: ${name}
1838
+ parent.command("remove <name>").option("--force", "Do not error if server does not exist").option("-g, --global", "Force target global configuration").option("-l, --local", "Force target local project configuration").description("Remove an upstream MCP server").action(async (name, options) => {
1839
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1840
+ removeServer(context.config, name, options.force ?? false);
1841
+ context.save();
1842
+ process.stdout.write(`Removed server: ${name} ${context.type === "project" ? `from project: ${context.projectPath}` : "globally"}
1648
1843
  `);
1649
1844
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1650
1845
  `);
1651
- await autoSyncManagedEntries(config, cliPath);
1846
+ const mergedConfig = loadMergedConfig();
1847
+ await autoSyncManagedEntries(mergedConfig, cliPath);
1652
1848
  });
1653
1849
  }
1654
1850
  function registerListCommand(parent) {
1655
- parent.command("list").option("--json", "Output JSON").description("List configured upstream MCP servers").action((options) => {
1656
- const config = loadConfig();
1657
- const servers = Object.entries(config.servers).map(([name, spec]) => ({ name, ...spec }));
1851
+ parent.command("list").option("--json", "Output JSON").option("-g, --global", "Force target global configuration").option("-l, --local", "Force target local project configuration").description("List configured upstream MCP servers").action((options) => {
1852
+ let servers = [];
1853
+ if (options.global || options.local) {
1854
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1855
+ servers = Object.entries(context.config.servers).map(([name, spec]) => ({
1856
+ name,
1857
+ ...spec,
1858
+ projectName: context.type === "project" ? context.config.name || path.basename(context.projectPath) : undefined
1859
+ }));
1860
+ } else {
1861
+ const mergedConfig = loadMergedConfig();
1862
+ const globalConfig = loadConfig();
1863
+ const projectNamesMap = new Map;
1864
+ if (globalConfig.projects) {
1865
+ for (const [projectPath, entry] of Object.entries(globalConfig.projects)) {
1866
+ const localPath = path.join(projectPath, ".mcpx.json");
1867
+ if (fs.existsSync(localPath)) {
1868
+ try {
1869
+ const localConfig = loadProjectConfig(localPath);
1870
+ const pName = localConfig.name || entry.name || path.basename(projectPath);
1871
+ projectNamesMap.set(pName, projectPath);
1872
+ } catch {}
1873
+ }
1874
+ }
1875
+ }
1876
+ servers = Object.entries(mergedConfig.servers).map(([name, spec]) => {
1877
+ let projectName;
1878
+ let baseName = name;
1879
+ for (const pName of projectNamesMap.keys()) {
1880
+ if (name.startsWith(`${pName}.`)) {
1881
+ projectName = pName;
1882
+ baseName = name.slice(pName.length + 1);
1883
+ break;
1884
+ }
1885
+ }
1886
+ return {
1887
+ name: baseName,
1888
+ ...spec,
1889
+ projectName
1890
+ };
1891
+ });
1892
+ }
1658
1893
  if (options.json) {
1659
1894
  process.stdout.write(`${JSON.stringify({ servers }, null, 2)}
1660
1895
  `);
@@ -1667,12 +1902,13 @@ function registerListCommand(parent) {
1667
1902
  }
1668
1903
  for (const server of servers) {
1669
1904
  const state = isServerEnabled(server) ? "enabled" : "disabled";
1905
+ const scope = server.projectName ? `project: ${server.projectName}` : "global";
1670
1906
  if (server.transport === "http") {
1671
- process.stdout.write(`- ${server.name} (${state}, http) ${server.url}
1907
+ process.stdout.write(`- ${server.name} (${state}, http, ${scope}) ${server.url}
1672
1908
  `);
1673
1909
  } else {
1674
1910
  const args = (server.args ?? []).join(" ");
1675
- process.stdout.write(`- ${server.name} (${state}, stdio) ${server.command}${args ? ` ${args}` : ""}
1911
+ process.stdout.write(`- ${server.name} (${state}, stdio, ${scope}) ${server.command}${args ? ` ${args}` : ""}
1676
1912
  `);
1677
1913
  }
1678
1914
  }
@@ -1680,7 +1916,7 @@ function registerListCommand(parent) {
1680
1916
  }
1681
1917
  function registerSyncCommand(program, cliPath) {
1682
1918
  program.command("sync [clients...]").option("--client <id>", "Limit sync to specific client(s), comma-separated or repeated", (value, prev = []) => [...prev, value], []).option("--json", "Output JSON").description("Sync gateway configuration to supported clients (e.g. `mcpx sync claude`)").action(async (clients, options) => {
1683
- const config = loadConfig();
1919
+ const config = loadMergedConfig();
1684
1920
  const targetClients = parseClientList([...clients ?? [], ...options.client ?? []]);
1685
1921
  const secrets = new SecretsManager;
1686
1922
  await ensureDaemonIfEnabled(cliPath, secrets);
@@ -1691,7 +1927,7 @@ function registerSyncCommand(program, cliPath) {
1691
1927
  }
1692
1928
  function registerStatusCommand(program, cliPath) {
1693
1929
  program.command("status").option("--no-interactive", "Disable interactive status menu").option("--json", "Output JSON").description("Show gateway, daemon, MCP inventory, and client sync status").action(async (options) => {
1694
- const report = loadStatusReport();
1930
+ const report = await loadStatusReport();
1695
1931
  if (options.json) {
1696
1932
  process.stdout.write(`${JSON.stringify(report, null, 2)}
1697
1933
  `);
@@ -1720,15 +1956,22 @@ function registerDoctorCommand(program) {
1720
1956
  status: fs.existsSync(getManagedIndexPath()) ? "ok" : "warn",
1721
1957
  details: getManagedIndexPath()
1722
1958
  });
1723
- if (process.platform === "darwin") {
1959
+ try {
1960
+ const keytar = __require(path.join(__dirname, "..", "node_modules", "keytar", "lib", "keytar.js"));
1724
1961
  try {
1725
- execFileSync("security", ["help"], { stdio: "ignore" });
1726
- checks.push({ check: "macos_keychain", status: "ok", details: "security CLI available" });
1727
- } catch {
1728
- checks.push({ check: "macos_keychain", status: "fail", details: "security CLI unavailable" });
1962
+ keytar.getPassword("mcpx-doctor-check", "__probe__");
1963
+ } catch {}
1964
+ checks.push({ check: "keyring", status: "ok", details: "keytar native addon loaded" });
1965
+ } catch {
1966
+ if (process.platform === "linux") {
1967
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable; install libsecret-1-dev and reinstall dependencies" });
1968
+ } else if (process.platform === "win32") {
1969
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable; rebuild keytar with 'npx node-gyp rebuild' in app/node_modules/keytar" });
1970
+ } else if (process.platform === "darwin") {
1971
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable; reinstall dependencies or check Xcode CLI tools" });
1972
+ } else {
1973
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable" });
1729
1974
  }
1730
- } else {
1731
- checks.push({ check: "macos_keychain", status: "warn", details: "Non-macOS platform; keychain integration unavailable" });
1732
1975
  }
1733
1976
  const daemon = getDaemonStatus(config);
1734
1977
  checks.push({
@@ -1839,6 +2082,27 @@ function registerSecretsCommands(program) {
1839
2082
  }
1840
2083
  function registerAuthCommands(program) {
1841
2084
  const auth = program.command("auth").description("Auth utilities");
2085
+ auth.command("login <server>").description("Run browser OAuth login for an HTTP upstream server").action(async (server) => {
2086
+ const config = loadConfig();
2087
+ const spec = getServerSpecOrThrow(config, server);
2088
+ if (spec.transport !== "http") {
2089
+ throw new Error(`OAuth login only supports HTTP servers.`);
2090
+ }
2091
+ const secrets = new SecretsManager;
2092
+ await runOAuthLogin(server, spec, secrets, (url) => {
2093
+ if (process.platform === "darwin") {
2094
+ execFileSync("open", [url], { stdio: "ignore" });
2095
+ return;
2096
+ }
2097
+ if (process.platform === "win32") {
2098
+ execFileSync("cmd", ["/c", "start", "", url], { stdio: "ignore" });
2099
+ return;
2100
+ }
2101
+ execFileSync("xdg-open", [url], { stdio: "ignore" });
2102
+ });
2103
+ process.stdout.write(`OAuth login complete for "${server}".
2104
+ `);
2105
+ });
1842
2106
  auth.command("set <server>").option("--header <name>", "HTTP header name (default for HTTP servers: Authorization)").option("--env <name>", "Env var name (required for stdio servers)").option("--value <value>", "Auth value/token (if omitted, read from stdin)").option("--secret-name <name>", "Override keychain secret name").option("--raw", "Do not auto-prefix Authorization values with Bearer").description("Store auth in keychain and bind it to an upstream server").action((server, options) => {
1843
2107
  const config = loadConfig();
1844
2108
  const spec = getServerSpecOrThrow(config, server);
@@ -2043,6 +2307,66 @@ function registerUpdateCommand(program) {
2043
2307
  }
2044
2308
  });
2045
2309
  }
2310
+ function registerProjectCommands(program, cliPath) {
2311
+ const projectCmd = program.command("project").description("Manage project-based MCP configurations");
2312
+ projectCmd.command("init [name]").description("Initialize a local .mcpx.json configuration in the current directory").action(async (name) => {
2313
+ const targetPath = path.join(process.cwd(), ".mcpx.json");
2314
+ if (fs.existsSync(targetPath)) {
2315
+ process.stderr.write(`Local project configuration already exists at: ${targetPath}
2316
+ `);
2317
+ process.exit(1);
2318
+ }
2319
+ const projectName = name?.trim() || path.basename(process.cwd());
2320
+ const localConfig = {
2321
+ name: projectName,
2322
+ servers: {}
2323
+ };
2324
+ const { saveProjectConfig: saveProjectConfig2 } = await import("./config-79j3jpra.js");
2325
+ saveProjectConfig2(localConfig, targetPath);
2326
+ process.stdout.write(`Initialized project configuration at: ${targetPath}
2327
+ `);
2328
+ const { registerProject: registerProject2 } = await import("./registry-1s14wd58.js");
2329
+ const globalConfig = loadConfig();
2330
+ registerProject2(globalConfig, process.cwd(), projectName);
2331
+ saveConfig(globalConfig);
2332
+ process.stdout.write(`Registered project "${projectName}" globally.
2333
+ `);
2334
+ });
2335
+ projectCmd.command("list").description("List all globally registered project paths").action(() => {
2336
+ const globalConfig = loadConfig();
2337
+ const projects = Object.entries(globalConfig.projects ?? {});
2338
+ if (projects.length === 0) {
2339
+ process.stdout.write(`No projects currently registered.
2340
+ `);
2341
+ return;
2342
+ }
2343
+ process.stdout.write(`Registered Projects:
2344
+ `);
2345
+ for (const [projectPath, entry] of projects) {
2346
+ process.stdout.write(`- ${entry.name}: ${projectPath}
2347
+ `);
2348
+ }
2349
+ });
2350
+ projectCmd.command("remove <path>").description("Unregister a project path from the global configuration").action(async (projectPath) => {
2351
+ const resolvedPath = path.resolve(projectPath);
2352
+ const globalConfig = loadConfig();
2353
+ if (!globalConfig.projects || !globalConfig.projects[resolvedPath]) {
2354
+ process.stderr.write(`No registered project found at path: ${resolvedPath}
2355
+ `);
2356
+ process.exit(1);
2357
+ }
2358
+ const entry = globalConfig.projects[resolvedPath];
2359
+ const { unregisterProject: unregisterProject2 } = await import("./registry-1s14wd58.js");
2360
+ unregisterProject2(globalConfig, resolvedPath);
2361
+ saveConfig(globalConfig);
2362
+ process.stdout.write(`Unregistered project "${entry.name}" (${resolvedPath}) from the global configuration.
2363
+ `);
2364
+ process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
2365
+ `);
2366
+ const mergedConfig = loadMergedConfig();
2367
+ await autoSyncManagedEntries(mergedConfig, cliPath);
2368
+ });
2369
+ }
2046
2370
  function registerProxyCommand(program) {
2047
2371
  program.command("proxy <name>").description("Run a stdio MCP proxy bridging to the local mcpx HTTP gateway for the given upstream server").action(async (name) => {
2048
2372
  await runStdioProxy(name);
@@ -2107,6 +2431,7 @@ async function runCli(argv = process.argv) {
2107
2431
  registerProxyCommand(program);
2108
2432
  registerMcpCompat(program, cliPath);
2109
2433
  registerUpdateCommand(program);
2434
+ registerProjectCommands(program, cliPath);
2110
2435
  await program.parseAsync(argv);
2111
2436
  }
2112
2437
  if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) {
@@ -0,0 +1,20 @@
1
+ import {
2
+ defaultConfig,
3
+ loadConfig,
4
+ loadMergedConfig,
5
+ loadProjectConfig,
6
+ resolveActiveConfig,
7
+ saveConfig,
8
+ saveProjectConfig
9
+ } from "./cli-7edpy4nk.js";
10
+ import"./cli-fsf33sgp.js";
11
+ import"./cli-s4gspqdf.js";
12
+ export {
13
+ saveProjectConfig,
14
+ saveConfig,
15
+ resolveActiveConfig,
16
+ loadProjectConfig,
17
+ loadMergedConfig,
18
+ loadConfig,
19
+ defaultConfig
20
+ };
@@ -0,0 +1,7 @@
1
+ import type { UpstreamServerSpec } from "../types.js";
2
+ export declare function tokenizeCommandLine(command: string): string[];
3
+ export declare function parseCliAddCommand(command: string): {
4
+ name: string;
5
+ spec: UpstreamServerSpec;
6
+ };
7
+ //# sourceMappingURL=add-command.d.ts.map