@yawlabs/mcph 0.37.0 → 0.38.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.38.0 — 2026-04-18
6
+
7
+ - **`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).
8
+
5
9
  ## 0.37.0 — 2026-04-18
6
10
 
7
11
  - **`mcph doctor` RELIABILITY section** — New block surfaces flaky dormant namespaces pulled directly from `~/.mcph/state.json`, using the same ≥3-dispatches / <80%-success definition as `mcp_connect_health`'s cross-session reliability block — so the CLI diagnostic and the LLM-facing health tool agree on what "flaky" means. Sorted worst-rate first, capped at 5. Silently omitted when no namespace qualifies, state.json doesn't exist yet, or `MCPH_DISABLE_PERSISTENCE` is set. Threshold constants + sort logic extracted into `selectFlakyNamespaces` so handleHealth and doctor can't drift apart.
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.37.0" : "dev";
1007
+ var VERSION = true ? "0.38.0" : "dev";
1008
1008
  async function runDoctor(opts = {}) {
1009
1009
  const lines = [];
1010
1010
  const write = opts.out ?? ((s) => process.stdout.write(s));
@@ -1791,9 +1791,61 @@ ${USAGE}` };
1791
1791
  }
1792
1792
  var INSTALL_USAGE = USAGE;
1793
1793
 
1794
+ // src/reset-learning-cmd.ts
1795
+ import { unlink } from "fs/promises";
1796
+ import { homedir as homedir6 } from "os";
1797
+ import { join as join6 } from "path";
1798
+ async function runResetLearning(opts = {}) {
1799
+ const home = opts.home ?? homedir6();
1800
+ const env = opts.env ?? process.env;
1801
+ const write = opts.out ?? ((s) => process.stdout.write(s));
1802
+ const writeErr = opts.err ?? ((s) => process.stderr.write(s));
1803
+ const lines = [];
1804
+ const print = (s = "") => {
1805
+ lines.push(s);
1806
+ write(`${s}
1807
+ `);
1808
+ };
1809
+ const printErr = (s) => {
1810
+ lines.push(s);
1811
+ writeErr(`${s}
1812
+ `);
1813
+ };
1814
+ const filePath = join6(userConfigDir(home), STATE_FILENAME);
1815
+ const raw = env.MCPH_DISABLE_PERSISTENCE;
1816
+ const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
1817
+ if (disabled) {
1818
+ print("mcph reset-learning: persistence is disabled (MCPH_DISABLE_PERSISTENCE) \u2014 nothing to clear.");
1819
+ return { exitCode: 0, lines, removed: false, path: filePath };
1820
+ }
1821
+ const persisted = await loadState(filePath);
1822
+ const learningCount = Object.keys(persisted.learning).length;
1823
+ const packCount = persisted.packHistory.length;
1824
+ try {
1825
+ await unlink(filePath);
1826
+ } catch (err) {
1827
+ if (isFileNotFound2(err)) {
1828
+ print("mcph reset-learning: no persisted state to reset.");
1829
+ print(` path: ${filePath}`);
1830
+ return { exitCode: 0, lines, removed: false, path: filePath };
1831
+ }
1832
+ const msg = err instanceof Error ? err.message : String(err);
1833
+ printErr(`mcph reset-learning: failed to remove ${filePath}: ${msg}`);
1834
+ return { exitCode: 1, lines, removed: false, path: filePath };
1835
+ }
1836
+ print("mcph reset-learning: cleared persisted state.");
1837
+ print(` path: ${filePath}`);
1838
+ print(` learning entries removed: ${learningCount}`);
1839
+ print(` pack history entries removed: ${packCount}`);
1840
+ return { exitCode: 0, lines, removed: true, path: filePath };
1841
+ }
1842
+ function isFileNotFound2(err) {
1843
+ return !!err && typeof err === "object" && "code" in err && err.code === "ENOENT";
1844
+ }
1845
+
1794
1846
  // src/server.ts
1795
1847
  import { readFile as readFile6 } from "fs/promises";
1796
- import { homedir as homedir6 } from "os";
1848
+ import { homedir as homedir7 } from "os";
1797
1849
  import { isAbsolute, relative, resolve as resolve3 } from "path";
1798
1850
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1799
1851
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -4013,7 +4065,7 @@ function categorizeSpawnError(err) {
4013
4065
  }
4014
4066
  async function connectToUpstream(config, onDisconnect, onListChanged) {
4015
4067
  const client = new Client(
4016
- { name: "mcph", version: true ? "0.37.0" : "dev" },
4068
+ { name: "mcph", version: true ? "0.38.0" : "dev" },
4017
4069
  { capabilities: {} }
4018
4070
  );
4019
4071
  let transport;
@@ -4494,7 +4546,7 @@ var ConnectServer = class _ConnectServer {
4494
4546
  this.apiUrl = apiUrl6;
4495
4547
  this.token = token6;
4496
4548
  this.server = new Server(
4497
- { name: "mcph", version: true ? "0.37.0" : "dev" },
4549
+ { name: "mcph", version: true ? "0.38.0" : "dev" },
4498
4550
  {
4499
4551
  capabilities: {
4500
4552
  tools: { listChanged: true },
@@ -5929,7 +5981,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
5929
5981
  }
5930
5982
  const ALLOWED_FILENAMES = ["claude_desktop_config.json", "mcp.json", "settings.json", "mcp_config.json"];
5931
5983
  try {
5932
- const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve3(homedir6(), filepath.slice(2)) : resolve3(filepath);
5984
+ const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve3(homedir7(), filepath.slice(2)) : resolve3(filepath);
5933
5985
  const resolvedBasename = resolved.split(/[/\\]/).pop() || "";
5934
5986
  if (!ALLOWED_FILENAMES.includes(resolvedBasename)) {
5935
5987
  return {
@@ -5946,7 +5998,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
5946
5998
  const rel = relative(base, p);
5947
5999
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
5948
6000
  };
5949
- if (!isUnder(homedir6(), resolved) && !isUnder(process.cwd(), resolved)) {
6001
+ if (!isUnder(homedir7(), resolved) && !isUnder(process.cwd(), resolved)) {
5950
6002
  return {
5951
6003
  content: [
5952
6004
  { type: "text", text: "Import path must be under your home directory or the current working directory." }
@@ -6547,7 +6599,17 @@ To load the top pack in one step, call \`mcp_connect_activate\` with namespaces=
6547
6599
  };
6548
6600
 
6549
6601
  // src/index.ts
6550
- var KNOWN_SUBCOMMANDS = ["compliance", "install", "doctor", "help", "--help", "-h", "--version", "-V"];
6602
+ var KNOWN_SUBCOMMANDS = [
6603
+ "compliance",
6604
+ "install",
6605
+ "doctor",
6606
+ "reset-learning",
6607
+ "help",
6608
+ "--help",
6609
+ "-h",
6610
+ "--version",
6611
+ "-V"
6612
+ ];
6551
6613
  var subcommand = process.argv[2];
6552
6614
  if (subcommand === "compliance") {
6553
6615
  runComplianceCommand(process.argv.slice(3)).then((code) => process.exit(code));
@@ -6561,6 +6623,8 @@ if (subcommand === "compliance") {
6561
6623
  runInstall(parsed.options).then((r) => process.exit(r.exitCode));
6562
6624
  } else if (subcommand === "doctor") {
6563
6625
  runDoctor().then((r) => process.exit(r.exitCode));
6626
+ } else if (subcommand === "reset-learning") {
6627
+ runResetLearning().then((r) => process.exit(r.exitCode));
6564
6628
  } else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
6565
6629
  const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
6566
6630
  process.stdout.write(
@@ -6572,6 +6636,7 @@ if (subcommand === "compliance") {
6572
6636
  mcph install <client> [flags] Auto-edit an MCP client's config to launch mcph
6573
6637
  mcph doctor Print loaded config + detected clients (support diagnostic)
6574
6638
  mcph compliance <target> [flags] Run the compliance suite against an MCP server
6639
+ mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
6575
6640
  mcph --version Print version
6576
6641
 
6577
6642
  Install:
@@ -6592,7 +6657,7 @@ ${installBlock}
6592
6657
  );
6593
6658
  process.exit(0);
6594
6659
  } else if (subcommand === "--version" || subcommand === "-V") {
6595
- process.stdout.write(`mcph ${true ? "0.37.0" : "dev"}
6660
+ process.stdout.write(`mcph ${true ? "0.38.0" : "dev"}
6596
6661
  `);
6597
6662
  process.exit(0);
6598
6663
  } else if (subcommand && !subcommand.startsWith("-")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.37.0",
3
+ "version": "0.38.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)",