@kwonye/mcpx 0.1.74 → 0.1.77

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/claude-desktop.d.ts.map +1 -1
  2. package/dist/adapters/cline.d.ts.map +1 -1
  3. package/dist/adapters/cursor.d.ts.map +1 -1
  4. package/dist/adapters/kiro.d.ts +2 -1
  5. package/dist/adapters/kiro.d.ts.map +1 -1
  6. package/dist/adapters/qwen.d.ts +2 -1
  7. package/dist/adapters/qwen.d.ts.map +1 -1
  8. package/dist/adapters/utils/index.d.ts.map +1 -1
  9. package/dist/adapters/vscode.d.ts.map +1 -1
  10. package/dist/cli-7bsf6akp.js +202 -0
  11. package/dist/{cli-33pamqwn.js → cli-f16kphtq.js} +591 -456
  12. package/dist/cli-fsf33sgp.js +105 -0
  13. package/dist/{cli-ezvj44tr.js → cli-htm5cf82.js} +590 -455
  14. package/dist/{cli-q8de7zb0.js → cli-s4gspqdf.js} +4 -1
  15. package/dist/cli-s4py8ckf.js +187 -0
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +506 -85
  18. package/dist/config-kayej21q.js +20 -0
  19. package/dist/core/auth-probe.d.ts.map +1 -1
  20. package/dist/core/config.d.ts +19 -1
  21. package/dist/core/config.d.ts.map +1 -1
  22. package/dist/core/daemon.d.ts.map +1 -1
  23. package/dist/core/index.d.ts +5 -2
  24. package/dist/core/index.d.ts.map +1 -1
  25. package/dist/core/index.js +44 -12
  26. package/dist/core/paths.d.ts +3 -0
  27. package/dist/core/paths.d.ts.map +1 -1
  28. package/dist/core/proxy.d.ts +2 -0
  29. package/dist/core/proxy.d.ts.map +1 -0
  30. package/dist/core/registry-client.d.ts +76 -0
  31. package/dist/core/registry-client.d.ts.map +1 -0
  32. package/dist/core/registry.d.ts +2 -0
  33. package/dist/core/registry.d.ts.map +1 -1
  34. package/dist/core/secrets.d.ts.map +1 -1
  35. package/dist/core/skills.d.ts +6 -0
  36. package/dist/core/skills.d.ts.map +1 -0
  37. package/dist/core/status.d.ts +6 -2
  38. package/dist/core/status.d.ts.map +1 -1
  39. package/dist/core/sync.d.ts.map +1 -1
  40. package/dist/gateway/server.d.ts +17 -0
  41. package/dist/gateway/server.d.ts.map +1 -1
  42. package/dist/registry-1s14wd58.js +24 -0
  43. package/dist/types.d.ts +17 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/types.js +1 -1
  46. package/dist/version.d.ts +1 -1
  47. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -2,48 +2,71 @@
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,
9
+ deleteSkill,
10
+ extractRequiredInputs,
11
+ fetchRegistryServerDetail,
11
12
  getAdapters,
12
- getConfigPath,
13
13
  getDaemonStatus,
14
- getManagedIndexPath,
14
+ getGatewayUrl,
15
+ getSkill,
15
16
  getStagedCliPath,
16
17
  getStagedUpdate,
17
18
  listAuthBindings,
18
- loadConfig,
19
+ listSkills,
19
20
  loadManagedIndex,
21
+ mapRegistryToSpec,
20
22
  maybePrefixBearer,
21
23
  performRollback,
22
24
  performUpdate,
23
25
  probeHttpAuthRequirement,
24
26
  readDaemonLogs,
25
- readSecretValueFromStdin,
26
27
  removeAuthReference,
27
- removeServer,
28
28
  resolveAuthTarget,
29
29
  restartDaemon,
30
30
  runBackgroundUpdate,
31
31
  runDaemonForeground,
32
- saveConfig,
32
+ saveSkill,
33
33
  secretRefName,
34
- setServerEnabled,
34
+ selectBestPackage,
35
35
  startDaemon,
36
36
  stopDaemon,
37
37
  syncAllClients,
38
38
  toSecretRef
39
- } from "./cli-33pamqwn.js";
39
+ } from "./cli-f16kphtq.js";
40
+ import {
41
+ loadConfig,
42
+ loadMergedConfig,
43
+ loadProjectConfig,
44
+ resolveActiveConfig,
45
+ saveConfig,
46
+ saveProjectConfig
47
+ } from "./cli-7bsf6akp.js";
40
48
  import {
49
+ SecretsManager,
50
+ addServer,
51
+ ensureGatewayToken,
52
+ readSecretValueFromStdin,
53
+ registerProject,
54
+ removeServer,
55
+ setServerEnabled
56
+ } from "./cli-s4py8ckf.js";
57
+ import {
58
+ getConfigPath,
59
+ getManagedIndexPath
60
+ } from "./cli-fsf33sgp.js";
61
+ import {
62
+ __require,
41
63
  isServerEnabled
42
- } from "./cli-q8de7zb0.js";
64
+ } from "./cli-s4gspqdf.js";
43
65
 
44
66
  // src/cli.ts
45
67
  import { Command } from "commander";
46
68
  import fs from "node:fs";
69
+ import path from "node:path";
47
70
  import { execFileSync } from "node:child_process";
48
71
  import { pathToFileURL } from "node:url";
49
72
  import { emitKeypressEvents } from "node:readline";
@@ -802,7 +825,79 @@ function parseCompatibilityArgs(argv) {
802
825
  };
803
826
  }
804
827
 
828
+ // src/core/proxy.ts
829
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
830
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
831
+ async function runStdioProxy(serverName) {
832
+ const config = loadConfig();
833
+ const secrets = new SecretsManager;
834
+ const localToken = ensureGatewayToken(config, secrets);
835
+ const gatewayUrl = getGatewayUrl(config);
836
+ if (process.env.MCPX_SKIP_DAEMON_AUTOSTART !== "1") {
837
+ const status = getDaemonStatus(config);
838
+ if (!status.running) {
839
+ const cliPath = process.argv[1] ?? "";
840
+ await startDaemon(config, cliPath, secrets);
841
+ }
842
+ }
843
+ const upstreamUrl = new URL(`${gatewayUrl}?upstream=${encodeURIComponent(serverName)}`);
844
+ const httpTransport = new StreamableHTTPClientTransport(upstreamUrl, {
845
+ requestInit: {
846
+ headers: { Authorization: `Bearer ${localToken}` }
847
+ }
848
+ });
849
+ const stdioTransport = new StdioServerTransport;
850
+ let closed = false;
851
+ const shutdown = async (code = 0) => {
852
+ if (closed)
853
+ return;
854
+ closed = true;
855
+ try {
856
+ await httpTransport.close();
857
+ } catch {}
858
+ try {
859
+ await stdioTransport.close();
860
+ } catch {}
861
+ process.exit(code);
862
+ };
863
+ stdioTransport.onmessage = (msg) => {
864
+ httpTransport.send(msg).catch((err) => {
865
+ process.stderr.write(`[mcpx proxy] gateway send failed: ${err.message}
866
+ `);
867
+ });
868
+ };
869
+ httpTransport.onmessage = (msg) => {
870
+ stdioTransport.send(msg).catch((err) => {
871
+ process.stderr.write(`[mcpx proxy] stdio send failed: ${err.message}
872
+ `);
873
+ });
874
+ };
875
+ httpTransport.onerror = (err) => {
876
+ process.stderr.write(`[mcpx proxy] gateway error: ${err.message}
877
+ `);
878
+ };
879
+ stdioTransport.onerror = (err) => {
880
+ process.stderr.write(`[mcpx proxy] stdio error: ${err.message}
881
+ `);
882
+ };
883
+ httpTransport.onclose = () => {
884
+ shutdown(0);
885
+ };
886
+ stdioTransport.onclose = () => {
887
+ shutdown(0);
888
+ };
889
+ process.on("SIGTERM", () => {
890
+ shutdown(0);
891
+ });
892
+ process.on("SIGINT", () => {
893
+ shutdown(0);
894
+ });
895
+ await httpTransport.start();
896
+ await stdioTransport.start();
897
+ }
898
+
805
899
  // src/cli.ts
900
+ var __dirname = "/home/runner/work/mcpx/mcpx/cli/src";
806
901
  var VALID_CLIENTS = STATUS_CLIENTS;
807
902
  function parseKeyValueFlag(value, label) {
808
903
  const split = value.indexOf("=");
@@ -969,10 +1064,11 @@ async function ensureDaemonIfEnabled(cliPath, secrets) {
969
1064
  }
970
1065
  await startDaemon(config, cliPath, secrets);
971
1066
  }
972
- async function autoSyncManagedEntries(config) {
1067
+ async function autoSyncManagedEntries(config, cliPath) {
973
1068
  const secrets = new SecretsManager;
974
- await ensureDaemonIfEnabled(process.argv[1] ?? "", secrets);
975
- const summary = syncAllClients(config, secrets);
1069
+ await ensureDaemonIfEnabled(cliPath, secrets);
1070
+ const mergedConfig = loadMergedConfig();
1071
+ const summary = syncAllClients(mergedConfig, secrets);
976
1072
  printSyncSummary(summary, false);
977
1073
  ensureExitCodeForSyncFailures(summary.hasErrors);
978
1074
  }
@@ -981,9 +1077,13 @@ function isNegativeChoice(value) {
981
1077
  return normalized === "n" || normalized === "no";
982
1078
  }
983
1079
  async function maybeAutoConfigureAuthForAddedServer(serverName, spec, secrets) {
984
- if (spec.transport !== "http") {
985
- return;
1080
+ if (spec.transport === "http") {
1081
+ await maybeConfigureHttpAuth(serverName, spec, secrets);
1082
+ } else {
1083
+ await maybeConfigureStdioAuth(serverName, spec, secrets);
986
1084
  }
1085
+ }
1086
+ async function maybeConfigureHttpAuth(serverName, spec, secrets) {
987
1087
  const probe = await probeHttpAuthRequirement(spec, secrets);
988
1088
  if (!probe.authRequired) {
989
1089
  return;
@@ -1056,9 +1156,48 @@ async function maybeAutoConfigureAuthForAddedServer(serverName, spec, secrets) {
1056
1156
  `);
1057
1157
  }
1058
1158
  }
1059
- function loadStatusReport() {
1060
- const config = loadConfig();
1061
- return buildStatusReport(config, loadManagedIndex(), getDaemonStatus(config));
1159
+ async function maybeConfigureStdioAuth(serverName, spec, secrets) {
1160
+ const binds = listAuthBindings(spec);
1161
+ const plainEnvVars = binds.filter((b) => b.kind === "env" && !secretRefName(b.value));
1162
+ if (plainEnvVars.length === 0) {
1163
+ return;
1164
+ }
1165
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1166
+ process.stdout.write(`Server "${serverName}" has plain-text env vars. Run \`mcpx auth set ${serverName} --env KEY=VALUE --value "<token>"\` to store them securely.
1167
+ `);
1168
+ return;
1169
+ }
1170
+ process.stdout.write(`Server "${serverName}" has ${plainEnvVars.length} env var(s) stored in plain text.
1171
+ `);
1172
+ const rl = createInterface({
1173
+ input: process.stdin,
1174
+ output: process.stdout
1175
+ });
1176
+ try {
1177
+ const shouldMigrate = await promptLine(rl, "Store them in the system keychain instead? (Y/n): ");
1178
+ if (isNegativeChoice(shouldMigrate)) {
1179
+ process.stdout.write(`Skipping keychain migration.
1180
+ `);
1181
+ return;
1182
+ }
1183
+ for (const binding of plainEnvVars) {
1184
+ const target = { kind: "env", key: binding.key };
1185
+ const secretName = defaultAuthSecretName(serverName, target);
1186
+ secrets.setSecret(secretName, binding.value);
1187
+ applyAuthReference(spec, target, toSecretRef(secretName));
1188
+ process.stdout.write(`Stored ${binding.key} as secret://${secretName}
1189
+ `);
1190
+ }
1191
+ } catch (error) {
1192
+ process.stdout.write(`Keychain migration skipped: ${error.message}
1193
+ `);
1194
+ } finally {
1195
+ rl.close();
1196
+ }
1197
+ }
1198
+ async function loadStatusReport() {
1199
+ const config = loadMergedConfig();
1200
+ return await buildStatusReport(config, loadManagedIndex(), getDaemonStatus(config));
1062
1201
  }
1063
1202
  function daemonEmoji(running) {
1064
1203
  return running ? "✅" : "⚠️";
@@ -1115,6 +1254,16 @@ function printSyncedConfigBullets(server, indent) {
1115
1254
  `);
1116
1255
  }
1117
1256
  }
1257
+ function formatTokenApprox(tokens) {
1258
+ if (tokens === 0)
1259
+ return "0";
1260
+ if (tokens > 0 && tokens < 500)
1261
+ return "<1k";
1262
+ const thousands = Math.round(tokens / 1000);
1263
+ if (thousands === 0)
1264
+ return "<1k";
1265
+ return `~${thousands}k`;
1266
+ }
1118
1267
  function printStatusReportText(report) {
1119
1268
  process.stdout.write(`Gateway URL: ${report.gatewayUrl}
1120
1269
  `);
@@ -1122,12 +1271,17 @@ function printStatusReportText(report) {
1122
1271
  `);
1123
1272
  process.stdout.write(`Upstream servers: ${report.upstreamCount}
1124
1273
  `);
1274
+ if (report.daemon.running) {
1275
+ process.stdout.write(`Global active tokens: ${formatTokenApprox(report.totalGlobalTokens ?? 0)}
1276
+ `);
1277
+ }
1125
1278
  if (report.servers.length === 0) {
1126
1279
  process.stdout.write(`⚠️ No upstream servers configured.
1127
1280
  `);
1128
1281
  } else {
1129
1282
  for (const server of report.servers) {
1130
- process.stdout.write(`- ${serverHealthEmoji(server)} ${server.name} (${server.transport})
1283
+ const tokenSuffix = server.enabled && server.tokenCount ? ` [tokens: ${formatTokenApprox(server.tokenCount.total)} (tools: ${server.tokenCount.tools}, resources: ${server.tokenCount.resources}, prompts: ${server.tokenCount.prompts})]` : "";
1284
+ process.stdout.write(`- ${serverHealthEmoji(server)} ${server.name} (${server.transport})${tokenSuffix}
1131
1285
  `);
1132
1286
  process.stdout.write(` state: ${server.enabled ? "enabled" : "disabled"}
1133
1287
  `);
@@ -1142,6 +1296,15 @@ function printStatusReportText(report) {
1142
1296
  printSyncedConfigBullets(server, " ");
1143
1297
  }
1144
1298
  }
1299
+ if (report.projects && Object.keys(report.projects).length > 0 && report.daemon.running) {
1300
+ process.stdout.write(`Project active tokens:
1301
+ `);
1302
+ for (const project of Object.values(report.projects)) {
1303
+ const projectTokens = report.totalProjectTokens?.[project.path] ?? 0;
1304
+ process.stdout.write(`- ${project.name}: ${formatTokenApprox(projectTokens)} tokens (${project.path})
1305
+ `);
1306
+ }
1307
+ }
1145
1308
  process.stdout.write(`Client sync states:
1146
1309
  `);
1147
1310
  for (const client of STATUS_CLIENTS) {
@@ -1392,16 +1555,16 @@ async function clearServerAuthInteractively(rl, serverName) {
1392
1555
  }
1393
1556
  saveConfig(config);
1394
1557
  }
1395
- async function reconnectGatewayAndSync() {
1558
+ async function reconnectGatewayAndSync(cliPath) {
1396
1559
  const config = loadConfig();
1397
1560
  const secrets = new SecretsManager;
1398
- const restart = await restartDaemon(config, process.argv[1] ?? "", secrets);
1561
+ const restart = await restartDaemon(config, cliPath, secrets);
1399
1562
  process.stdout.write(`${restart.message} pid=${restart.pid} port=${restart.port}
1400
1563
  `);
1401
1564
  const summary = syncAllClients(config, secrets);
1402
1565
  printSyncSummary(summary, false);
1403
1566
  }
1404
- async function setServerEnabledInteractively(rl, serverName, enabled) {
1567
+ async function setServerEnabledInteractively(rl, serverName, enabled, cliPath) {
1405
1568
  const action = enabled ? "enable" : "disable";
1406
1569
  const confirmation = await promptLine(rl, `Type "${serverName}" to ${action} this MCP (blank to cancel): `);
1407
1570
  if (confirmation !== serverName) {
@@ -1409,6 +1572,31 @@ async function setServerEnabledInteractively(rl, serverName, enabled) {
1409
1572
  `);
1410
1573
  return false;
1411
1574
  }
1575
+ if (serverName.includes(".")) {
1576
+ const parts = serverName.split(".");
1577
+ const projectName = parts[0];
1578
+ const baseServerName = parts.slice(1).join(".");
1579
+ const globalConfigForProjects = loadConfig();
1580
+ if (globalConfigForProjects.projects) {
1581
+ const projectEntry = Object.values(globalConfigForProjects.projects).find((p) => p.name === projectName);
1582
+ if (projectEntry) {
1583
+ const localPath = path.join(projectEntry.path, ".mcpx.json");
1584
+ if (fs.existsSync(localPath)) {
1585
+ const projectConfig = loadProjectConfig(localPath);
1586
+ if (projectConfig.servers[baseServerName]) {
1587
+ setServerEnabled(projectConfig, baseServerName, enabled);
1588
+ saveProjectConfig(projectConfig, localPath);
1589
+ process.stdout.write(`${enabled ? "Enabled" : "Disabled"} server: ${serverName}
1590
+ `);
1591
+ process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1592
+ `);
1593
+ await autoSyncManagedEntries(loadMergedConfig(), cliPath);
1594
+ return true;
1595
+ }
1596
+ }
1597
+ }
1598
+ }
1599
+ }
1412
1600
  const config = loadConfig();
1413
1601
  setServerEnabled(config, serverName, enabled);
1414
1602
  saveConfig(config);
@@ -1416,7 +1604,7 @@ async function setServerEnabledInteractively(rl, serverName, enabled) {
1416
1604
  `);
1417
1605
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1418
1606
  `);
1419
- await autoSyncManagedEntries(config);
1607
+ await autoSyncManagedEntries(loadMergedConfig(), cliPath);
1420
1608
  return true;
1421
1609
  }
1422
1610
  function buildServerActionTitle(server) {
@@ -1451,9 +1639,9 @@ function buildServerMenuDetail(server) {
1451
1639
  const authSummary = server.authBindings.length > 0 ? "\uD83D\uDD10 auth configured • " : "";
1452
1640
  return `${authSummary}${syncSummary}${errorSummary}`;
1453
1641
  }
1454
- async function runServerActionsMenu(rl, serverName) {
1642
+ async function runServerActionsMenu(rl, serverName, cliPath) {
1455
1643
  while (true) {
1456
- const report = loadStatusReport();
1644
+ const report = await loadStatusReport();
1457
1645
  const server = report.servers.find((entry) => entry.name === serverName);
1458
1646
  if (!server) {
1459
1647
  process.stdout.write(`Server "${serverName}" no longer exists.
@@ -1479,12 +1667,12 @@ async function runServerActionsMenu(rl, serverName) {
1479
1667
  } else if (action === 2) {
1480
1668
  await clearServerAuthInteractively(rl, serverName);
1481
1669
  } else if (action === 3) {
1482
- const updated = await setServerEnabledInteractively(rl, serverName, !server.enabled);
1670
+ const updated = await setServerEnabledInteractively(rl, serverName, !server.enabled, cliPath);
1483
1671
  if (updated) {
1484
1672
  return;
1485
1673
  }
1486
1674
  } else if (action === 4) {
1487
- await reconnectGatewayAndSync();
1675
+ await reconnectGatewayAndSync(cliPath);
1488
1676
  }
1489
1677
  } catch (error) {
1490
1678
  process.stdout.write(`${error.message}
@@ -1493,14 +1681,14 @@ async function runServerActionsMenu(rl, serverName) {
1493
1681
  await promptLine(rl, "Press Enter to continue...");
1494
1682
  }
1495
1683
  }
1496
- async function runInteractiveStatusMenu() {
1684
+ async function runInteractiveStatusMenu(cliPath) {
1497
1685
  const rl = createInterface({
1498
1686
  input: process.stdin,
1499
1687
  output: process.stdout
1500
1688
  });
1501
1689
  try {
1502
1690
  while (true) {
1503
- const report = loadStatusReport();
1691
+ const report = await loadStatusReport();
1504
1692
  if (report.servers.length === 0) {
1505
1693
  clearTerminalScreen();
1506
1694
  process.stdout.write(`mcpx status
@@ -1530,54 +1718,177 @@ async function runInteractiveStatusMenu() {
1530
1718
  if (!server) {
1531
1719
  continue;
1532
1720
  }
1533
- await runServerActionsMenu(rl, server.name);
1721
+ await runServerActionsMenu(rl, server.name, cliPath);
1534
1722
  }
1535
1723
  } finally {
1536
1724
  rl.close();
1537
1725
  }
1538
1726
  }
1539
- function registerEnabledCommand(parent, enabled) {
1540
- parent.command(enabled ? "enable <name>" : "disable <name>").description(`${enabled ? "Enable" : "Disable"} an upstream MCP server`).action(async (name) => {
1541
- const config = loadConfig();
1542
- setServerEnabled(config, name, enabled);
1543
- saveConfig(config);
1544
- process.stdout.write(`${enabled ? "Enabled" : "Disabled"} server: ${name}
1727
+ function registerEnabledCommand(parent, enabled, cliPath) {
1728
+ 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) => {
1729
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1730
+ setServerEnabled(context.config, name, enabled);
1731
+ context.save();
1732
+ if (context.type === "project" && context.projectPath) {
1733
+ const globalConfig = loadConfig();
1734
+ registerProject(globalConfig, context.projectPath);
1735
+ saveConfig(globalConfig);
1736
+ }
1737
+ process.stdout.write(`${enabled ? "Enabled" : "Disabled"} server: ${name} (${context.type} config)
1545
1738
  `);
1546
1739
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1547
1740
  `);
1548
- await autoSyncManagedEntries(config);
1741
+ const mergedConfig = loadMergedConfig();
1742
+ await autoSyncManagedEntries(mergedConfig, cliPath);
1549
1743
  });
1550
1744
  }
1551
- function registerAddCommand(parent) {
1552
- 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) => {
1553
- const config = loadConfig();
1554
- const parsed = parseAddServerSpec(values ?? [], options);
1555
- addServer(config, parsed.name, parsed.spec, options.force ?? false);
1556
- await maybeAutoConfigureAuthForAddedServer(parsed.name, parsed.spec, new SecretsManager);
1557
- saveConfig(config);
1558
- process.stdout.write(`Added server: ${parsed.name} (${parsed.spec.transport})
1745
+ function registerAddCommand(parent, cliPath) {
1746
+ 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) => {
1747
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1748
+ const safeValues = values ?? [];
1749
+ const secrets = new SecretsManager;
1750
+ if (safeValues.length === 1 && safeValues[0]?.includes("/")) {
1751
+ const registryName = safeValues[0];
1752
+ const shortName = registryName.split("/").pop() ?? registryName;
1753
+ process.stdout.write(`Fetching "${registryName}" from registry...
1754
+ `);
1755
+ const detail = await fetchRegistryServerDetail(registryName);
1756
+ const option = selectBestPackage(detail.packages, detail.remotes);
1757
+ const requiredInputs = extractRequiredInputs(option);
1758
+ const resolvedValues = {};
1759
+ if (requiredInputs.length > 0) {
1760
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1761
+ throw new Error(`Server "${registryName}" requires inputs: ${requiredInputs.map((i) => `${i.name} (${i.kind})`).join(", ")}. Run in an interactive terminal.`);
1762
+ }
1763
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1764
+ try {
1765
+ for (const input of requiredInputs) {
1766
+ const promptText = input.description ? `${input.name} (${input.kind}): ${input.description}
1767
+ Value: ` : `${input.name} (${input.kind}): `;
1768
+ const value = await promptLine(rl, promptText);
1769
+ if (!value) {
1770
+ process.stdout.write(`Skipping ${input.name} (empty value).
1771
+ `);
1772
+ continue;
1773
+ }
1774
+ resolvedValues[input.name] = value;
1775
+ }
1776
+ } finally {
1777
+ rl.close();
1778
+ }
1779
+ } else {
1780
+ process.stdout.write(`No required inputs.
1781
+ `);
1782
+ }
1783
+ let spec = mapRegistryToSpec(shortName, option, resolvedValues);
1784
+ for (const input of requiredInputs) {
1785
+ if (!input.isSecret)
1786
+ continue;
1787
+ const rawValue = resolvedValues[input.name];
1788
+ if (!rawValue)
1789
+ continue;
1790
+ const secretName = `auth_${shortName.toLowerCase().replace(/[^a-z0-9._-]/g, "_")}_${input.kind}_${input.name.toLowerCase().replace(/[^a-z0-9._-]/g, "_")}`;
1791
+ secrets.setSecret(secretName, rawValue);
1792
+ if (input.kind === "header" && spec.transport === "http") {
1793
+ const headers = { ...spec.headers ?? {} };
1794
+ headers[input.name] = `secret://${secretName}`;
1795
+ spec.headers = Object.keys(headers).length > 0 ? headers : undefined;
1796
+ } else if (input.kind === "env" && spec.transport === "stdio") {
1797
+ const env = { ...spec.env ?? {} };
1798
+ env[input.name] = `secret://${secretName}`;
1799
+ spec.env = Object.keys(env).length > 0 ? env : undefined;
1800
+ }
1801
+ }
1802
+ let resolvedServerName = shortName;
1803
+ if (context.type === "project") {
1804
+ const globalConfig = loadConfig();
1805
+ registerProject(globalConfig, context.projectPath, context.config.name);
1806
+ saveConfig(globalConfig);
1807
+ const projectName = context.config.name || path.basename(context.projectPath);
1808
+ resolvedServerName = `${projectName}.${shortName}`;
1809
+ }
1810
+ addServer(context.config, shortName, spec, options.force ?? false);
1811
+ context.save();
1812
+ process.stdout.write(`Added server: ${shortName} (${spec.transport}) from registry "${registryName}" ${context.type === "project" ? `to project: ${context.projectPath}` : "globally"}
1559
1813
  `);
1814
+ } else {
1815
+ const parsed = parseAddServerSpec(safeValues, options);
1816
+ let resolvedServerName = parsed.name;
1817
+ if (context.type === "project") {
1818
+ const globalConfig = loadConfig();
1819
+ registerProject(globalConfig, context.projectPath, context.config.name);
1820
+ saveConfig(globalConfig);
1821
+ const projectName = context.config.name || path.basename(context.projectPath);
1822
+ resolvedServerName = `${projectName}.${parsed.name}`;
1823
+ }
1824
+ addServer(context.config, parsed.name, parsed.spec, options.force ?? false);
1825
+ await maybeAutoConfigureAuthForAddedServer(resolvedServerName, parsed.spec, secrets);
1826
+ context.save();
1827
+ process.stdout.write(`Added server: ${parsed.name} (${parsed.spec.transport}) ${context.type === "project" ? `to project: ${context.projectPath}` : "globally"}
1828
+ `);
1829
+ }
1560
1830
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1561
1831
  `);
1562
- await autoSyncManagedEntries(config);
1832
+ const mergedConfig = loadMergedConfig();
1833
+ await autoSyncManagedEntries(mergedConfig, cliPath);
1563
1834
  });
1564
1835
  }
1565
- function registerRemoveCommand(parent) {
1566
- parent.command("remove <name>").option("--force", "Do not error if server does not exist").description("Remove an upstream MCP server").action(async (name, options) => {
1567
- const config = loadConfig();
1568
- removeServer(config, name, options.force ?? false);
1569
- saveConfig(config);
1570
- process.stdout.write(`Removed server: ${name}
1836
+ function registerRemoveCommand(parent, cliPath) {
1837
+ 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) => {
1838
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1839
+ removeServer(context.config, name, options.force ?? false);
1840
+ context.save();
1841
+ process.stdout.write(`Removed server: ${name} ${context.type === "project" ? `from project: ${context.projectPath}` : "globally"}
1571
1842
  `);
1572
1843
  process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
1573
1844
  `);
1574
- await autoSyncManagedEntries(config);
1845
+ const mergedConfig = loadMergedConfig();
1846
+ await autoSyncManagedEntries(mergedConfig, cliPath);
1575
1847
  });
1576
1848
  }
1577
1849
  function registerListCommand(parent) {
1578
- parent.command("list").option("--json", "Output JSON").description("List configured upstream MCP servers").action((options) => {
1579
- const config = loadConfig();
1580
- const servers = Object.entries(config.servers).map(([name, spec]) => ({ name, ...spec }));
1850
+ 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) => {
1851
+ let servers = [];
1852
+ if (options.global || options.local) {
1853
+ const context = resolveActiveConfig({ global: options.global, local: options.local });
1854
+ servers = Object.entries(context.config.servers).map(([name, spec]) => ({
1855
+ name,
1856
+ ...spec,
1857
+ projectName: context.type === "project" ? context.config.name || path.basename(context.projectPath) : undefined
1858
+ }));
1859
+ } else {
1860
+ const mergedConfig = loadMergedConfig();
1861
+ const globalConfig = loadConfig();
1862
+ const projectNamesMap = new Map;
1863
+ if (globalConfig.projects) {
1864
+ for (const [projectPath, entry] of Object.entries(globalConfig.projects)) {
1865
+ const localPath = path.join(projectPath, ".mcpx.json");
1866
+ if (fs.existsSync(localPath)) {
1867
+ try {
1868
+ const localConfig = loadProjectConfig(localPath);
1869
+ const pName = localConfig.name || entry.name || path.basename(projectPath);
1870
+ projectNamesMap.set(pName, projectPath);
1871
+ } catch {}
1872
+ }
1873
+ }
1874
+ }
1875
+ servers = Object.entries(mergedConfig.servers).map(([name, spec]) => {
1876
+ let projectName;
1877
+ let baseName = name;
1878
+ for (const pName of projectNamesMap.keys()) {
1879
+ if (name.startsWith(`${pName}.`)) {
1880
+ projectName = pName;
1881
+ baseName = name.slice(pName.length + 1);
1882
+ break;
1883
+ }
1884
+ }
1885
+ return {
1886
+ name: baseName,
1887
+ ...spec,
1888
+ projectName
1889
+ };
1890
+ });
1891
+ }
1581
1892
  if (options.json) {
1582
1893
  process.stdout.write(`${JSON.stringify({ servers }, null, 2)}
1583
1894
  `);
@@ -1590,31 +1901,32 @@ function registerListCommand(parent) {
1590
1901
  }
1591
1902
  for (const server of servers) {
1592
1903
  const state = isServerEnabled(server) ? "enabled" : "disabled";
1904
+ const scope = server.projectName ? `project: ${server.projectName}` : "global";
1593
1905
  if (server.transport === "http") {
1594
- process.stdout.write(`- ${server.name} (${state}, http) ${server.url}
1906
+ process.stdout.write(`- ${server.name} (${state}, http, ${scope}) ${server.url}
1595
1907
  `);
1596
1908
  } else {
1597
1909
  const args = (server.args ?? []).join(" ");
1598
- process.stdout.write(`- ${server.name} (${state}, stdio) ${server.command}${args ? ` ${args}` : ""}
1910
+ process.stdout.write(`- ${server.name} (${state}, stdio, ${scope}) ${server.command}${args ? ` ${args}` : ""}
1599
1911
  `);
1600
1912
  }
1601
1913
  }
1602
1914
  });
1603
1915
  }
1604
- function registerSyncCommand(program) {
1916
+ function registerSyncCommand(program, cliPath) {
1605
1917
  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) => {
1606
- const config = loadConfig();
1918
+ const config = loadMergedConfig();
1607
1919
  const targetClients = parseClientList([...clients ?? [], ...options.client ?? []]);
1608
1920
  const secrets = new SecretsManager;
1609
- await ensureDaemonIfEnabled(process.argv[1] ?? "", secrets);
1921
+ await ensureDaemonIfEnabled(cliPath, secrets);
1610
1922
  const summary = syncAllClients(config, secrets, targetClients);
1611
1923
  printSyncSummary(summary, options.json ?? false);
1612
1924
  ensureExitCodeForSyncFailures(summary.hasErrors);
1613
1925
  });
1614
1926
  }
1615
- function registerStatusCommand(program) {
1927
+ function registerStatusCommand(program, cliPath) {
1616
1928
  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) => {
1617
- const report = loadStatusReport();
1929
+ const report = await loadStatusReport();
1618
1930
  if (options.json) {
1619
1931
  process.stdout.write(`${JSON.stringify(report, null, 2)}
1620
1932
  `);
@@ -1622,7 +1934,7 @@ function registerStatusCommand(program) {
1622
1934
  }
1623
1935
  const interactive = (options.interactive ?? true) && process.stdin.isTTY && process.stdout.isTTY;
1624
1936
  if (interactive) {
1625
- await runInteractiveStatusMenu();
1937
+ await runInteractiveStatusMenu(cliPath);
1626
1938
  return;
1627
1939
  }
1628
1940
  printStatusReportText(report);
@@ -1643,15 +1955,22 @@ function registerDoctorCommand(program) {
1643
1955
  status: fs.existsSync(getManagedIndexPath()) ? "ok" : "warn",
1644
1956
  details: getManagedIndexPath()
1645
1957
  });
1646
- if (process.platform === "darwin") {
1958
+ try {
1959
+ const keytar = __require(path.join(__dirname, "..", "node_modules", "keytar", "lib", "keytar.js"));
1647
1960
  try {
1648
- execFileSync("security", ["help"], { stdio: "ignore" });
1649
- checks.push({ check: "macos_keychain", status: "ok", details: "security CLI available" });
1650
- } catch {
1651
- checks.push({ check: "macos_keychain", status: "fail", details: "security CLI unavailable" });
1961
+ keytar.getPassword("mcpx-doctor-check", "__probe__");
1962
+ } catch {}
1963
+ checks.push({ check: "keyring", status: "ok", details: "keytar native addon loaded" });
1964
+ } catch {
1965
+ if (process.platform === "linux") {
1966
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable; install libsecret-1-dev and reinstall dependencies" });
1967
+ } else if (process.platform === "win32") {
1968
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable; rebuild keytar with 'npx node-gyp rebuild' in app/node_modules/keytar" });
1969
+ } else if (process.platform === "darwin") {
1970
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable; reinstall dependencies or check Xcode CLI tools" });
1971
+ } else {
1972
+ checks.push({ check: "keyring", status: "warn", details: "keytar native addon unavailable" });
1652
1973
  }
1653
- } else {
1654
- checks.push({ check: "macos_keychain", status: "warn", details: "Non-macOS platform; keychain integration unavailable" });
1655
1974
  }
1656
1975
  const daemon = getDaemonStatus(config);
1657
1976
  checks.push({
@@ -1856,6 +2175,39 @@ function registerAuthCommands(program) {
1856
2175
  process.stdout.write("Run `mcpx sync <client>` (or `mcpx sync`) to update managed `mcpx` entries with the new token.\n");
1857
2176
  });
1858
2177
  }
2178
+ function registerSkillsCommands(program) {
2179
+ const skill = program.command("skill").description("Manage shared agent skills");
2180
+ skill.command("ls").description("List all skills").action(() => {
2181
+ const skills = listSkills();
2182
+ if (skills.length === 0) {
2183
+ process.stdout.write(`No skills found.
2184
+ `);
2185
+ return;
2186
+ }
2187
+ for (const s of skills) {
2188
+ process.stdout.write(`- ${s.id}
2189
+ `);
2190
+ }
2191
+ });
2192
+ skill.command("add <id>").description("Add a new skill").action(async (id) => {
2193
+ const existing = getSkill(id);
2194
+ if (existing) {
2195
+ process.stderr.write(`Skill "${id}" already exists.
2196
+ `);
2197
+ process.exit(1);
2198
+ }
2199
+ saveSkill(id, `# ${id}
2200
+
2201
+ Add your instructions here.`);
2202
+ process.stdout.write(`Skill "${id}" created.
2203
+ `);
2204
+ });
2205
+ skill.command("rm <id>").description("Remove a skill").action((id) => {
2206
+ deleteSkill(id);
2207
+ process.stdout.write(`Skill "${id}" removed.
2208
+ `);
2209
+ });
2210
+ }
1859
2211
  function registerClientsCommands(program) {
1860
2212
  const clients = program.command("clients").description("Client adapter utilities");
1861
2213
  clients.command("list").option("--json", "Output JSON").description("List supported clients and detected config paths").action((options) => {
@@ -1933,12 +2285,77 @@ function registerUpdateCommand(program) {
1933
2285
  }
1934
2286
  });
1935
2287
  }
1936
- function registerMcpCompat(program) {
2288
+ function registerProjectCommands(program, cliPath) {
2289
+ const projectCmd = program.command("project").description("Manage project-based MCP configurations");
2290
+ projectCmd.command("init [name]").description("Initialize a local .mcpx.json configuration in the current directory").action(async (name) => {
2291
+ const targetPath = path.join(process.cwd(), ".mcpx.json");
2292
+ if (fs.existsSync(targetPath)) {
2293
+ process.stderr.write(`Local project configuration already exists at: ${targetPath}
2294
+ `);
2295
+ process.exit(1);
2296
+ }
2297
+ const projectName = name?.trim() || path.basename(process.cwd());
2298
+ const localConfig = {
2299
+ name: projectName,
2300
+ servers: {}
2301
+ };
2302
+ const { saveProjectConfig: saveProjectConfig2 } = await import("./config-kayej21q.js");
2303
+ saveProjectConfig2(localConfig, targetPath);
2304
+ process.stdout.write(`Initialized project configuration at: ${targetPath}
2305
+ `);
2306
+ const { registerProject: registerProject2 } = await import("./registry-1s14wd58.js");
2307
+ const globalConfig = loadConfig();
2308
+ registerProject2(globalConfig, process.cwd(), projectName);
2309
+ saveConfig(globalConfig);
2310
+ process.stdout.write(`Registered project "${projectName}" globally.
2311
+ `);
2312
+ });
2313
+ projectCmd.command("list").description("List all globally registered project paths").action(() => {
2314
+ const globalConfig = loadConfig();
2315
+ const projects = Object.entries(globalConfig.projects ?? {});
2316
+ if (projects.length === 0) {
2317
+ process.stdout.write(`No projects currently registered.
2318
+ `);
2319
+ return;
2320
+ }
2321
+ process.stdout.write(`Registered Projects:
2322
+ `);
2323
+ for (const [projectPath, entry] of projects) {
2324
+ process.stdout.write(`- ${entry.name}: ${projectPath}
2325
+ `);
2326
+ }
2327
+ });
2328
+ projectCmd.command("remove <path>").description("Unregister a project path from the global configuration").action(async (projectPath) => {
2329
+ const resolvedPath = path.resolve(projectPath);
2330
+ const globalConfig = loadConfig();
2331
+ if (!globalConfig.projects || !globalConfig.projects[resolvedPath]) {
2332
+ process.stderr.write(`No registered project found at path: ${resolvedPath}
2333
+ `);
2334
+ process.exit(1);
2335
+ }
2336
+ const entry = globalConfig.projects[resolvedPath];
2337
+ const { unregisterProject: unregisterProject2 } = await import("./registry-1s14wd58.js");
2338
+ unregisterProject2(globalConfig, resolvedPath);
2339
+ saveConfig(globalConfig);
2340
+ process.stdout.write(`Unregistered project "${entry.name}" (${resolvedPath}) from the global configuration.
2341
+ `);
2342
+ process.stdout.write(`Auto-syncing managed gateway entries across all supported clients...
2343
+ `);
2344
+ const mergedConfig = loadMergedConfig();
2345
+ await autoSyncManagedEntries(mergedConfig, cliPath);
2346
+ });
2347
+ }
2348
+ function registerProxyCommand(program) {
2349
+ 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) => {
2350
+ await runStdioProxy(name);
2351
+ });
2352
+ }
2353
+ function registerMcpCompat(program, cliPath) {
1937
2354
  const mcp = program.command("mcp").description("Compatibility namespace for MCP commands");
1938
- registerAddCommand(mcp);
1939
- registerRemoveCommand(mcp);
1940
- registerEnabledCommand(mcp, true);
1941
- registerEnabledCommand(mcp, false);
2355
+ registerAddCommand(mcp, cliPath);
2356
+ registerRemoveCommand(mcp, cliPath);
2357
+ registerEnabledCommand(mcp, true, cliPath);
2358
+ registerEnabledCommand(mcp, false, cliPath);
1942
2359
  registerListCommand(mcp);
1943
2360
  }
1944
2361
  async function runCli(argv = process.argv) {
@@ -1973,25 +2390,29 @@ async function runCli(argv = process.argv) {
1973
2390
  argv = [argv[0], argv[1], "add", ...compat.normalizedArgs];
1974
2391
  }
1975
2392
  }
2393
+ const cliPath = argv[1] ?? "";
1976
2394
  const program = new Command;
1977
2395
  program.name("mcpx").description("HTTP-first MCP gateway and multi-client installer").version(APP_VERSION);
1978
- registerAddCommand(program);
1979
- registerRemoveCommand(program);
1980
- registerEnabledCommand(program, true);
1981
- registerEnabledCommand(program, false);
2396
+ registerAddCommand(program, cliPath);
2397
+ registerRemoveCommand(program, cliPath);
2398
+ registerEnabledCommand(program, true, cliPath);
2399
+ registerEnabledCommand(program, false, cliPath);
1982
2400
  registerListCommand(program);
1983
- registerSyncCommand(program);
1984
- registerStatusCommand(program);
2401
+ registerSyncCommand(program, cliPath);
2402
+ registerStatusCommand(program, cliPath);
1985
2403
  registerDoctorCommand(program);
1986
2404
  registerDaemonCommands(program);
1987
2405
  registerSecretsCommands(program);
1988
2406
  registerAuthCommands(program);
1989
2407
  registerClientsCommands(program);
1990
- registerMcpCompat(program);
2408
+ registerSkillsCommands(program);
2409
+ registerProxyCommand(program);
2410
+ registerMcpCompat(program, cliPath);
1991
2411
  registerUpdateCommand(program);
2412
+ registerProjectCommands(program, cliPath);
1992
2413
  await program.parseAsync(argv);
1993
2414
  }
1994
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
2415
+ if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) {
1995
2416
  runCli().catch((error) => {
1996
2417
  process.stderr.write(`${error.message}
1997
2418
  `);