@yawlabs/mcph 0.44.0 → 0.46.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 +8 -0
- package/README.md +1 -0
- package/dist/index.js +204 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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.46.0 — 2026-04-18
|
|
6
|
+
|
|
7
|
+
- **`mcph upgrade` — show (or run) the command that bumps `@yawlabs/mcph` to the latest version** — `mcph doctor` has surfaced staleness for a while, but the fix step was left to the user. This subcommand turns that prompt into an action: it detects *how* mcph is installed by inspecting `process.argv[1]` (global npm, npx cache, project-local `node_modules`, or a dev checkout), fetches the latest version from the npm registry (3s timeout, graceful offline fallback), and prints the exact command that moves the current install forward. `--run` spawns the upgrade for the global-npm case (whitelisted to `npm install -g @yawlabs/mcph@latest` — never arbitrary input into a shell), refuses with exit 2 on non-global install methods to avoid surprise writes, and exit 3 if the spawned npm invocation fails. `--json` emits `{ current, latest, stale, method, command }` so CI scripts can branch on staleness without parsing prose. `npx -y` installs are a no-op ("restart the MCP client and it will fetch the new version") — the path detection catches the `_npx` staging directory and says so. Exit codes are wired for scripting: 0 up-to-date or offline, 1 stale without `--run` (copy-paste mode), 2 usage/refusal, 3 `--run` failed. Completes the doctor→fix handoff that's been missing since the upgrade-check section landed.
|
|
8
|
+
|
|
9
|
+
## 0.45.0 — 2026-04-18
|
|
10
|
+
|
|
11
|
+
- **Clearer 401/403 errors with token fingerprint + actionable fix link** — When the backend rejects a token (`HTTP 401` revoked/malformed, `HTTP 403` accepted but scope-denied), `fetchConfig` now throws an error that names the offending token by its fingerprint (e.g., `mcp_pat_…abcd`), explains what state the token is in, and points directly at the tokens page with a concrete re-install command. Prior wording was "Invalid MCPH_TOKEN — check your token at mcp.hosting" and "Access denied — your token may have expired" — both too vague to action without pinging support. New wording is structured as three lines: cause, fix URL, and the `mcph install … --token mcp_pat_...` re-install command. Messages surface verbatim through `mcph servers`, the top-level `mcph` runtime, and anywhere else `fetchConfig` is awaited, so every user-facing rejection reads the same way.
|
|
12
|
+
|
|
5
13
|
## 0.44.0 — 2026-04-18
|
|
6
14
|
|
|
7
15
|
- **`mcph install --list` + `mcph install --all`** — Two new modes on the install subcommand. `--list` is read-only: it enumerates every client/scope combo for the current OS and shows whether an `mcp.hosting` entry is already wired up, plus a path-per-row and a one-line summary (`N/M client scopes have mcp.hosting configured on linux`). No token, no network, no writes — just a diagnostic view that mirrors the `doctor` CLIENTS section but without the rest of doctor's noise. `--all` walks `INSTALL_TARGETS`, picks the default scope per client (user where supported, else the first non-project-dir scope, else skipped unless `--project-dir` is passed), and calls `runInstall` in a loop — so `--dry-run`, `--force`, `--skip`, and `--token` all propagate as expected. Status is aggregated into a single summary line, and the process exit code is non-zero if any sub-install failed so CI can still gate on one-shot onboarding. Works around the main drop-off during setup ("which client am I supposed to pick?") by offering both the answer (`--list`) and the sledgehammer (`--all`) from the same subcommand.
|
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ mcph servers [<namespace-filter>] [--json] # list servers; optional substring
|
|
|
92
92
|
mcph bundles [list|match] [--json] # browse curated multi-server bundles (PR review, DevOps incident, etc.)
|
|
93
93
|
mcph reset-learning # clear cross-session learning history (~/.mcph/state.json)
|
|
94
94
|
mcph completion <bash|zsh|fish|powershell> # print shell completion script
|
|
95
|
+
mcph upgrade [--run] [--json] # show (or execute) the command that bumps @yawlabs/mcph
|
|
95
96
|
mcph compliance <target> [--publish] # run the compliance suite against an MCP server
|
|
96
97
|
mcph --version # print version
|
|
97
98
|
```
|
package/dist/index.js
CHANGED
|
@@ -464,12 +464,22 @@ async function fetchConfig(apiUrl6, token6, currentVersion) {
|
|
|
464
464
|
if (res.statusCode === 401) {
|
|
465
465
|
await res.body.text().catch(() => {
|
|
466
466
|
});
|
|
467
|
-
throw new ConfigError(
|
|
467
|
+
throw new ConfigError(
|
|
468
|
+
`Token rejected (HTTP 401) \u2014 the token ${tokenFingerprint(token6)} is invalid or revoked.
|
|
469
|
+
Generate a new token at https://mcp.hosting/dashboard/settings/tokens,
|
|
470
|
+
then re-run \`mcph install <client> --token mcp_pat_...\` or set MCPH_TOKEN.`,
|
|
471
|
+
true
|
|
472
|
+
);
|
|
468
473
|
}
|
|
469
474
|
if (res.statusCode === 403) {
|
|
470
475
|
await res.body.text().catch(() => {
|
|
471
476
|
});
|
|
472
|
-
throw new ConfigError(
|
|
477
|
+
throw new ConfigError(
|
|
478
|
+
`Access denied (HTTP 403) \u2014 the token ${tokenFingerprint(token6)} was accepted but lacks permission to read this account's servers.
|
|
479
|
+
The account may be suspended or the token scope reduced \u2014 check
|
|
480
|
+
https://mcp.hosting/dashboard/settings/tokens, or reach support@mcp.hosting.`,
|
|
481
|
+
true
|
|
482
|
+
);
|
|
473
483
|
}
|
|
474
484
|
if (res.statusCode !== 200) {
|
|
475
485
|
const body = await res.body.text().catch(() => "");
|
|
@@ -682,6 +692,7 @@ var SUBCOMMAND_SPEC = [
|
|
|
682
692
|
{ name: "compliance", flags: ["--publish", "--help"] },
|
|
683
693
|
{ name: "reset-learning", flags: ["--help"] },
|
|
684
694
|
{ name: "completion", positional: ["bash", "zsh", "fish", "powershell"], flags: ["--help"] },
|
|
695
|
+
{ name: "upgrade", flags: ["--run", "--json", "--help"] },
|
|
685
696
|
{ name: "help", flags: [] }
|
|
686
697
|
];
|
|
687
698
|
function parseCompletionArgs(argv) {
|
|
@@ -781,6 +792,7 @@ function renderZsh() {
|
|
|
781
792
|
compliance: "Run the compliance suite against a server",
|
|
782
793
|
"reset-learning": "Clear cross-session learning history",
|
|
783
794
|
completion: "Print a shell completion script",
|
|
795
|
+
upgrade: "Upgrade @yawlabs/mcph to the latest version",
|
|
784
796
|
help: "Show usage"
|
|
785
797
|
};
|
|
786
798
|
const subcommandList = SUBCOMMAND_SPEC.map((s) => ` '${s.name}:${subcommandDescriptions[s.name] ?? ""}'`).join(
|
|
@@ -1446,7 +1458,7 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
1446
1458
|
}
|
|
1447
1459
|
|
|
1448
1460
|
// src/doctor-cmd.ts
|
|
1449
|
-
var VERSION = true ? "0.
|
|
1461
|
+
var VERSION = true ? "0.46.0" : "dev";
|
|
1450
1462
|
async function runDoctor(opts = {}) {
|
|
1451
1463
|
if (opts.json) return runDoctorJson(opts);
|
|
1452
1464
|
const lines = [];
|
|
@@ -4734,7 +4746,7 @@ function categorizeSpawnError(err) {
|
|
|
4734
4746
|
}
|
|
4735
4747
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
4736
4748
|
const client = new Client(
|
|
4737
|
-
{ name: "mcph", version: true ? "0.
|
|
4749
|
+
{ name: "mcph", version: true ? "0.46.0" : "dev" },
|
|
4738
4750
|
{ capabilities: {} }
|
|
4739
4751
|
);
|
|
4740
4752
|
let transport;
|
|
@@ -5215,7 +5227,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
5215
5227
|
this.apiUrl = apiUrl6;
|
|
5216
5228
|
this.token = token6;
|
|
5217
5229
|
this.server = new Server(
|
|
5218
|
-
{ name: "mcph", version: true ? "0.
|
|
5230
|
+
{ name: "mcph", version: true ? "0.46.0" : "dev" },
|
|
5219
5231
|
{
|
|
5220
5232
|
capabilities: {
|
|
5221
5233
|
tools: { listChanged: true },
|
|
@@ -7388,6 +7400,182 @@ function truncateVersion(v) {
|
|
|
7388
7400
|
return v.length > 8 ? v.slice(0, 8) : v;
|
|
7389
7401
|
}
|
|
7390
7402
|
|
|
7403
|
+
// src/upgrade-cmd.ts
|
|
7404
|
+
import { spawn as spawn4 } from "child_process";
|
|
7405
|
+
var UPGRADE_USAGE = `Usage: mcph upgrade [--run] [--json]
|
|
7406
|
+
|
|
7407
|
+
Show (or execute) the command to upgrade @yawlabs/mcph to the latest version.
|
|
7408
|
+
|
|
7409
|
+
--run If this install is global (npm install -g), spawn the upgrade
|
|
7410
|
+
command. No-op for npx installs \u2014 they always fetch the latest.
|
|
7411
|
+
--json Emit a machine-readable snapshot ({ current, latest, stale,
|
|
7412
|
+
method, command }) instead of prose.`;
|
|
7413
|
+
function parseUpgradeArgs(argv) {
|
|
7414
|
+
const opts = {};
|
|
7415
|
+
for (const a of argv) {
|
|
7416
|
+
if (a === "--run") opts.run = true;
|
|
7417
|
+
else if (a === "--json") opts.json = true;
|
|
7418
|
+
else if (a === "--help" || a === "-h") return { ok: false, error: UPGRADE_USAGE };
|
|
7419
|
+
else return { ok: false, error: `mcph upgrade: unknown argument "${a}"
|
|
7420
|
+
|
|
7421
|
+
${UPGRADE_USAGE}` };
|
|
7422
|
+
}
|
|
7423
|
+
return { ok: true, options: opts };
|
|
7424
|
+
}
|
|
7425
|
+
function detectInstallMethod(argvPath) {
|
|
7426
|
+
if (!argvPath) return "unknown";
|
|
7427
|
+
const normalized = argvPath.replace(/\\/g, "/");
|
|
7428
|
+
if (/\/_npx\//.test(normalized)) return "npx";
|
|
7429
|
+
if (/\/npm\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
|
|
7430
|
+
if (/\/lib\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
|
|
7431
|
+
if (/\/AppData\/Roaming\/npm\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
|
|
7432
|
+
if (/\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "local-node-modules";
|
|
7433
|
+
if (/\/mcph\/(dist|src)\//.test(normalized)) return "dev-checkout";
|
|
7434
|
+
return "unknown";
|
|
7435
|
+
}
|
|
7436
|
+
function buildUpgradePlan(input) {
|
|
7437
|
+
const { current, latest, method } = input;
|
|
7438
|
+
const stale = latest !== null && current !== "dev" && compareSemverLocal(current, latest) < 0;
|
|
7439
|
+
let command;
|
|
7440
|
+
switch (method) {
|
|
7441
|
+
case "global-npm":
|
|
7442
|
+
command = "npm install -g @yawlabs/mcph@latest";
|
|
7443
|
+
break;
|
|
7444
|
+
case "npx":
|
|
7445
|
+
command = null;
|
|
7446
|
+
break;
|
|
7447
|
+
case "local-node-modules":
|
|
7448
|
+
command = "npm install @yawlabs/mcph@latest";
|
|
7449
|
+
break;
|
|
7450
|
+
case "dev-checkout":
|
|
7451
|
+
command = "git pull && npm run build";
|
|
7452
|
+
break;
|
|
7453
|
+
default:
|
|
7454
|
+
command = "npm install -g @yawlabs/mcph@latest";
|
|
7455
|
+
break;
|
|
7456
|
+
}
|
|
7457
|
+
return { current, latest, stale, method, command };
|
|
7458
|
+
}
|
|
7459
|
+
function compareSemverLocal(a, b) {
|
|
7460
|
+
const parse = (s) => {
|
|
7461
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(s);
|
|
7462
|
+
if (!m) return null;
|
|
7463
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
7464
|
+
};
|
|
7465
|
+
const pa = parse(a);
|
|
7466
|
+
const pb = parse(b);
|
|
7467
|
+
if (!pa || !pb) return 0;
|
|
7468
|
+
for (let i = 0; i < 3; i++) {
|
|
7469
|
+
if (pa[i] < pb[i]) return -1;
|
|
7470
|
+
if (pa[i] > pb[i]) return 1;
|
|
7471
|
+
}
|
|
7472
|
+
return 0;
|
|
7473
|
+
}
|
|
7474
|
+
async function defaultFetchLatest() {
|
|
7475
|
+
const ac = new AbortController();
|
|
7476
|
+
const timer = setTimeout(() => ac.abort(), 3e3);
|
|
7477
|
+
try {
|
|
7478
|
+
const res = await fetch("https://registry.npmjs.org/@yawlabs/mcph/latest", {
|
|
7479
|
+
signal: ac.signal,
|
|
7480
|
+
headers: { accept: "application/json" }
|
|
7481
|
+
});
|
|
7482
|
+
if (!res.ok) return null;
|
|
7483
|
+
const body = await res.json();
|
|
7484
|
+
return typeof body.version === "string" ? body.version : null;
|
|
7485
|
+
} catch {
|
|
7486
|
+
return null;
|
|
7487
|
+
} finally {
|
|
7488
|
+
clearTimeout(timer);
|
|
7489
|
+
}
|
|
7490
|
+
}
|
|
7491
|
+
async function defaultSpawn(cmd, args) {
|
|
7492
|
+
return new Promise((resolve4) => {
|
|
7493
|
+
const child = spawn4(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
|
|
7494
|
+
child.on("close", (code) => resolve4(typeof code === "number" ? code : 1));
|
|
7495
|
+
child.on("error", () => resolve4(1));
|
|
7496
|
+
});
|
|
7497
|
+
}
|
|
7498
|
+
async function runUpgrade(opts = {}) {
|
|
7499
|
+
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
7500
|
+
const writeErr = opts.err ?? ((s) => process.stderr.write(s));
|
|
7501
|
+
const lines = [];
|
|
7502
|
+
const print = (s = "") => {
|
|
7503
|
+
lines.push(s);
|
|
7504
|
+
write(`${s}
|
|
7505
|
+
`);
|
|
7506
|
+
};
|
|
7507
|
+
const printErr = (s) => {
|
|
7508
|
+
lines.push(s);
|
|
7509
|
+
writeErr(`${s}
|
|
7510
|
+
`);
|
|
7511
|
+
};
|
|
7512
|
+
const fetcher = opts.fetchLatest ?? defaultFetchLatest;
|
|
7513
|
+
const current = opts.currentVersion ?? readCurrentVersion();
|
|
7514
|
+
const argvPath = opts.argvPath ?? process.argv[1];
|
|
7515
|
+
const method = detectInstallMethod(argvPath);
|
|
7516
|
+
let latest;
|
|
7517
|
+
try {
|
|
7518
|
+
latest = await fetcher();
|
|
7519
|
+
} catch {
|
|
7520
|
+
latest = null;
|
|
7521
|
+
}
|
|
7522
|
+
const plan = buildUpgradePlan({ current, latest, method });
|
|
7523
|
+
if (opts.json) {
|
|
7524
|
+
print(JSON.stringify(plan, null, 2));
|
|
7525
|
+
return { exitCode: plan.stale && !opts.run ? 1 : 0, lines };
|
|
7526
|
+
}
|
|
7527
|
+
if (latest === null) {
|
|
7528
|
+
print("mcph upgrade: couldn't reach the npm registry (offline? firewall?).");
|
|
7529
|
+
if (plan.command) {
|
|
7530
|
+
print(`When you're back online, run:
|
|
7531
|
+
${plan.command}`);
|
|
7532
|
+
} else {
|
|
7533
|
+
print("Your install uses `npx -y` \u2014 just restart the MCP client when you're back online.");
|
|
7534
|
+
}
|
|
7535
|
+
return { exitCode: 0, lines };
|
|
7536
|
+
}
|
|
7537
|
+
print(`Current: ${current}`);
|
|
7538
|
+
print(`Latest: ${latest}`);
|
|
7539
|
+
print(`Install: ${method}`);
|
|
7540
|
+
if (!plan.stale) {
|
|
7541
|
+
print("");
|
|
7542
|
+
print("\u2713 You're on the latest version \u2014 nothing to do.");
|
|
7543
|
+
return { exitCode: 0, lines };
|
|
7544
|
+
}
|
|
7545
|
+
print("");
|
|
7546
|
+
if (method === "npx") {
|
|
7547
|
+
print("Your install uses `npx -y` \u2014 restart the MCP client and it will fetch the new version.");
|
|
7548
|
+
return { exitCode: 0, lines };
|
|
7549
|
+
}
|
|
7550
|
+
if (!plan.command) {
|
|
7551
|
+
print("No upgrade command available for this install method.");
|
|
7552
|
+
return { exitCode: 0, lines };
|
|
7553
|
+
}
|
|
7554
|
+
if (!opts.run) {
|
|
7555
|
+
print(`Run:
|
|
7556
|
+
${plan.command}`);
|
|
7557
|
+
return { exitCode: 1, lines };
|
|
7558
|
+
}
|
|
7559
|
+
if (method !== "global-npm") {
|
|
7560
|
+
printErr(`mcph upgrade --run: refusing to auto-run upgrade for method "${method}". Run manually: ${plan.command}`);
|
|
7561
|
+
return { exitCode: 2, lines };
|
|
7562
|
+
}
|
|
7563
|
+
const runner = opts.spawnImpl ?? defaultSpawn;
|
|
7564
|
+
print(`Running: ${plan.command}`);
|
|
7565
|
+
const code = await runner("npm", ["install", "-g", "@yawlabs/mcph@latest"]);
|
|
7566
|
+
if (code === 0) {
|
|
7567
|
+
print("");
|
|
7568
|
+
print(`\u2713 Upgraded @yawlabs/mcph to ${latest}.`);
|
|
7569
|
+
return { exitCode: 0, lines };
|
|
7570
|
+
}
|
|
7571
|
+
printErr(`mcph upgrade: npm exited ${code}. Try running the command manually.`);
|
|
7572
|
+
return { exitCode: 3, lines };
|
|
7573
|
+
}
|
|
7574
|
+
function readCurrentVersion() {
|
|
7575
|
+
const v = globalThis.__VERSION__;
|
|
7576
|
+
return typeof v === "string" ? v : "dev";
|
|
7577
|
+
}
|
|
7578
|
+
|
|
7391
7579
|
// src/index.ts
|
|
7392
7580
|
var KNOWN_SUBCOMMANDS = [
|
|
7393
7581
|
"compliance",
|
|
@@ -7397,6 +7585,7 @@ var KNOWN_SUBCOMMANDS = [
|
|
|
7397
7585
|
"servers",
|
|
7398
7586
|
"bundles",
|
|
7399
7587
|
"completion",
|
|
7588
|
+
"upgrade",
|
|
7400
7589
|
"help",
|
|
7401
7590
|
"--help",
|
|
7402
7591
|
"-h",
|
|
@@ -7456,6 +7645,14 @@ if (subcommand === "compliance") {
|
|
|
7456
7645
|
process.exit(2);
|
|
7457
7646
|
}
|
|
7458
7647
|
runCompletion(parsed.options).then((r) => process.exit(r.exitCode));
|
|
7648
|
+
} else if (subcommand === "upgrade") {
|
|
7649
|
+
const parsed = parseUpgradeArgs(process.argv.slice(3));
|
|
7650
|
+
if (!parsed.ok) {
|
|
7651
|
+
process.stderr.write(`${parsed.error}
|
|
7652
|
+
`);
|
|
7653
|
+
process.exit(2);
|
|
7654
|
+
}
|
|
7655
|
+
runUpgrade(parsed.options).then((r) => process.exit(r.exitCode));
|
|
7459
7656
|
} else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
7460
7657
|
const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
|
|
7461
7658
|
process.stdout.write(
|
|
@@ -7471,6 +7668,7 @@ if (subcommand === "compliance") {
|
|
|
7471
7668
|
mcph compliance <target> [flags] Run the compliance suite against an MCP server
|
|
7472
7669
|
mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
|
|
7473
7670
|
mcph completion <shell> Print a shell completion script (bash|zsh|fish|powershell)
|
|
7671
|
+
mcph upgrade [--run] [--json] Show (or run) the upgrade command for @yawlabs/mcph
|
|
7474
7672
|
mcph --version Print version
|
|
7475
7673
|
|
|
7476
7674
|
Install:
|
|
@@ -7491,7 +7689,7 @@ ${installBlock}
|
|
|
7491
7689
|
);
|
|
7492
7690
|
process.exit(0);
|
|
7493
7691
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
7494
|
-
process.stdout.write(`mcph ${true ? "0.
|
|
7692
|
+
process.stdout.write(`mcph ${true ? "0.46.0" : "dev"}
|
|
7495
7693
|
`);
|
|
7496
7694
|
process.exit(0);
|
|
7497
7695
|
} else if (subcommand && !subcommand.startsWith("-")) {
|
package/package.json
CHANGED