@yawlabs/mcph 0.30.0 → 0.32.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.32.0 — 2026-04-18
6
+
7
+ - **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.
8
+
9
+ ## 0.31.0 — 2026-04-18
10
+
11
+ - **"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.
12
+
5
13
  ## 0.30.0 — 2026-04-18
6
14
 
7
15
  - **Inline bundle completions in `discover()`** — When a curated bundle has some installed servers but is missing one or two, `mcp_connect_discover` surfaces a "Bundle completions" block with the partial bundle id, what's already installed, and what to add. Top 3 entries, ranked by fewest-missing first (cheapest to complete), tie-broken by most-momentum then id. Same data source as `mcp_connect_bundles action="match"`, but inline so the model can act on the nudge without the extra round-trip. Suppressed when no curated bundle has any overlap with the installed set.
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.30.0" : "dev";
949
+ var VERSION = true ? "0.32.0" : "dev";
950
950
  async function runDoctor(opts = {}) {
951
951
  const lines = [];
952
952
  const write = opts.out ?? ((s) => process.stdout.write(s));
@@ -1264,6 +1264,60 @@ function compareSemver(a, b) {
1264
1264
  return 0;
1265
1265
  }
1266
1266
 
1267
+ // src/fuzzy.ts
1268
+ function levenshtein(a, b) {
1269
+ if (a === b) return 0;
1270
+ const aLen = a.length;
1271
+ const bLen = b.length;
1272
+ if (aLen === 0) return bLen;
1273
+ if (bLen === 0) return aLen;
1274
+ let prev = new Array(bLen + 1);
1275
+ let curr = new Array(bLen + 1);
1276
+ for (let j = 0; j <= bLen; j++) prev[j] = j;
1277
+ for (let i = 1; i <= aLen; i++) {
1278
+ curr[0] = i;
1279
+ for (let j = 1; j <= bLen; j++) {
1280
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
1281
+ curr[j] = Math.min(
1282
+ curr[j - 1] + 1,
1283
+ // insertion
1284
+ prev[j] + 1,
1285
+ // deletion
1286
+ prev[j - 1] + cost
1287
+ // substitution
1288
+ );
1289
+ }
1290
+ [prev, curr] = [curr, prev];
1291
+ }
1292
+ return prev[bLen];
1293
+ }
1294
+ function closestNames(query, candidates, limit) {
1295
+ if (limit <= 0) return [];
1296
+ const q = query.toLowerCase();
1297
+ const scored = [];
1298
+ for (const c of candidates) {
1299
+ if (c === query) continue;
1300
+ const lc = c.toLowerCase();
1301
+ let score = null;
1302
+ if (lc === q) {
1303
+ score = 0;
1304
+ } else if (lc.startsWith(q) || q.startsWith(lc)) {
1305
+ score = 1;
1306
+ } else if (lc.includes(q) || q.includes(lc)) {
1307
+ score = 2;
1308
+ } else {
1309
+ const d = levenshtein(q, lc);
1310
+ if (d <= 2) score = 2 + d;
1311
+ }
1312
+ if (score !== null) scored.push({ name: c, score });
1313
+ }
1314
+ scored.sort((a, b) => {
1315
+ if (a.score !== b.score) return a.score - b.score;
1316
+ return a.name.localeCompare(b.name);
1317
+ });
1318
+ return scored.slice(0, limit).map((s) => s.name);
1319
+ }
1320
+
1267
1321
  // src/install-cmd.ts
1268
1322
  import { existsSync as existsSync2 } from "fs";
1269
1323
  import { chmod, mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
@@ -3836,7 +3890,7 @@ function categorizeSpawnError(err) {
3836
3890
  }
3837
3891
  async function connectToUpstream(config, onDisconnect, onListChanged) {
3838
3892
  const client = new Client(
3839
- { name: "mcph", version: true ? "0.30.0" : "dev" },
3893
+ { name: "mcph", version: true ? "0.32.0" : "dev" },
3840
3894
  { capabilities: {} }
3841
3895
  );
3842
3896
  let transport;
@@ -4353,7 +4407,7 @@ var ConnectServer = class _ConnectServer {
4353
4407
  this.apiUrl = apiUrl6;
4354
4408
  this.token = token6;
4355
4409
  this.server = new Server(
4356
- { name: "mcph", version: true ? "0.30.0" : "dev" },
4410
+ { name: "mcph", version: true ? "0.32.0" : "dev" },
4357
4411
  {
4358
4412
  capabilities: {
4359
4413
  tools: { listChanged: true },
@@ -5320,10 +5374,21 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
5320
5374
  serverId: existing.config.id
5321
5375
  };
5322
5376
  }
5323
- const serverConfig = this.config?.servers.find((s) => s.namespace === namespace && s.isActive);
5324
- if (!serverConfig) {
5325
- return { ok: false, isChanged: false, message: `"${namespace}" not found or disabled.` };
5377
+ const anyMatch = this.config?.servers.find((s) => s.namespace === namespace);
5378
+ if (!anyMatch) {
5379
+ const allNamespaces = this.config?.servers.map((s) => s.namespace) ?? [];
5380
+ const suggestions = closestNames(namespace, allNamespaces, 3);
5381
+ const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : " Use mcp_connect_discover to see installed servers.";
5382
+ return { ok: false, isChanged: false, message: `"${namespace}" is not installed.${hint}` };
5326
5383
  }
5384
+ if (!anyMatch.isActive) {
5385
+ return {
5386
+ ok: false,
5387
+ isChanged: false,
5388
+ message: `"${namespace}" is installed but disabled. Enable it at https://mcp.hosting to activate.`
5389
+ };
5390
+ }
5391
+ const serverConfig = anyMatch;
5327
5392
  if (!profileAllows(this.profile, namespace)) {
5328
5393
  return {
5329
5394
  ok: false,
@@ -6378,6 +6443,7 @@ To load the top pack in one step, call \`mcp_connect_activate\` with namespaces=
6378
6443
  };
6379
6444
 
6380
6445
  // src/index.ts
6446
+ var KNOWN_SUBCOMMANDS = ["compliance", "install", "doctor", "help", "--help", "-h", "--version", "-V"];
6381
6447
  var subcommand = process.argv[2];
6382
6448
  if (subcommand === "compliance") {
6383
6449
  runComplianceCommand(process.argv.slice(3)).then((code) => process.exit(code));
@@ -6422,9 +6488,16 @@ ${installBlock}
6422
6488
  );
6423
6489
  process.exit(0);
6424
6490
  } else if (subcommand === "--version" || subcommand === "-V") {
6425
- process.stdout.write(`mcph ${true ? "0.30.0" : "dev"}
6491
+ process.stdout.write(`mcph ${true ? "0.32.0" : "dev"}
6426
6492
  `);
6427
6493
  process.exit(0);
6494
+ } else if (subcommand && !subcommand.startsWith("-")) {
6495
+ const visible = KNOWN_SUBCOMMANDS.filter((s) => !s.startsWith("-") && s !== "help");
6496
+ const suggestions = closestNames(subcommand, visible, 3);
6497
+ const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : " Run `mcph --help` for the list of subcommands.";
6498
+ process.stderr.write(`mcph: unknown subcommand "${subcommand}".${hint}
6499
+ `);
6500
+ process.exit(2);
6428
6501
  } else {
6429
6502
  runServer();
6430
6503
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.30.0",
3
+ "version": "0.32.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)",