@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.
- package/dist/adapters/claude-desktop.d.ts.map +1 -1
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/cline.d.ts.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/vscode.d.ts.map +1 -1
- package/dist/cli-7edpy4nk.js +514 -0
- package/dist/cli-fsf33sgp.js +105 -0
- package/dist/{cli-q8de7zb0.js → cli-s4gspqdf.js} +4 -1
- package/dist/cli-s4py8ckf.js +187 -0
- package/dist/{cli-e9hdd44n.js → cli-w4m7471h.js} +933 -542
- package/dist/{cli-fh6jh7fs.js → cli-ydr75jw4.js} +934 -543
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +381 -56
- package/dist/config-79j3jpra.js +20 -0
- package/dist/core/add-command.d.ts +7 -0
- package/dist/core/add-command.d.ts.map +1 -0
- package/dist/core/auth-probe.d.ts +1 -0
- package/dist/core/auth-probe.d.ts.map +1 -1
- package/dist/core/config-repair.d.ts +3 -0
- package/dist/core/config-repair.d.ts.map +1 -0
- package/dist/core/config.d.ts +19 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/daemon.d.ts.map +1 -1
- package/dist/core/index.d.ts +7 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +50 -12
- package/dist/core/oauth.d.ts +12 -0
- package/dist/core/oauth.d.ts.map +1 -0
- package/dist/core/paths.d.ts +2 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/registry-client.d.ts +76 -0
- package/dist/core/registry-client.d.ts.map +1 -0
- package/dist/core/registry.d.ts +2 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/secrets.d.ts.map +1 -1
- package/dist/core/spawn-env.d.ts +2 -0
- package/dist/core/spawn-env.d.ts.map +1 -0
- package/dist/core/status.d.ts +6 -2
- package/dist/core/status.d.ts.map +1 -1
- package/dist/core/sync.d.ts.map +1 -1
- package/dist/gateway/server.d.ts +21 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/registry-1s14wd58.js +24 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/version.d.ts +1 -1
- 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":";
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
runOAuthLogin,
|
|
38
33
|
saveSkill,
|
|
39
34
|
secretRefName,
|
|
40
|
-
|
|
35
|
+
selectBestPackage,
|
|
41
36
|
startDaemon,
|
|
42
37
|
stopDaemon,
|
|
43
38
|
syncAllClients,
|
|
44
39
|
toSecretRef
|
|
45
|
-
} from "./cli-
|
|
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-
|
|
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
|
|
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
|
|
1062
|
-
|
|
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
|
|
1137
|
-
const
|
|
1138
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1619
|
-
setServerEnabled(config, name, enabled);
|
|
1620
|
-
|
|
1621
|
-
|
|
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
|
-
|
|
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
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
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
|
-
|
|
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
|
|
1645
|
-
removeServer(config, name, options.force ?? false);
|
|
1646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1657
|
-
|
|
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 =
|
|
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
|
-
|
|
1959
|
+
try {
|
|
1960
|
+
const keytar = __require(path.join(__dirname, "..", "node_modules", "keytar", "lib", "keytar.js"));
|
|
1724
1961
|
try {
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
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
|