@yawlabs/mcph 0.38.0 → 0.39.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 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.39.0 — 2026-04-18
6
+
7
+ - **`mcph servers` CLI subcommand** — Lists the servers currently configured for your account in the mcp.hosting dashboard, hitting the same `/api/connect/config` endpoint that `runServer` polls at startup. Fills a gap between `mcph doctor` (local state: config files, clients, state.json) and the web dashboard: users can sanity-check their dashboard edits from the terminal, support engineers can ask for `mcph servers --json` output in a ticket, and scripts can pick a namespace up-front before piping into `mcph compliance` or `mcph install`. Table view groups the relevant columns (namespace, name, type, enabled/disabled, compliance grade, cached tool count) and is sorted alphabetically by namespace for diffable re-runs; `--json` emits the raw backend response verbatim. Exit codes: 0 success, 1 no token, 2 fetch error.
8
+
5
9
  ## 0.38.0 — 2026-04-18
6
10
 
7
11
  - **`mcph reset-learning` CLI subcommand** — Deletes `~/.mcph/state.json` so cross-session learning starts fresh; prints the entry counts that were cleared. Pairs with v0.37.0's doctor RELIABILITY section: once a namespace has been flagged flaky, the dispatch penalty branch (v0.36.0) keeps suppressing it until enough new successes pile up — but if the user has since fixed the underlying cause (rotated a token, swapped the upstream, re-authed), that history is stale and the penalty has overstayed its welcome. This gives them a direct CLI lever to clear it. Scope is all-or-nothing by design; a per-namespace flag is footgunny (user clears one, forgets the others, keeps getting silently mis-ranked). No-op with an explanatory message when `MCPH_DISABLE_PERSISTENCE` is set or the file doesn't exist, so `mcph reset-learning` never surprises. Exit 0 on success or no-op, exit 1 on I/O error (permissions, disk).
package/dist/index.js CHANGED
@@ -1004,7 +1004,7 @@ function selectFlakyNamespaces(entries, limit) {
1004
1004
  }
1005
1005
 
1006
1006
  // src/doctor-cmd.ts
1007
- var VERSION = true ? "0.38.0" : "dev";
1007
+ var VERSION = true ? "0.39.0" : "dev";
1008
1008
  async function runDoctor(opts = {}) {
1009
1009
  const lines = [];
1010
1010
  const write = opts.out ?? ((s) => process.stdout.write(s));
@@ -4065,7 +4065,7 @@ function categorizeSpawnError(err) {
4065
4065
  }
4066
4066
  async function connectToUpstream(config, onDisconnect, onListChanged) {
4067
4067
  const client = new Client(
4068
- { name: "mcph", version: true ? "0.38.0" : "dev" },
4068
+ { name: "mcph", version: true ? "0.39.0" : "dev" },
4069
4069
  { capabilities: {} }
4070
4070
  );
4071
4071
  let transport;
@@ -4546,7 +4546,7 @@ var ConnectServer = class _ConnectServer {
4546
4546
  this.apiUrl = apiUrl6;
4547
4547
  this.token = token6;
4548
4548
  this.server = new Server(
4549
- { name: "mcph", version: true ? "0.38.0" : "dev" },
4549
+ { name: "mcph", version: true ? "0.39.0" : "dev" },
4550
4550
  {
4551
4551
  capabilities: {
4552
4552
  tools: { listChanged: true },
@@ -6598,12 +6598,117 @@ To load the top pack in one step, call \`mcp_connect_activate\` with namespaces=
6598
6598
  }
6599
6599
  };
6600
6600
 
6601
+ // src/servers-cmd.ts
6602
+ function parseServersArgs(argv) {
6603
+ let json = false;
6604
+ for (const a of argv) {
6605
+ if (a === "--json") {
6606
+ json = true;
6607
+ } else if (a === "--help" || a === "-h") {
6608
+ return { ok: false, error: SERVERS_USAGE };
6609
+ } else {
6610
+ return { ok: false, error: `mcph servers: unknown argument "${a}"
6611
+
6612
+ ${SERVERS_USAGE}` };
6613
+ }
6614
+ }
6615
+ return { ok: true, options: { json } };
6616
+ }
6617
+ var SERVERS_USAGE = `Usage: mcph servers [--json]
6618
+
6619
+ List the servers configured in your mcp.hosting dashboard.
6620
+
6621
+ --json Emit machine-readable JSON instead of a table.`;
6622
+ async function runServersCommand(opts = {}) {
6623
+ const write = opts.out ?? ((s) => process.stdout.write(s));
6624
+ const writeErr = opts.err ?? ((s) => process.stderr.write(s));
6625
+ const lines = [];
6626
+ const print = (s = "") => {
6627
+ lines.push(s);
6628
+ write(`${s}
6629
+ `);
6630
+ };
6631
+ const printErr = (s) => {
6632
+ lines.push(s);
6633
+ writeErr(`${s}
6634
+ `);
6635
+ };
6636
+ const config = await loadMcphConfig({
6637
+ cwd: opts.cwd,
6638
+ home: opts.home,
6639
+ env: opts.env
6640
+ });
6641
+ if (!config.token) {
6642
+ printErr("mcph servers: no token resolved. Run `mcph install <client> --token mcp_pat_\u2026` or set MCPH_TOKEN.");
6643
+ return { exitCode: 1, lines };
6644
+ }
6645
+ const fetcher = opts.fetcher ?? fetchConfig;
6646
+ let backend;
6647
+ try {
6648
+ backend = await fetcher(config.apiBase, config.token);
6649
+ } catch (err) {
6650
+ const msg = err instanceof ConfigError || err instanceof Error ? err.message : String(err);
6651
+ printErr(`mcph servers: ${msg}`);
6652
+ return { exitCode: 2, lines };
6653
+ }
6654
+ if (!backend) {
6655
+ printErr("mcph servers: backend returned no data (unexpected 304).");
6656
+ return { exitCode: 2, lines };
6657
+ }
6658
+ if (opts.json) {
6659
+ print(JSON.stringify(backend, null, 2));
6660
+ return { exitCode: 0, lines };
6661
+ }
6662
+ renderTable(backend, print);
6663
+ return { exitCode: 0, lines };
6664
+ }
6665
+ function renderTable(cfg, print) {
6666
+ const servers = cfg.servers;
6667
+ if (servers.length === 0) {
6668
+ print("No servers configured yet. Visit https://mcp.hosting to add one.");
6669
+ return;
6670
+ }
6671
+ const version = cfg.configVersion ? ` (config ${truncateVersion(cfg.configVersion)})` : "";
6672
+ const active = servers.filter((s) => s.isActive).length;
6673
+ const disabled = servers.length - active;
6674
+ const summary = disabled === 0 ? `${servers.length} server${servers.length === 1 ? "" : "s"}` : `${servers.length} servers (${active} enabled, ${disabled} disabled)`;
6675
+ print(`${summary}${version}`);
6676
+ print("");
6677
+ const rows = servers.map((s) => ({
6678
+ namespace: s.namespace,
6679
+ name: s.name,
6680
+ type: s.type,
6681
+ status: s.isActive ? "enabled" : "disabled",
6682
+ grade: s.complianceGrade ?? "-",
6683
+ tools: s.toolCache ? String(s.toolCache.length) : "?"
6684
+ }));
6685
+ const widths = {
6686
+ namespace: Math.max("NAMESPACE".length, ...rows.map((r) => r.namespace.length)),
6687
+ name: Math.max("NAME".length, ...rows.map((r) => r.name.length)),
6688
+ type: Math.max("TYPE".length, ...rows.map((r) => r.type.length)),
6689
+ status: Math.max("STATUS".length, ...rows.map((r) => r.status.length)),
6690
+ grade: Math.max("GRADE".length, ...rows.map((r) => r.grade.length)),
6691
+ tools: Math.max("TOOLS".length, ...rows.map((r) => r.tools.length))
6692
+ };
6693
+ const header = ` ${"NAMESPACE".padEnd(widths.namespace)} ${"NAME".padEnd(widths.name)} ${"TYPE".padEnd(widths.type)} ${"STATUS".padEnd(widths.status)} ${"GRADE".padEnd(widths.grade)} ${"TOOLS".padStart(widths.tools)}`;
6694
+ print(header);
6695
+ const sorted = [...rows].sort((a, b) => a.namespace.localeCompare(b.namespace));
6696
+ for (const r of sorted) {
6697
+ const line = ` ${r.namespace.padEnd(widths.namespace)} ${r.name.padEnd(widths.name)} ${r.type.padEnd(widths.type)} ${r.status.padEnd(widths.status)} ${r.grade.padEnd(widths.grade)} ${r.tools.padStart(widths.tools)}`;
6698
+ print(line);
6699
+ }
6700
+ }
6701
+ function truncateVersion(v) {
6702
+ return v.length > 8 ? v.slice(0, 8) : v;
6703
+ }
6704
+
6601
6705
  // src/index.ts
6602
6706
  var KNOWN_SUBCOMMANDS = [
6603
6707
  "compliance",
6604
6708
  "install",
6605
6709
  "doctor",
6606
6710
  "reset-learning",
6711
+ "servers",
6607
6712
  "help",
6608
6713
  "--help",
6609
6714
  "-h",
@@ -6625,6 +6730,14 @@ if (subcommand === "compliance") {
6625
6730
  runDoctor().then((r) => process.exit(r.exitCode));
6626
6731
  } else if (subcommand === "reset-learning") {
6627
6732
  runResetLearning().then((r) => process.exit(r.exitCode));
6733
+ } else if (subcommand === "servers") {
6734
+ const parsed = parseServersArgs(process.argv.slice(3));
6735
+ if (!parsed.ok) {
6736
+ process.stderr.write(`${parsed.error}
6737
+ `);
6738
+ process.exit(2);
6739
+ }
6740
+ runServersCommand(parsed.options).then((r) => process.exit(r.exitCode));
6628
6741
  } else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
6629
6742
  const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
6630
6743
  process.stdout.write(
@@ -6635,6 +6748,7 @@ if (subcommand === "compliance") {
6635
6748
  mcph Run as MCP server (requires a token)
6636
6749
  mcph install <client> [flags] Auto-edit an MCP client's config to launch mcph
6637
6750
  mcph doctor Print loaded config + detected clients (support diagnostic)
6751
+ mcph servers [--json] List servers configured in your mcp.hosting dashboard
6638
6752
  mcph compliance <target> [flags] Run the compliance suite against an MCP server
6639
6753
  mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
6640
6754
  mcph --version Print version
@@ -6657,7 +6771,7 @@ ${installBlock}
6657
6771
  );
6658
6772
  process.exit(0);
6659
6773
  } else if (subcommand === "--version" || subcommand === "-V") {
6660
- process.stdout.write(`mcph ${true ? "0.38.0" : "dev"}
6774
+ process.stdout.write(`mcph ${true ? "0.39.0" : "dev"}
6661
6775
  `);
6662
6776
  process.exit(0);
6663
6777
  } else if (subcommand && !subcommand.startsWith("-")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
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)",