@yawlabs/mcph 0.30.0 → 0.31.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.31.0 — 2026-04-18
6
+
7
+ - **"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.
8
+
5
9
  ## 0.30.0 — 2026-04-18
6
10
 
7
11
  - **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.31.0" : "dev";
950
950
  async function runDoctor(opts = {}) {
951
951
  const lines = [];
952
952
  const write = opts.out ?? ((s) => process.stdout.write(s));
@@ -2135,6 +2135,60 @@ function stepBindingKey(step, index) {
2135
2135
  return typeof step.id === "string" && step.id.length > 0 ? step.id : String(index);
2136
2136
  }
2137
2137
 
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
+
2138
2192
  // src/guide.ts
2139
2193
  import { readFile as readFile5 } from "fs/promises";
2140
2194
  var GUIDE_READ_TIMEOUT_MS = 1e3;
@@ -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.31.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.31.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}` };
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
+ };
5326
5390
  }
5391
+ const serverConfig = anyMatch;
5327
5392
  if (!profileAllows(this.profile, namespace)) {
5328
5393
  return {
5329
5394
  ok: false,
@@ -6422,7 +6487,7 @@ ${installBlock}
6422
6487
  );
6423
6488
  process.exit(0);
6424
6489
  } else if (subcommand === "--version" || subcommand === "-V") {
6425
- process.stdout.write(`mcph ${true ? "0.30.0" : "dev"}
6490
+ process.stdout.write(`mcph ${true ? "0.31.0" : "dev"}
6426
6491
  `);
6427
6492
  process.exit(0);
6428
6493
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.30.0",
3
+ "version": "0.31.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)",