@yawlabs/mcph 0.45.0 → 0.46.1

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 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.1 — 2026-04-18
6
+
7
+ - **Fix `mcph upgrade` reporting `Current: dev` in shipped bundles** — The v0.46.0 `readCurrentVersion()` used `(globalThis as ...).__VERSION__`, but tsup's `define` only substitutes bare identifier references, not property accesses — so the compiled bundle fell through to the "dev" fallback regardless of what version was installed. Switched to the same `declare const __VERSION__ / typeof __VERSION__ !== "undefined"` pattern used in `index.ts`, `doctor-cmd.ts`, `server.ts`, and `upstream.ts`. Smoke-tested via `npx @yawlabs/mcph@latest upgrade`: now reports the actual installed version.
8
+
9
+ ## 0.46.0 — 2026-04-18
10
+
11
+ - **`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.
12
+
5
13
  ## 0.45.0 — 2026-04-18
6
14
 
7
15
  - **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.
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
@@ -692,6 +692,7 @@ var SUBCOMMAND_SPEC = [
692
692
  { name: "compliance", flags: ["--publish", "--help"] },
693
693
  { name: "reset-learning", flags: ["--help"] },
694
694
  { name: "completion", positional: ["bash", "zsh", "fish", "powershell"], flags: ["--help"] },
695
+ { name: "upgrade", flags: ["--run", "--json", "--help"] },
695
696
  { name: "help", flags: [] }
696
697
  ];
697
698
  function parseCompletionArgs(argv) {
@@ -791,6 +792,7 @@ function renderZsh() {
791
792
  compliance: "Run the compliance suite against a server",
792
793
  "reset-learning": "Clear cross-session learning history",
793
794
  completion: "Print a shell completion script",
795
+ upgrade: "Upgrade @yawlabs/mcph to the latest version",
794
796
  help: "Show usage"
795
797
  };
796
798
  const subcommandList = SUBCOMMAND_SPEC.map((s) => ` '${s.name}:${subcommandDescriptions[s.name] ?? ""}'`).join(
@@ -1456,7 +1458,7 @@ function selectFlakyNamespaces(entries, limit) {
1456
1458
  }
1457
1459
 
1458
1460
  // src/doctor-cmd.ts
1459
- var VERSION = true ? "0.45.0" : "dev";
1461
+ var VERSION = true ? "0.46.1" : "dev";
1460
1462
  async function runDoctor(opts = {}) {
1461
1463
  if (opts.json) return runDoctorJson(opts);
1462
1464
  const lines = [];
@@ -4744,7 +4746,7 @@ function categorizeSpawnError(err) {
4744
4746
  }
4745
4747
  async function connectToUpstream(config, onDisconnect, onListChanged) {
4746
4748
  const client = new Client(
4747
- { name: "mcph", version: true ? "0.45.0" : "dev" },
4749
+ { name: "mcph", version: true ? "0.46.1" : "dev" },
4748
4750
  { capabilities: {} }
4749
4751
  );
4750
4752
  let transport;
@@ -5225,7 +5227,7 @@ var ConnectServer = class _ConnectServer {
5225
5227
  this.apiUrl = apiUrl6;
5226
5228
  this.token = token6;
5227
5229
  this.server = new Server(
5228
- { name: "mcph", version: true ? "0.45.0" : "dev" },
5230
+ { name: "mcph", version: true ? "0.46.1" : "dev" },
5229
5231
  {
5230
5232
  capabilities: {
5231
5233
  tools: { listChanged: true },
@@ -7398,6 +7400,181 @@ function truncateVersion(v) {
7398
7400
  return v.length > 8 ? v.slice(0, 8) : v;
7399
7401
  }
7400
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
+ return true ? "0.46.1" : "dev";
7576
+ }
7577
+
7401
7578
  // src/index.ts
7402
7579
  var KNOWN_SUBCOMMANDS = [
7403
7580
  "compliance",
@@ -7407,6 +7584,7 @@ var KNOWN_SUBCOMMANDS = [
7407
7584
  "servers",
7408
7585
  "bundles",
7409
7586
  "completion",
7587
+ "upgrade",
7410
7588
  "help",
7411
7589
  "--help",
7412
7590
  "-h",
@@ -7466,6 +7644,14 @@ if (subcommand === "compliance") {
7466
7644
  process.exit(2);
7467
7645
  }
7468
7646
  runCompletion(parsed.options).then((r) => process.exit(r.exitCode));
7647
+ } else if (subcommand === "upgrade") {
7648
+ const parsed = parseUpgradeArgs(process.argv.slice(3));
7649
+ if (!parsed.ok) {
7650
+ process.stderr.write(`${parsed.error}
7651
+ `);
7652
+ process.exit(2);
7653
+ }
7654
+ runUpgrade(parsed.options).then((r) => process.exit(r.exitCode));
7469
7655
  } else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
7470
7656
  const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
7471
7657
  process.stdout.write(
@@ -7481,6 +7667,7 @@ if (subcommand === "compliance") {
7481
7667
  mcph compliance <target> [flags] Run the compliance suite against an MCP server
7482
7668
  mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
7483
7669
  mcph completion <shell> Print a shell completion script (bash|zsh|fish|powershell)
7670
+ mcph upgrade [--run] [--json] Show (or run) the upgrade command for @yawlabs/mcph
7484
7671
  mcph --version Print version
7485
7672
 
7486
7673
  Install:
@@ -7501,7 +7688,7 @@ ${installBlock}
7501
7688
  );
7502
7689
  process.exit(0);
7503
7690
  } else if (subcommand === "--version" || subcommand === "-V") {
7504
- process.stdout.write(`mcph ${true ? "0.45.0" : "dev"}
7691
+ process.stdout.write(`mcph ${true ? "0.46.1" : "dev"}
7505
7692
  `);
7506
7693
  process.exit(0);
7507
7694
  } else if (subcommand && !subcommand.startsWith("-")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.45.0",
3
+ "version": "0.46.1",
4
4
  "description": "mcp.hosting — one install, all your MCP servers, managed from the cloud",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Yaw Labs <contact@yaw.sh> (https://yaw.sh)",