@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.
- package/dist/adapters/claude-desktop.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/kiro.d.ts +2 -1
- package/dist/adapters/kiro.d.ts.map +1 -1
- package/dist/adapters/qwen.d.ts +2 -1
- package/dist/adapters/qwen.d.ts.map +1 -1
- package/dist/adapters/utils/index.d.ts.map +1 -1
- package/dist/adapters/vscode.d.ts.map +1 -1
- package/dist/cli-7bsf6akp.js +202 -0
- package/dist/{cli-33pamqwn.js → cli-f16kphtq.js} +591 -456
- package/dist/cli-fsf33sgp.js +105 -0
- package/dist/{cli-ezvj44tr.js → cli-htm5cf82.js} +590 -455
- package/dist/{cli-q8de7zb0.js → cli-s4gspqdf.js} +4 -1
- package/dist/cli-s4py8ckf.js +187 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +506 -85
- package/dist/config-kayej21q.js +20 -0
- package/dist/core/auth-probe.d.ts.map +1 -1
- 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 +5 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +44 -12
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/proxy.d.ts +2 -0
- package/dist/core/proxy.d.ts.map +1 -0
- 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/skills.d.ts +6 -0
- package/dist/core/skills.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 +17 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/registry-1s14wd58.js +24 -0
- package/dist/types.d.ts +17 -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.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
|
-
|
|
14
|
+
getGatewayUrl,
|
|
15
|
+
getSkill,
|
|
15
16
|
getStagedCliPath,
|
|
16
17
|
getStagedUpdate,
|
|
17
18
|
listAuthBindings,
|
|
18
|
-
|
|
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
|
-
|
|
32
|
+
saveSkill,
|
|
33
33
|
secretRefName,
|
|
34
|
-
|
|
34
|
+
selectBestPackage,
|
|
35
35
|
startDaemon,
|
|
36
36
|
stopDaemon,
|
|
37
37
|
syncAllClients,
|
|
38
38
|
toSecretRef
|
|
39
|
-
} from "./cli-
|
|
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-
|
|
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(
|
|
975
|
-
const
|
|
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
|
|
985
|
-
|
|
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
|
|
1060
|
-
const
|
|
1061
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
1542
|
-
setServerEnabled(config, name, enabled);
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
-
|
|
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
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
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
|
-
|
|
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
|
|
1568
|
-
removeServer(config, name, options.force ?? false);
|
|
1569
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1580
|
-
|
|
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 =
|
|
1918
|
+
const config = loadMergedConfig();
|
|
1607
1919
|
const targetClients = parseClientList([...clients ?? [], ...options.client ?? []]);
|
|
1608
1920
|
const secrets = new SecretsManager;
|
|
1609
|
-
await ensureDaemonIfEnabled(
|
|
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
|
-
|
|
1958
|
+
try {
|
|
1959
|
+
const keytar = __require(path.join(__dirname, "..", "node_modules", "keytar", "lib", "keytar.js"));
|
|
1647
1960
|
try {
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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
|
|
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
|
-
|
|
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
|
`);
|