@yawlabs/mcp 0.64.1 → 0.64.2

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.
Files changed (2) hide show
  1. package/dist/index.js +155 -63
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1148,9 +1148,15 @@ async function runBundlesCommand(opts = {}) {
1148
1148
  env: opts.env
1149
1149
  });
1150
1150
  if (!config.token) {
1151
- printErr(
1152
- "yaw-mcp bundles match: no token resolved. Run `yaw-mcp install <client> --token mcp_pat_\u2026` or set YAW_MCP_TOKEN."
1153
- );
1151
+ const msg = "no token resolved. Run `yaw-mcp install <client> --token mcp_pat_\u2026` or set YAW_MCP_TOKEN.";
1152
+ if (opts.json) {
1153
+ const jsonErr = JSON.stringify({ ok: false, error: msg });
1154
+ lines.push(jsonErr);
1155
+ writeErr(`${jsonErr}
1156
+ `);
1157
+ } else {
1158
+ printErr(`yaw-mcp bundles match: ${msg}`);
1159
+ }
1154
1160
  return { exitCode: 1, lines };
1155
1161
  }
1156
1162
  const fetcher = opts.fetcher ?? fetchConfig;
@@ -1159,11 +1165,26 @@ async function runBundlesCommand(opts = {}) {
1159
1165
  backend = await fetcher(config.apiBase, config.token);
1160
1166
  } catch (err) {
1161
1167
  const msg = err instanceof ConfigError || err instanceof Error ? err.message : String(err);
1162
- printErr(`yaw-mcp bundles match: ${msg}`);
1168
+ if (opts.json) {
1169
+ const jsonErr = JSON.stringify({ ok: false, error: msg });
1170
+ lines.push(jsonErr);
1171
+ writeErr(`${jsonErr}
1172
+ `);
1173
+ } else {
1174
+ printErr(`yaw-mcp bundles match: ${msg}`);
1175
+ }
1163
1176
  return { exitCode: 2, lines };
1164
1177
  }
1165
1178
  if (!backend) {
1166
- printErr("yaw-mcp bundles match: backend returned 304 without a conditional request.");
1179
+ const msg = "backend returned 304 without a conditional request.";
1180
+ if (opts.json) {
1181
+ const jsonErr = JSON.stringify({ ok: false, error: msg });
1182
+ lines.push(jsonErr);
1183
+ writeErr(`${jsonErr}
1184
+ `);
1185
+ } else {
1186
+ printErr(`yaw-mcp bundles match: ${msg}`);
1187
+ }
1167
1188
  return { exitCode: 2, lines };
1168
1189
  }
1169
1190
  const installed = backend.servers.filter((s) => s.isActive).map((s) => s.namespace);
@@ -1380,18 +1401,27 @@ function renderScript(shell) {
1380
1401
  return renderPowershell();
1381
1402
  }
1382
1403
  }
1404
+ function isPlaceholder(s) {
1405
+ return s.startsWith("<") && s.endsWith(">");
1406
+ }
1383
1407
  function renderBash() {
1384
1408
  const subcommandList = SUBCOMMAND_SPEC.map((s) => s.name).join(" ");
1385
1409
  const topLevelFlags = "--help -h --version -V";
1386
1410
  const cases = SUBCOMMAND_SPEC.map((spec) => {
1387
- const posClause = spec.positional ? ` if [[ $cword -eq 2 ]]; then
1388
- COMPREPLY=( $(compgen -W "${spec.positional.join(" ")} ${spec.flags.join(" ")}" -- "$cur") )
1411
+ const indexedPositionals = (spec.positional ?? []).map((p, i) => ({ value: p, index: i })).filter(({ value }) => !isPlaceholder(value));
1412
+ const posClauses = indexedPositionals.map(
1413
+ ({ value, index }) => ` if [[ $cword -eq $((${index} + 2)) ]]; then
1414
+ COMPREPLY=( $(compgen -W "${value}" -- "$cur") )
1389
1415
  return 0
1390
- fi` : "";
1416
+ fi`
1417
+ );
1418
+ const parts = [
1419
+ ...posClauses,
1420
+ ` COMPREPLY=( $(compgen -W "${spec.flags.join(" ")}" -- "$cur") )`,
1421
+ " return 0"
1422
+ ].filter((p) => p !== "");
1391
1423
  return ` ${spec.name})
1392
- ${posClause}
1393
- COMPREPLY=( $(compgen -W "${spec.flags.join(" ")}" -- "$cur") )
1394
- return 0
1424
+ ${parts.join("\n")}
1395
1425
  ;;`;
1396
1426
  }).join("\n");
1397
1427
  return `# bash completion for yaw-mcp \u2014 generated by \`yaw-mcp completion bash\`
@@ -1418,8 +1448,10 @@ function renderZsh() {
1418
1448
  const subcommandList = SUBCOMMAND_SPEC.map((s) => ` '${s.name}:${s.description}'`).join("\n");
1419
1449
  const argsCases = SUBCOMMAND_SPEC.map((spec) => {
1420
1450
  const lines = [` ${spec.name})`];
1421
- if (spec.positional) {
1422
- lines.push(` _arguments '1: :(${spec.positional.join(" ")})' '*: :(${spec.flags.join(" ")})'`);
1451
+ const indexedPositionals = (spec.positional ?? []).map((p, i) => ({ value: p, index: i })).filter(({ value }) => !isPlaceholder(value));
1452
+ if (indexedPositionals.length > 0) {
1453
+ const posArgs = indexedPositionals.map(({ value, index }) => `'${index + 1}: :(${value})'`).join(" ");
1454
+ lines.push(` _arguments ${posArgs} '*: :(${spec.flags.join(" ")})'`);
1423
1455
  } else {
1424
1456
  lines.push(` _arguments '*: :(${spec.flags.join(" ")})'`);
1425
1457
  }
@@ -1463,9 +1495,13 @@ complete -c yaw-mcp -f`;
1463
1495
  const flagLines = [];
1464
1496
  for (const spec of SUBCOMMAND_SPEC) {
1465
1497
  if (spec.positional) {
1466
- for (const p of spec.positional) {
1467
- positionalLines.push(`complete -c yaw-mcp -n "__fish_seen_subcommand_from ${spec.name}" -a ${p}`);
1468
- }
1498
+ spec.positional.forEach((p, i) => {
1499
+ if (isPlaceholder(p)) return;
1500
+ const expectedCount = i + 2;
1501
+ positionalLines.push(
1502
+ `complete -c yaw-mcp -n "__fish_seen_subcommand_from ${spec.name}; and test (count (commandline -opc)) -eq ${expectedCount}" -a ${p}`
1503
+ );
1504
+ });
1469
1505
  }
1470
1506
  for (const f of spec.flags) {
1471
1507
  if (!f.startsWith("--")) continue;
@@ -1478,12 +1514,13 @@ complete -c yaw-mcp -f`;
1478
1514
  function renderPowershell() {
1479
1515
  const subcommandNames = SUBCOMMAND_SPEC.map((s) => `'${s.name}'`).join(", ");
1480
1516
  const caseBranches = SUBCOMMAND_SPEC.map((spec) => {
1481
- const positional = spec.positional ? spec.positional.map((p) => `'${p}'`).join(", ") : "";
1517
+ const indexedPositionals = (spec.positional ?? []).map((p, i) => ({ value: p, index: i })).filter(({ value }) => !isPlaceholder(value));
1482
1518
  const flags = spec.flags.map((f) => `'${f}'`).join(", ");
1483
- const positionalLine = positional ? ` $completions += @(${positional})
1519
+ const positionalLines = indexedPositionals.map(({ value, index }) => ` if ($tokens.Count -eq ${index + 2}) { $completions += @('${value}') }`).join("\n");
1520
+ const positionalBlock = positionalLines ? `${positionalLines}
1484
1521
  ` : "";
1485
1522
  return ` '${spec.name}' {
1486
- ${positionalLine} $completions += @(${flags})
1523
+ ${positionalBlock} $completions += @(${flags})
1487
1524
  }`;
1488
1525
  }).join("\n");
1489
1526
  return `# PowerShell completion for yaw-mcp \u2014 generated by \`yaw-mcp completion powershell\`
@@ -1694,6 +1731,19 @@ var token = "";
1694
1731
  var lastFailure = null;
1695
1732
  var lastLoggedConnectStatus = null;
1696
1733
  var lastLoggedDispatchStatus = null;
1734
+ var warnedInsecureBearerSkipConnect = false;
1735
+ var warnedInsecureBearerSkipDispatch = false;
1736
+ function shouldSendBearer(targetUrl) {
1737
+ let parsed;
1738
+ try {
1739
+ parsed = new URL(targetUrl);
1740
+ } catch {
1741
+ return false;
1742
+ }
1743
+ if (parsed.protocol === "https:") return true;
1744
+ if (parsed.protocol === "http:" && isLoopbackHost(parsed.hostname)) return true;
1745
+ return false;
1746
+ }
1697
1747
  function getLastAnalyticsFailure() {
1698
1748
  return lastFailure;
1699
1749
  }
@@ -1746,12 +1796,20 @@ async function flush() {
1746
1796
  const events = buffer.splice(0, FLUSH_SIZE);
1747
1797
  const url = `${apiUrl.replace(/\/$/, "")}/api/connect/analytics`;
1748
1798
  try {
1799
+ const headers = { "Content-Type": "application/json" };
1800
+ if (shouldSendBearer(url)) {
1801
+ headers.Authorization = `Bearer ${token}`;
1802
+ } else if (!warnedInsecureBearerSkipConnect) {
1803
+ log(
1804
+ "warn",
1805
+ "Analytics URL is not https and not loopback; sending without Authorization header to avoid leaking the bearer token",
1806
+ { url }
1807
+ );
1808
+ warnedInsecureBearerSkipConnect = true;
1809
+ }
1749
1810
  const res = await request3(url, {
1750
1811
  method: "POST",
1751
- headers: {
1752
- Authorization: `Bearer ${token}`,
1753
- "Content-Type": "application/json"
1754
- },
1812
+ headers,
1755
1813
  body: JSON.stringify({ events }),
1756
1814
  headersTimeout: 1e4,
1757
1815
  bodyTimeout: 1e4
@@ -1788,17 +1846,25 @@ async function flushDispatch() {
1788
1846
  const events = dispatchBuffer.splice(0, FLUSH_SIZE);
1789
1847
  const url = `${apiUrl.replace(/\/$/, "")}/api/connect/dispatch-events`;
1790
1848
  try {
1849
+ const headers = { "Content-Type": "application/json" };
1850
+ if (shouldSendBearer(url)) {
1851
+ headers.Authorization = `Bearer ${token}`;
1852
+ } else if (!warnedInsecureBearerSkipDispatch) {
1853
+ log(
1854
+ "warn",
1855
+ "Analytics URL is not https and not loopback; sending without Authorization header to avoid leaking the bearer token",
1856
+ { url }
1857
+ );
1858
+ warnedInsecureBearerSkipDispatch = true;
1859
+ }
1791
1860
  const res = await request3(url, {
1792
1861
  method: "POST",
1793
- headers: {
1794
- Authorization: `Bearer ${token}`,
1795
- "Content-Type": "application/json"
1796
- },
1862
+ headers,
1797
1863
  body: JSON.stringify({ events }),
1798
1864
  headersTimeout: 1e4,
1799
1865
  bodyTimeout: 1e4
1800
1866
  });
1801
- if (res.statusCode >= 400 && res.statusCode !== 204) {
1867
+ if (res.statusCode >= 400) {
1802
1868
  const retryable = res.statusCode >= 500 || res.statusCode === 408 || res.statusCode === 429;
1803
1869
  if (retryable) {
1804
1870
  const room = MAX_BUFFER - dispatchBuffer.length;
@@ -1812,7 +1878,7 @@ async function flushDispatch() {
1812
1878
  lastLoggedDispatchStatus = res.statusCode;
1813
1879
  }
1814
1880
  lastFailure = { statusCode: res.statusCode, url, at: Date.now() };
1815
- } else if (res.statusCode < 400) {
1881
+ } else {
1816
1882
  lastFailure = null;
1817
1883
  lastLoggedDispatchStatus = null;
1818
1884
  }
@@ -1830,6 +1896,8 @@ function initAnalytics(url, tok) {
1830
1896
  token = tok;
1831
1897
  lastLoggedConnectStatus = null;
1832
1898
  lastLoggedDispatchStatus = null;
1899
+ warnedInsecureBearerSkipConnect = false;
1900
+ warnedInsecureBearerSkipDispatch = false;
1833
1901
  teamAnalyticsDisabled = false;
1834
1902
  flushTimer = setInterval(() => {
1835
1903
  flush().catch(() => {
@@ -2296,9 +2364,12 @@ function errorMessage(err) {
2296
2364
  import { request as request4 } from "undici";
2297
2365
  var apiUrl2 = "";
2298
2366
  var token2 = "";
2299
- var lastFailure2 = null;
2300
- function getLastReportFailure() {
2301
- return lastFailure2;
2367
+ var lastFailureByServer = /* @__PURE__ */ new Map();
2368
+ function getLastReportFailure(serverId) {
2369
+ if (serverId !== void 0) return lastFailureByServer.get(serverId) ?? null;
2370
+ const entries = [...lastFailureByServer.values()];
2371
+ if (entries.length === 0) return null;
2372
+ return entries.reduce((a, b) => a.at >= b.at ? a : b);
2302
2373
  }
2303
2374
  function initToolReport(url, tok) {
2304
2375
  apiUrl2 = url;
@@ -2306,7 +2377,7 @@ function initToolReport(url, tok) {
2306
2377
  }
2307
2378
  async function reportTools(serverId, tools) {
2308
2379
  if (!apiUrl2 || !token2 || !serverId) return;
2309
- const url = `${apiUrl2.replace(/\/$/, "")}/api/connect/servers/${serverId}/tools`;
2380
+ const url = `${apiUrl2.replace(/\/$/, "")}/api/connect/servers/${encodeURIComponent(serverId)}/tools`;
2310
2381
  try {
2311
2382
  const res = await request4(url, {
2312
2383
  method: "POST",
@@ -2322,12 +2393,13 @@ async function reportTools(serverId, tools) {
2322
2393
  });
2323
2394
  if (res.statusCode >= 400 && res.statusCode !== 404) {
2324
2395
  log("warn", "Tool report failed", { serverId, status: res.statusCode });
2325
- lastFailure2 = { statusCode: res.statusCode, url, at: Date.now() };
2326
- } else if (res.statusCode < 400) {
2327
- lastFailure2 = null;
2396
+ lastFailureByServer.set(serverId, { statusCode: res.statusCode, url, at: Date.now() });
2397
+ } else {
2398
+ lastFailureByServer.delete(serverId);
2328
2399
  }
2329
2400
  } catch (err) {
2330
2401
  log("warn", "Tool report error", { serverId, error: err?.message });
2402
+ lastFailureByServer.set(serverId, { statusCode: 0, url, at: Date.now() });
2331
2403
  }
2332
2404
  }
2333
2405
 
@@ -2367,6 +2439,9 @@ function tokenizeCommand(cmd) {
2367
2439
  has = true;
2368
2440
  }
2369
2441
  }
2442
+ if (quote !== null) {
2443
+ throw new Error(`Unbalanced quote in command: ${cmd}`);
2444
+ }
2370
2445
  if (has) out.push(cur);
2371
2446
  return out;
2372
2447
  }
@@ -3912,7 +3987,7 @@ async function runUpgrade(opts = {}) {
3912
3987
  return { exitCode: 3, lines };
3913
3988
  }
3914
3989
  function readCurrentVersion() {
3915
- return true ? "0.64.1" : "dev";
3990
+ return true ? "0.64.2" : "dev";
3916
3991
  }
3917
3992
 
3918
3993
  // src/usage-hints.ts
@@ -3974,7 +4049,7 @@ function selectFlakyNamespaces(entries, limit) {
3974
4049
  }
3975
4050
 
3976
4051
  // src/doctor-cmd.ts
3977
- var VERSION = true ? "0.64.1" : "dev";
4052
+ var VERSION = true ? "0.64.2" : "dev";
3978
4053
  function isPersistenceDisabled(env) {
3979
4054
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
3980
4055
  return raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
@@ -6447,7 +6522,7 @@ function defaultSpawn2(cmd, args) {
6447
6522
  async function maybeAutoUpgrade(deps = {}) {
6448
6523
  const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
6449
6524
  if (optOut === "0" || optOut?.toLowerCase() === "false") return;
6450
- const current = deps.currentVersion ?? (true ? "0.64.1" : "dev");
6525
+ const current = deps.currentVersion ?? (true ? "0.64.2" : "dev");
6451
6526
  if (current === "dev") return;
6452
6527
  const method = (deps.isSeaImpl ? await deps.isSeaImpl() : await detectSea()) ? "binary" : detectInstallMethod(deps.argvPath ?? process.argv[1]);
6453
6528
  const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
@@ -6957,18 +7032,18 @@ import { readFile as readFile10 } from "fs/promises";
6957
7032
  var GUIDE_READ_TIMEOUT_MS = 1e3;
6958
7033
  async function readGuide(path5, scope) {
6959
7034
  let raw;
7035
+ const ac = new AbortController();
7036
+ const timer = setTimeout(() => ac.abort(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS);
6960
7037
  try {
6961
- raw = await Promise.race([
6962
- readFile10(path5, "utf8"),
6963
- new Promise(
6964
- (_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
6965
- )
6966
- ]);
7038
+ raw = await readFile10(path5, { encoding: "utf8", signal: ac.signal });
6967
7039
  } catch (err) {
6968
- if (err instanceof Error && err.message === "guide read timeout") {
7040
+ const isTimeout = err instanceof Error && err.code === "ABORT_ERR";
7041
+ if (isTimeout) {
6969
7042
  log("warn", "Guide read timed out", { path: path5 });
6970
7043
  }
6971
7044
  return null;
7045
+ } finally {
7046
+ clearTimeout(timer);
6972
7047
  }
6973
7048
  const content = raw.trim();
6974
7049
  if (content.length === 0) {
@@ -7080,7 +7155,7 @@ function initHeartbeat(url, tok) {
7080
7155
  lastLoggedErrorMessage = null;
7081
7156
  warnedInsecureBearerSkip = false;
7082
7157
  }
7083
- function shouldSendBearer(targetUrl) {
7158
+ function shouldSendBearer2(targetUrl) {
7084
7159
  let parsed;
7085
7160
  try {
7086
7161
  parsed = new URL(targetUrl);
@@ -7106,7 +7181,7 @@ async function reportHeartbeat(clientName, clientVersion, isRefresh = false) {
7106
7181
  const headers = {
7107
7182
  "Content-Type": "application/json"
7108
7183
  };
7109
- if (shouldSendBearer(fullUrl)) {
7184
+ if (shouldSendBearer2(fullUrl)) {
7110
7185
  headers.Authorization = `Bearer ${token3}`;
7111
7186
  }
7112
7187
  const res = await request6(fullUrl, {
@@ -8139,7 +8214,8 @@ function pruneJson(value) {
8139
8214
  }
8140
8215
 
8141
8216
  // src/read-tool.ts
8142
- function normalizeToolName(namespace, raw) {
8217
+ function normalizeToolName(namespace, raw, tools) {
8218
+ if (tools?.some((t) => t.name === raw)) return raw;
8143
8219
  const prefix = `${namespace}_`;
8144
8220
  if (raw.startsWith(prefix) && raw.length > prefix.length) return raw.slice(prefix.length);
8145
8221
  return raw;
@@ -9208,7 +9284,7 @@ function categorizeSpawnError(err) {
9208
9284
  }
9209
9285
  async function connectToUpstream(config, onDisconnect, onListChanged) {
9210
9286
  const client = new Client(
9211
- { name: "yaw-mcp", version: true ? "0.64.1" : "dev" },
9287
+ { name: "yaw-mcp", version: true ? "0.64.2" : "dev" },
9212
9288
  { capabilities: {} }
9213
9289
  );
9214
9290
  let transport;
@@ -9279,7 +9355,7 @@ async function connectToUpstream(config, onDisconnect, onListChanged) {
9279
9355
  message = `Server "${config.namespace}" started but didn't complete the MCP handshake within ${connectTimeoutMs / 1e3}s.${trimmedStderr ? ` stderr tail: ${redactSecretsInOutput(trimmedStderr, resolvedServerEnv).slice(-500)}` : ""}`;
9280
9356
  } else if (trimmedStderr.length > 0) {
9281
9357
  category = "install_failure";
9282
- const safe = redactSecretsInOutput(trimmedStderr, config.env ?? {});
9358
+ const safe = redactSecretsInOutput(trimmedStderr, resolvedServerEnv);
9283
9359
  message = `Server "${config.namespace}" failed to start. stderr: ${safe.slice(-500)}`;
9284
9360
  } else {
9285
9361
  category = categorizeSpawnError(err);
@@ -9292,7 +9368,7 @@ async function connectToUpstream(config, onDisconnect, onListChanged) {
9292
9368
  if (config.id) {
9293
9369
  message = `${message} \u2192 Edit at https://yaw.sh/mcp/dashboard/connect#server-${config.id}`;
9294
9370
  }
9295
- const redactedTail = trimmedStderr ? redactSecretsInOutput(trimmedStderr, config.env ?? {}) : void 0;
9371
+ const redactedTail = trimmedStderr ? redactSecretsInOutput(trimmedStderr, resolvedServerEnv) : void 0;
9296
9372
  throw new ActivationError(message, category, redactedTail, err);
9297
9373
  }
9298
9374
  log("info", "Connected to upstream", { name: config.name, namespace: config.namespace, type: config.type });
@@ -9540,7 +9616,7 @@ var ConnectServer = class _ConnectServer {
9540
9616
  this.apiUrl = apiUrl5;
9541
9617
  this.token = token5;
9542
9618
  this.server = new Server(
9543
- { name: "yaw-mcp", version: true ? "0.64.1" : "dev" },
9619
+ { name: "yaw-mcp", version: true ? "0.64.2" : "dev" },
9544
9620
  {
9545
9621
  capabilities: {
9546
9622
  tools: { listChanged: true },
@@ -11471,9 +11547,9 @@ Use mcp_connect_discover to see imported servers.`
11471
11547
  isError: true
11472
11548
  };
11473
11549
  }
11474
- const toolName = normalizeToolName(serverArg, toolArg);
11475
11550
  const existing = this.connections.get(serverArg);
11476
11551
  if (existing && existing.status === "connected") {
11552
+ const toolName = normalizeToolName(serverArg, toolArg, existing.tools);
11477
11553
  const tool = findTool(existing.tools, toolName);
11478
11554
  if (!tool) {
11479
11555
  return {
@@ -11509,6 +11585,7 @@ Use mcp_connect_discover to see imported servers.`
11509
11585
  };
11510
11586
  }
11511
11587
  try {
11588
+ const toolName = normalizeToolName(serverArg, toolArg, transient.tools);
11512
11589
  const tool = findTool(transient.tools, toolName);
11513
11590
  if (!tool) {
11514
11591
  return {
@@ -11974,9 +12051,10 @@ async function runServersCommand(opts = {}) {
11974
12051
  printErr("yaw-mcp servers: backend returned no data (unexpected 304).");
11975
12052
  return { exitCode: 2, lines };
11976
12053
  }
11977
- const filtered = opts.filter ? {
12054
+ const filterStr = opts.filter;
12055
+ const filtered = filterStr !== void 0 ? {
11978
12056
  ...backend,
11979
- servers: backend.servers.filter((s) => s.namespace.toLowerCase().includes(opts.filter.toLowerCase()))
12057
+ servers: backend.servers.filter((s) => s.namespace.toLowerCase().includes(filterStr.toLowerCase()))
11980
12058
  } : backend;
11981
12059
  const gradesReader = opts.gradesReader ?? readGradesCache;
11982
12060
  const grades = await gradesReader(opts.home).catch(() => ({}));
@@ -11991,12 +12069,12 @@ async function runServersCommand(opts = {}) {
11991
12069
  const payload = {
11992
12070
  ...merged,
11993
12071
  filter: opts.filter ?? null,
11994
- filterMatched: opts.filter ? merged.servers.length > 0 : null
12072
+ filterMatched: opts.filter !== void 0 ? merged.servers.length > 0 : null
11995
12073
  };
11996
12074
  print(JSON.stringify(payload, null, 2));
11997
12075
  return { exitCode: 0, lines };
11998
12076
  }
11999
- if (opts.filter && filtered.servers.length === 0) {
12077
+ if (opts.filter !== void 0 && filtered.servers.length === 0) {
12000
12078
  print(`No servers match "${opts.filter}". Run \`yaw-mcp servers\` to see the full list.`);
12001
12079
  return { exitCode: 0, lines };
12002
12080
  }
@@ -12166,6 +12244,11 @@ async function runSetActive(opts, io = { out: (s) => process.stdout.write(s), er
12166
12244
  mcp_bundles: { lastPulledVersion: putRes.version }
12167
12245
  }).catch(() => {
12168
12246
  });
12247
+ } else {
12248
+ io.err(
12249
+ `yaw-mcp set-active: putRes.version was not a number (got ${JSON.stringify(putRes.version)}); local sync-state not updated
12250
+ `
12251
+ );
12169
12252
  }
12170
12253
  return done(io, opts.json, namespace, active, true);
12171
12254
  } catch (e) {
@@ -12416,7 +12499,15 @@ function suggestSubcommand(input, limit = 3) {
12416
12499
  }
12417
12500
  function suggestFlag(input, limit = 2) {
12418
12501
  if (input.length <= 2) return [];
12419
- return closestNames(input, FLAG_ALIASES, limit);
12502
+ const q = input.toLowerCase();
12503
+ const hits = [];
12504
+ for (const alias of FLAG_ALIASES) {
12505
+ if (alias.toLowerCase() === q) continue;
12506
+ const d = levenshtein(q, alias.toLowerCase());
12507
+ if (d <= 2) hits.push({ name: alias, d });
12508
+ }
12509
+ hits.sort((a, b) => a.d - b.d || a.name.localeCompare(b.name));
12510
+ return hits.slice(0, limit).map((h) => h.name);
12420
12511
  }
12421
12512
 
12422
12513
  // src/sync-cmd.ts
@@ -12685,10 +12776,11 @@ function handleSyncError(err, opts, io) {
12685
12776
  return { exitCode: 1 };
12686
12777
  }
12687
12778
  if (err instanceof TeamSyncAuthError) {
12688
- if (opts.json)
12689
- io.err(`${JSON.stringify({ ok: false, error: "Session expired or revoked. Run `yaw-mcp login` again." })}
12779
+ const authMsg = "Session expired or revoked. Run `yaw-mcp login --key <license-key>` again.";
12780
+ if (opts.json) io.err(`${JSON.stringify({ ok: false, error: authMsg })}
12781
+ `);
12782
+ else io.err(`yaw-mcp sync: ${authMsg}
12690
12783
  `);
12691
- else io.err("yaw-mcp sync: session expired or revoked. Run `yaw-mcp login --key <license-key>` again.\n");
12692
12784
  return { exitCode: 1 };
12693
12785
  }
12694
12786
  if (err instanceof TeamSyncForbiddenError) {
@@ -13100,7 +13192,7 @@ if (subcommand === "compliance") {
13100
13192
  `);
13101
13193
  process.exit(0);
13102
13194
  } else if (subcommand === "--version" || subcommand === "-V") {
13103
- process.stdout.write(`yaw-mcp ${true ? "0.64.1" : "dev"}
13195
+ process.stdout.write(`yaw-mcp ${true ? "0.64.2" : "dev"}
13104
13196
  `);
13105
13197
  process.exit(0);
13106
13198
  } else if (subcommand && !subcommand.startsWith("-")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcp",
3
- "version": "0.64.1",
3
+ "version": "0.64.2",
4
4
  "mcpName": "io.github.YawLabs/mcp",
5
5
  "description": "Yaw MCP -- MCP servers, managed. Free to run locally; Yaw Team adds cross-machine sync.",
6
6
  "license": "UNLICENSED",