@yawlabs/mcph 0.40.0 → 0.41.0
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/CHANGELOG.md +4 -0
- package/dist/index.js +116 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@yawlabs/mcph` are documented here. This project uses [semantic versioning](https://semver.org) and a CI-gated release flow: pushing a `vX.Y.Z` tag triggers `.github/workflows/release.yml`, which publishes to npm.
|
|
4
4
|
|
|
5
|
+
## 0.41.0 — 2026-04-18
|
|
6
|
+
|
|
7
|
+
- **`mcph doctor --json` — machine-readable diagnostic output** — Doctor already tracks a lot of state (config files, token source, env overrides, persisted learning, installed clients, shell-history shadow hits, upgrade availability, diagnosis summary) and the text output optimises for pasting into a support ticket. `--json` emits the same data as a single structured blob so dashboards, CI scripts, and support tooling can pick fields with `jq` instead of parsing the text layout. Token is fingerprinted the same way in both modes (never raw). Section data is 1:1 with the text renderer: config (token/apiBase/loadedFiles/warnings), env overrides (null when unset), state (path/savedAt/entries; `disabled: true` when `MCPH_DISABLE_PERSISTENCE` is set), reliability (same `selectFlakyNamespaces` rollup that `mcp_connect_health` and the text RELIABILITY section use), clients probe results, shell shadow hits, upgrade info, and the exit-code diagnosis. Completes the `--json` pattern across `servers`, `bundles`, and now `doctor` — every CLI that reads state has a pipeline mode.
|
|
8
|
+
|
|
5
9
|
## 0.40.0 — 2026-04-18
|
|
6
10
|
|
|
7
11
|
- **`mcph bundles` CLI subcommand** — CLI counterpart to the `mcp_connect_bundles` meta-tool (v0.28.0). Two actions mirror the meta-tool's `action` parameter: `list` prints every curated bundle grouped by category with activate hints (static, no network, no token needed — good for browsing or sharing in onboarding docs), and `match` partitions the curated set against the user's enabled servers from the backend into ready-to-activate vs partially-installed, so a human can see in the terminal what the LLM-facing tool would suggest. The LLM tool has always been primary surface, but "what bundles exist?" is a frequent enough support question that surfacing them in the CLI earns its keep. Match only counts `isActive: true` servers — disabled ones don't auto-activate, so they shouldn't count toward "ready" — matching the LLM tool's filter so both surfaces agree. Partial bundles sort fewest-missing first to match the discover inline hint ranking. `--json` emits machine-readable output (`{bundles}` for list, `{installed, ready, partial}` for match). Exit codes: 0 success, 1 match needs a token and none resolved, 2 match couldn't reach the backend.
|
package/dist/index.js
CHANGED
|
@@ -1219,8 +1219,9 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
1219
1219
|
}
|
|
1220
1220
|
|
|
1221
1221
|
// src/doctor-cmd.ts
|
|
1222
|
-
var VERSION = true ? "0.
|
|
1222
|
+
var VERSION = true ? "0.41.0" : "dev";
|
|
1223
1223
|
async function runDoctor(opts = {}) {
|
|
1224
|
+
if (opts.json) return runDoctorJson(opts);
|
|
1224
1225
|
const lines = [];
|
|
1225
1226
|
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
1226
1227
|
const print = (s = "") => {
|
|
@@ -1309,6 +1310,101 @@ async function runDoctor(opts = {}) {
|
|
|
1309
1310
|
}
|
|
1310
1311
|
return { exitCode, lines, snapshot: { version: VERSION, config, clients } };
|
|
1311
1312
|
}
|
|
1313
|
+
async function runDoctorJson(opts) {
|
|
1314
|
+
const lines = [];
|
|
1315
|
+
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
1316
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
1317
|
+
const home = opts.home ?? homedir4();
|
|
1318
|
+
const os = opts.os ?? CURRENT_OS;
|
|
1319
|
+
const env = opts.env ?? process.env;
|
|
1320
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1321
|
+
const config = await loadMcphConfig({ cwd, home, env });
|
|
1322
|
+
const clients = probeClients({ home, os, cwd });
|
|
1323
|
+
const envVarNames = [
|
|
1324
|
+
"MCPH_POLL_INTERVAL",
|
|
1325
|
+
"MCPH_SERVER_CAP",
|
|
1326
|
+
"MCPH_MIN_COMPLIANCE",
|
|
1327
|
+
"MCPH_AUTO_LOAD",
|
|
1328
|
+
"MCPH_PRUNE_RESPONSES"
|
|
1329
|
+
];
|
|
1330
|
+
const envOverrides = {};
|
|
1331
|
+
for (const name of envVarNames) {
|
|
1332
|
+
const raw = env[name];
|
|
1333
|
+
envOverrides[name] = raw === void 0 || raw === "" ? null : raw;
|
|
1334
|
+
}
|
|
1335
|
+
const persistRaw = env.MCPH_DISABLE_PERSISTENCE;
|
|
1336
|
+
const persistDisabled = persistRaw !== void 0 && persistRaw !== "" && (persistRaw === "1" || persistRaw.toLowerCase() === "true");
|
|
1337
|
+
const state = persistDisabled ? { disabled: true, path: null, savedAt: null, learningEntries: null, packHistoryEntries: null } : await (async () => {
|
|
1338
|
+
const filePath = join4(userConfigDir(home), STATE_FILENAME);
|
|
1339
|
+
const persisted = await loadState(filePath);
|
|
1340
|
+
const fresh = persisted.savedAt === 0;
|
|
1341
|
+
return {
|
|
1342
|
+
disabled: false,
|
|
1343
|
+
path: filePath,
|
|
1344
|
+
savedAt: fresh ? null : new Date(persisted.savedAt).toISOString(),
|
|
1345
|
+
learningEntries: fresh ? 0 : Object.keys(persisted.learning).length,
|
|
1346
|
+
packHistoryEntries: fresh ? 0 : persisted.packHistory.length
|
|
1347
|
+
};
|
|
1348
|
+
})();
|
|
1349
|
+
const reliability = [];
|
|
1350
|
+
if (!persistDisabled) {
|
|
1351
|
+
const filePath = join4(userConfigDir(home), STATE_FILENAME);
|
|
1352
|
+
const persisted = await loadState(filePath);
|
|
1353
|
+
if (persisted.savedAt !== 0) {
|
|
1354
|
+
const entries = Object.entries(persisted.learning).map(([namespace, usage]) => ({ namespace, usage }));
|
|
1355
|
+
for (const { namespace, usage } of selectFlakyNamespaces(entries, 5)) {
|
|
1356
|
+
reliability.push({
|
|
1357
|
+
namespace,
|
|
1358
|
+
dispatched: usage.dispatched,
|
|
1359
|
+
succeeded: usage.succeeded,
|
|
1360
|
+
successRate: usage.succeeded / usage.dispatched,
|
|
1361
|
+
lastUsedAt: new Date(usage.lastUsedAt).toISOString()
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const shellShadows = scanShellHistoryForShadows({ home, env });
|
|
1367
|
+
const skipCheck = opts.skipRegistryCheck === true || Boolean(process.env.VITEST);
|
|
1368
|
+
const latest = skipCheck ? null : await fetchLatestVersion(opts.registryFetch);
|
|
1369
|
+
const stale = latest !== null && VERSION !== "dev" && compareSemver(VERSION, latest) < 0;
|
|
1370
|
+
let exitCode = 0;
|
|
1371
|
+
let summary;
|
|
1372
|
+
if (config.token === null) {
|
|
1373
|
+
exitCode = 1;
|
|
1374
|
+
summary = "No token resolved \u2014 mcph cannot start.";
|
|
1375
|
+
} else if (config.warnings.length > 0) {
|
|
1376
|
+
exitCode = 2;
|
|
1377
|
+
summary = "Token present, but warnings need attention.";
|
|
1378
|
+
} else {
|
|
1379
|
+
summary = stale ? "Healthy, but an upgrade is available." : "All good. mcph should start cleanly.";
|
|
1380
|
+
}
|
|
1381
|
+
const snapshotJson = {
|
|
1382
|
+
timestamp,
|
|
1383
|
+
version: VERSION,
|
|
1384
|
+
platform: os,
|
|
1385
|
+
token: { fingerprint: tokenFingerprint(config.token), source: config.tokenSource },
|
|
1386
|
+
apiBase: { value: config.apiBase, source: config.apiBaseSource },
|
|
1387
|
+
loadedFiles: config.loadedFiles.map((f) => ({
|
|
1388
|
+
scope: f.scope,
|
|
1389
|
+
path: f.path,
|
|
1390
|
+
...f.version !== void 0 ? { schemaVersion: f.version } : {},
|
|
1391
|
+
schemaAhead: f.version !== void 0 && f.version > CURRENT_SCHEMA_VERSION
|
|
1392
|
+
})),
|
|
1393
|
+
warnings: config.warnings,
|
|
1394
|
+
env: envOverrides,
|
|
1395
|
+
state,
|
|
1396
|
+
reliability,
|
|
1397
|
+
clients,
|
|
1398
|
+
shellShadows,
|
|
1399
|
+
upgrade: { current: VERSION, latest, stale },
|
|
1400
|
+
diagnosis: { exitCode, summary }
|
|
1401
|
+
};
|
|
1402
|
+
const blob = JSON.stringify(snapshotJson, null, 2);
|
|
1403
|
+
lines.push(blob);
|
|
1404
|
+
write(`${blob}
|
|
1405
|
+
`);
|
|
1406
|
+
return { exitCode, lines, snapshot: { version: VERSION, config, clients } };
|
|
1407
|
+
}
|
|
1312
1408
|
function renderEnvSection(opts) {
|
|
1313
1409
|
const { env, print } = opts;
|
|
1314
1410
|
const vars = [
|
|
@@ -4207,7 +4303,7 @@ function categorizeSpawnError(err) {
|
|
|
4207
4303
|
}
|
|
4208
4304
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
4209
4305
|
const client = new Client(
|
|
4210
|
-
{ name: "mcph", version: true ? "0.
|
|
4306
|
+
{ name: "mcph", version: true ? "0.41.0" : "dev" },
|
|
4211
4307
|
{ capabilities: {} }
|
|
4212
4308
|
);
|
|
4213
4309
|
let transport;
|
|
@@ -4688,7 +4784,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
4688
4784
|
this.apiUrl = apiUrl6;
|
|
4689
4785
|
this.token = token6;
|
|
4690
4786
|
this.server = new Server(
|
|
4691
|
-
{ name: "mcph", version: true ? "0.
|
|
4787
|
+
{ name: "mcph", version: true ? "0.41.0" : "dev" },
|
|
4692
4788
|
{
|
|
4693
4789
|
capabilities: {
|
|
4694
4790
|
tools: { listChanged: true },
|
|
@@ -6870,7 +6966,21 @@ if (subcommand === "compliance") {
|
|
|
6870
6966
|
}
|
|
6871
6967
|
runInstall(parsed.options).then((r) => process.exit(r.exitCode));
|
|
6872
6968
|
} else if (subcommand === "doctor") {
|
|
6873
|
-
|
|
6969
|
+
const doctorArgs = process.argv.slice(3);
|
|
6970
|
+
const doctorJson = doctorArgs.includes("--json");
|
|
6971
|
+
const doctorUnknown = doctorArgs.find((a) => a !== "--json" && a !== "--help" && a !== "-h");
|
|
6972
|
+
if (doctorArgs.includes("--help") || doctorArgs.includes("-h")) {
|
|
6973
|
+
process.stdout.write(
|
|
6974
|
+
"Usage: mcph doctor [--json]\n\n Print a diagnostic of your mcph setup.\n\n --json Emit machine-readable JSON instead of text.\n"
|
|
6975
|
+
);
|
|
6976
|
+
process.exit(0);
|
|
6977
|
+
}
|
|
6978
|
+
if (doctorUnknown) {
|
|
6979
|
+
process.stderr.write(`mcph doctor: unknown argument "${doctorUnknown}"
|
|
6980
|
+
`);
|
|
6981
|
+
process.exit(2);
|
|
6982
|
+
}
|
|
6983
|
+
runDoctor({ json: doctorJson }).then((r) => process.exit(r.exitCode));
|
|
6874
6984
|
} else if (subcommand === "reset-learning") {
|
|
6875
6985
|
runResetLearning().then((r) => process.exit(r.exitCode));
|
|
6876
6986
|
} else if (subcommand === "servers") {
|
|
@@ -6898,7 +7008,7 @@ if (subcommand === "compliance") {
|
|
|
6898
7008
|
Usage:
|
|
6899
7009
|
mcph Run as MCP server (requires a token)
|
|
6900
7010
|
mcph install <client> [flags] Auto-edit an MCP client's config to launch mcph
|
|
6901
|
-
mcph doctor
|
|
7011
|
+
mcph doctor [--json] Print loaded config + detected clients (support diagnostic)
|
|
6902
7012
|
mcph servers [--json] List servers configured in your mcp.hosting dashboard
|
|
6903
7013
|
mcph bundles [list|match] Browse curated multi-server bundles
|
|
6904
7014
|
mcph compliance <target> [flags] Run the compliance suite against an MCP server
|
|
@@ -6923,7 +7033,7 @@ ${installBlock}
|
|
|
6923
7033
|
);
|
|
6924
7034
|
process.exit(0);
|
|
6925
7035
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
6926
|
-
process.stdout.write(`mcph ${true ? "0.
|
|
7036
|
+
process.stdout.write(`mcph ${true ? "0.41.0" : "dev"}
|
|
6927
7037
|
`);
|
|
6928
7038
|
process.exit(0);
|
|
6929
7039
|
} else if (subcommand && !subcommand.startsWith("-")) {
|
package/package.json
CHANGED