@yawlabs/mcph 0.31.0 → 0.33.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,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.33.0 — 2026-04-18
6
+
7
+ - **`mcph doctor` ENVIRONMENT section** — New block enumerating every behavior-modifier env var mcph actually reads (`MCPH_POLL_INTERVAL`, `MCPH_SERVER_CAP`, `MCPH_MIN_COMPLIANCE`, `MCPH_AUTO_LOAD`, `MCPH_PRUNE_RESPONSES`). Each shows its current value, or `(not set — <default>)` when unset. Closes a diagnostic gap where users reporting "my server cap isn't taking effect" or "compliance filter isn't blocking anything" had no doctor signal on whether the knob was even set. TOKEN / URL / DISABLE_PERSISTENCE still get their dedicated sections (richer context there).
8
+
9
+ ## 0.32.0 — 2026-04-18
10
+
11
+ - **Unknown CLI subcommand detection + typo suggestions** — `mcph <typo>` (e.g. `mcph instal`, `mcph docto`) now exits 2 with `unknown subcommand "X". Did you mean: install?` instead of silently falling through to MCP-server mode and erroring opaquely on the missing token. Bare flags (anything with a leading `-`) still fall through so server startup can parse them.
12
+
5
13
  ## 0.31.0 — 2026-04-18
6
14
 
7
15
  - **"Did you mean?" suggestions on `mcp_connect_activate`** — When a caller tries to activate a namespace that doesn't exist, the error message now splits the two underlying cases: (a) not installed at all (with up to 3 fuzzy-matched installed namespaces via substring containment or ≤2 edit distance, or a pointer to `mcp_connect_discover` when nothing is close), and (b) installed but disabled in the dashboard (with a pointer to `mcp.hosting` to enable). Replaces the previous conflated "`X` not found or disabled" message.
package/dist/index.js CHANGED
@@ -946,7 +946,7 @@ function errorMessage(err) {
946
946
  }
947
947
 
948
948
  // src/doctor-cmd.ts
949
- var VERSION = true ? "0.31.0" : "dev";
949
+ var VERSION = true ? "0.33.0" : "dev";
950
950
  async function runDoctor(opts = {}) {
951
951
  const lines = [];
952
952
  const write = opts.out ?? ((s) => process.stdout.write(s));
@@ -981,6 +981,7 @@ async function runDoctor(opts = {}) {
981
981
  print(` value: ${config.apiBase}`);
982
982
  print(` source: ${config.apiBaseSource}`);
983
983
  print("");
984
+ renderEnvSection({ env, print });
984
985
  await renderStateSection({ home, env, print });
985
986
  const clients = probeClients({ home, os, cwd });
986
987
  print("INSTALLED CLIENTS (probed config files)");
@@ -1034,6 +1035,24 @@ async function runDoctor(opts = {}) {
1034
1035
  }
1035
1036
  return { exitCode, lines, snapshot: { version: VERSION, config, clients } };
1036
1037
  }
1038
+ function renderEnvSection(opts) {
1039
+ const { env, print } = opts;
1040
+ const vars = [
1041
+ { name: "MCPH_POLL_INTERVAL", defaultHint: "default 60s" },
1042
+ { name: "MCPH_SERVER_CAP", defaultHint: "default 6" },
1043
+ { name: "MCPH_MIN_COMPLIANCE", defaultHint: "filter inactive" },
1044
+ { name: "MCPH_AUTO_LOAD", defaultHint: "auto-load inactive" },
1045
+ { name: "MCPH_PRUNE_RESPONSES", defaultHint: "pruning active" }
1046
+ ];
1047
+ const widest = vars.reduce((m, v) => Math.max(m, v.name.length), 0);
1048
+ print("ENVIRONMENT (behavior overrides)");
1049
+ for (const v of vars) {
1050
+ const raw = env[v.name];
1051
+ const value = raw === void 0 || raw === "" ? `(not set \u2014 ${v.defaultHint})` : raw;
1052
+ print(` ${v.name.padEnd(widest)} ${value}`);
1053
+ }
1054
+ print("");
1055
+ }
1037
1056
  async function renderStateSection(opts) {
1038
1057
  const { home, env, print } = opts;
1039
1058
  const raw = env.MCPH_DISABLE_PERSISTENCE;
@@ -1264,6 +1283,60 @@ function compareSemver(a, b) {
1264
1283
  return 0;
1265
1284
  }
1266
1285
 
1286
+ // src/fuzzy.ts
1287
+ function levenshtein(a, b) {
1288
+ if (a === b) return 0;
1289
+ const aLen = a.length;
1290
+ const bLen = b.length;
1291
+ if (aLen === 0) return bLen;
1292
+ if (bLen === 0) return aLen;
1293
+ let prev = new Array(bLen + 1);
1294
+ let curr = new Array(bLen + 1);
1295
+ for (let j = 0; j <= bLen; j++) prev[j] = j;
1296
+ for (let i = 1; i <= aLen; i++) {
1297
+ curr[0] = i;
1298
+ for (let j = 1; j <= bLen; j++) {
1299
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
1300
+ curr[j] = Math.min(
1301
+ curr[j - 1] + 1,
1302
+ // insertion
1303
+ prev[j] + 1,
1304
+ // deletion
1305
+ prev[j - 1] + cost
1306
+ // substitution
1307
+ );
1308
+ }
1309
+ [prev, curr] = [curr, prev];
1310
+ }
1311
+ return prev[bLen];
1312
+ }
1313
+ function closestNames(query, candidates, limit) {
1314
+ if (limit <= 0) return [];
1315
+ const q = query.toLowerCase();
1316
+ const scored = [];
1317
+ for (const c of candidates) {
1318
+ if (c === query) continue;
1319
+ const lc = c.toLowerCase();
1320
+ let score = null;
1321
+ if (lc === q) {
1322
+ score = 0;
1323
+ } else if (lc.startsWith(q) || q.startsWith(lc)) {
1324
+ score = 1;
1325
+ } else if (lc.includes(q) || q.includes(lc)) {
1326
+ score = 2;
1327
+ } else {
1328
+ const d = levenshtein(q, lc);
1329
+ if (d <= 2) score = 2 + d;
1330
+ }
1331
+ if (score !== null) scored.push({ name: c, score });
1332
+ }
1333
+ scored.sort((a, b) => {
1334
+ if (a.score !== b.score) return a.score - b.score;
1335
+ return a.name.localeCompare(b.name);
1336
+ });
1337
+ return scored.slice(0, limit).map((s) => s.name);
1338
+ }
1339
+
1267
1340
  // src/install-cmd.ts
1268
1341
  import { existsSync as existsSync2 } from "fs";
1269
1342
  import { chmod, mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
@@ -2135,60 +2208,6 @@ function stepBindingKey(step, index) {
2135
2208
  return typeof step.id === "string" && step.id.length > 0 ? step.id : String(index);
2136
2209
  }
2137
2210
 
2138
- // src/fuzzy.ts
2139
- function levenshtein(a, b) {
2140
- if (a === b) return 0;
2141
- const aLen = a.length;
2142
- const bLen = b.length;
2143
- if (aLen === 0) return bLen;
2144
- if (bLen === 0) return aLen;
2145
- let prev = new Array(bLen + 1);
2146
- let curr = new Array(bLen + 1);
2147
- for (let j = 0; j <= bLen; j++) prev[j] = j;
2148
- for (let i = 1; i <= aLen; i++) {
2149
- curr[0] = i;
2150
- for (let j = 1; j <= bLen; j++) {
2151
- const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
2152
- curr[j] = Math.min(
2153
- curr[j - 1] + 1,
2154
- // insertion
2155
- prev[j] + 1,
2156
- // deletion
2157
- prev[j - 1] + cost
2158
- // substitution
2159
- );
2160
- }
2161
- [prev, curr] = [curr, prev];
2162
- }
2163
- return prev[bLen];
2164
- }
2165
- function closestNames(query, candidates, limit) {
2166
- if (limit <= 0) return [];
2167
- const q = query.toLowerCase();
2168
- const scored = [];
2169
- for (const c of candidates) {
2170
- if (c === query) continue;
2171
- const lc = c.toLowerCase();
2172
- let score = null;
2173
- if (lc === q) {
2174
- score = 0;
2175
- } else if (lc.startsWith(q) || q.startsWith(lc)) {
2176
- score = 1;
2177
- } else if (lc.includes(q) || q.includes(lc)) {
2178
- score = 2;
2179
- } else {
2180
- const d = levenshtein(q, lc);
2181
- if (d <= 2) score = 2 + d;
2182
- }
2183
- if (score !== null) scored.push({ name: c, score });
2184
- }
2185
- scored.sort((a, b) => {
2186
- if (a.score !== b.score) return a.score - b.score;
2187
- return a.name.localeCompare(b.name);
2188
- });
2189
- return scored.slice(0, limit).map((s) => s.name);
2190
- }
2191
-
2192
2211
  // src/guide.ts
2193
2212
  import { readFile as readFile5 } from "fs/promises";
2194
2213
  var GUIDE_READ_TIMEOUT_MS = 1e3;
@@ -3890,7 +3909,7 @@ function categorizeSpawnError(err) {
3890
3909
  }
3891
3910
  async function connectToUpstream(config, onDisconnect, onListChanged) {
3892
3911
  const client = new Client(
3893
- { name: "mcph", version: true ? "0.31.0" : "dev" },
3912
+ { name: "mcph", version: true ? "0.33.0" : "dev" },
3894
3913
  { capabilities: {} }
3895
3914
  );
3896
3915
  let transport;
@@ -4407,7 +4426,7 @@ var ConnectServer = class _ConnectServer {
4407
4426
  this.apiUrl = apiUrl6;
4408
4427
  this.token = token6;
4409
4428
  this.server = new Server(
4410
- { name: "mcph", version: true ? "0.31.0" : "dev" },
4429
+ { name: "mcph", version: true ? "0.33.0" : "dev" },
4411
4430
  {
4412
4431
  capabilities: {
4413
4432
  tools: { listChanged: true },
@@ -6443,6 +6462,7 @@ To load the top pack in one step, call \`mcp_connect_activate\` with namespaces=
6443
6462
  };
6444
6463
 
6445
6464
  // src/index.ts
6465
+ var KNOWN_SUBCOMMANDS = ["compliance", "install", "doctor", "help", "--help", "-h", "--version", "-V"];
6446
6466
  var subcommand = process.argv[2];
6447
6467
  if (subcommand === "compliance") {
6448
6468
  runComplianceCommand(process.argv.slice(3)).then((code) => process.exit(code));
@@ -6487,9 +6507,16 @@ ${installBlock}
6487
6507
  );
6488
6508
  process.exit(0);
6489
6509
  } else if (subcommand === "--version" || subcommand === "-V") {
6490
- process.stdout.write(`mcph ${true ? "0.31.0" : "dev"}
6510
+ process.stdout.write(`mcph ${true ? "0.33.0" : "dev"}
6491
6511
  `);
6492
6512
  process.exit(0);
6513
+ } else if (subcommand && !subcommand.startsWith("-")) {
6514
+ const visible = KNOWN_SUBCOMMANDS.filter((s) => !s.startsWith("-") && s !== "help");
6515
+ const suggestions = closestNames(subcommand, visible, 3);
6516
+ const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : " Run `mcph --help` for the list of subcommands.";
6517
+ process.stderr.write(`mcph: unknown subcommand "${subcommand}".${hint}
6518
+ `);
6519
+ process.exit(2);
6493
6520
  } else {
6494
6521
  runServer();
6495
6522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.31.0",
3
+ "version": "0.33.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)",