@yawlabs/mcp 0.63.0 → 0.63.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  All notable changes to `@yawlabs/mcp` (formerly `@yawlabs/mcph`) are documented here. This project uses [semantic versioning](https://semver.org) and a script-gated release flow: `./release.sh <version>` runs lint + tests + build, bumps, tags, publishes to npm, and creates the GitHub release.
4
4
 
5
+ ## 0.63.2 -- release pipeline: publish npm from CI
6
+
7
+ No changes to the package runtime or CLI -- this release exists to exercise the
8
+ new CI-on-tag-push publish flow end to end. The published artifact is identical
9
+ to 0.63.1 aside from the version bump.
10
+
11
+ - **npm is now published from CI, not the workstation.** A new `publish-npm` job
12
+ in `release.yml` publishes `@yawlabs/mcp` on every `v*` tag using the org
13
+ `NPM_TOKEN` + `--provenance` (the repo and package are public), gated on the
14
+ binary build so npm and the GitHub Release stay in lockstep. It is idempotent:
15
+ a version already live is a clean skip, and an `EPUBLISHCONFLICT` from
16
+ registry read-replica lag is treated as success. `publish-registry` now
17
+ `needs: publish-npm`, so the MCP-registry verify can no longer race ahead of
18
+ the npm publish. `release.sh`'s hand-off detection was tightened to key on a
19
+ real `npm publish` / `NODE_AUTH_TOKEN` signal instead of the registry job's
20
+ `id-token: write` (the false positive that wedged the 0.63.0/0.63.1 runs).
21
+ - **Registry job hardening.** `mcp-publisher` is pinned to a tagged release and
22
+ verified against its published sha256 before execution (was an unpinned
23
+ `curl .../latest | tar`), and the job pins its Node toolchain via `setup-node`.
24
+
25
+ ## 0.63.1 -- CLI follow-ups: wire dead --dry-run/--stdin flags, fix completion drift, dedup probes
26
+
27
+ Patch-level follow-ups on the 0.63.0 CLI hardening pass. All fixes; no behavior changes for callers who weren't already hitting the dead-flag bugs.
28
+
29
+ - **`sync push --dry-run`** now short-circuits before any remote mutation. The flag was parsed but never checked by `syncPush`, so a "dry-run" PUT actually mutated `mcp_bundles`. Preview now prints server count and exits 0 without calling `putResource`.
30
+ - **`secrets set --stdin`** now reads raw multi-line stdin even on a TTY. The flag was parsed but never consumed by `runSecrets`, so it silently fell through to the line-buffered echo-off prompt regardless. `--stdin` now forces the raw read path as documented.
31
+ - **`install <client> --dry-run`** now bypasses the collision gate. With an existing yaw-mcp entry + non-TTY stdin, the gate refused before the dry-run preview block ever ran -- so the "...or --dry-run to preview" hint was unreachable. The decision chain now treats `dryRun` like `force` for the overwrite-vs-skip choice.
32
+ - **Completion `SUBCOMMAND_SPEC` drift fixed.** `sync` no longer advertises a phantom `--key` (replaced with the real `--dry-run`); `secrets` no longer advertises a phantom `--key` (replaced with the real `--force`).
33
+ - **`try-cmd` telemetry POSTs are now awaited.** Three fire-and-forget `postEvent(...).catch(() => undefined)` calls (try / cleanup / expiry-gc) could be killed by `process.exit` before the request landed. Now awaited so the analytics event reliably reaches the backend before exit.
34
+ - **`doctor` `state.json` double-read eliminated.** `peekStateFile` hoisted to the caller and the result threaded into `renderStateSection`, removing the redundant disk read inside the section.
35
+ - **`doctor` probe duplication removed.** Extracted `classifyProbeContent` shared by both sync and async client-config probes (~60 lines deduped). Also added the missing `try/catch` around `resolveInstallPath` in `probeClientsAsync` for parity with the sync variant.
36
+ - **`secrets get` / `remove` against a missing vault or missing entry** now short-circuit before the passphrase prompt, avoiding the wasted scrypt key derivation just to say "not found".
37
+ - **`upgrade-cmd`:** removed unreachable `if (!plan.command)` branch -- every install method whose plan reaches that point already returned earlier in the chain.
38
+ - **`index.ts` help text:** corrected the `YAW_MCP_AUTO_UPGRADE` description ("yaw-mcp serve startup" was not a subcommand; now "server startup").
39
+
5
40
  ## 0.63.0 -- CLI hardening: flag parsing, exit-code consistency, secret-file perms, dispatch error handling
6
41
 
7
42
  A full-pass sweep of the `yaw-mcp` subcommand surface. Every change is a fix, a hardening, or additive; there are no breaking changes to the MCP server or the public CLI contract.
File without changes
package/dist/index.js CHANGED
@@ -1136,14 +1136,14 @@ var SUBCOMMAND_SPEC = [
1136
1136
  name: "sync",
1137
1137
  description: "Sync bundles across machines",
1138
1138
  positional: ["push", "pull", "status"],
1139
- flags: ["--key", "--json", "--help"]
1139
+ flags: ["--dry-run", "--json", "--help"]
1140
1140
  },
1141
1141
  { name: "stats", description: "Show usage statistics", flags: ["--limit", "--days", "--json", "--help"] },
1142
1142
  {
1143
1143
  name: "secrets",
1144
1144
  description: "Manage stored secrets",
1145
1145
  positional: ["set", "get", "list", "remove", "lock", "push", "pull"],
1146
- flags: ["--key", "--value", "--stdin", "--json", "--help"]
1146
+ flags: ["--force", "--value", "--stdin", "--json", "--help"]
1147
1147
  },
1148
1148
  {
1149
1149
  name: "set-active",
@@ -2376,7 +2376,7 @@ ${USAGE}`);
2376
2376
  }
2377
2377
  if (existingHasEntry) {
2378
2378
  let decision;
2379
- if (opts.force) decision = "overwrite";
2379
+ if (opts.force || opts.dryRun) decision = "overwrite";
2380
2380
  else if (opts.skip) decision = "skip";
2381
2381
  else if (opts.promptAnswer) decision = opts.promptAnswer;
2382
2382
  else if (opts.io?.isTTY ?? (Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY))) {
@@ -3236,7 +3236,7 @@ async function runTry(opts) {
3236
3236
  }
3237
3237
  const anonId = await loadOrCreateAnonId(home);
3238
3238
  const postEvent = opts.postEvent ?? defaultPostEvent;
3239
- postEvent(baseUrl, { slug, action: "try", anonId }).catch(() => void 0);
3239
+ await postEvent(baseUrl, { slug, action: "try", anonId }).catch(() => void 0);
3240
3240
  const ttlPretty = formatTtl(ttlMs);
3241
3241
  print(`Trial wired: ${server.name} via yaw-mcp-try-${slug} -> ${resolved.absolute}`);
3242
3242
  print(`Expires in ${ttlPretty}; remove sooner with: yaw-mcp try-cleanup ${slug}`);
@@ -3318,7 +3318,7 @@ async function runTryCleanup(opts) {
3318
3318
  }
3319
3319
  const anonId = await loadOrCreateAnonId(home);
3320
3320
  const postEvent = opts.postEvent ?? defaultPostEvent;
3321
- postEvent(baseUrl, { slug, action: "cleanup", anonId }).catch(() => void 0);
3321
+ await postEvent(baseUrl, { slug, action: "cleanup", anonId }).catch(() => void 0);
3322
3322
  print(`Trial for "${slug}" cleaned up.`);
3323
3323
  return { exitCode: 0, written };
3324
3324
  }
@@ -3392,7 +3392,7 @@ async function gcExpiredTrials(opts) {
3392
3392
  }
3393
3393
  }
3394
3394
  await unlink(trialMarkerPath(marker.slug, home));
3395
- postEvent(baseUrl, { slug: marker.slug, action: "expiry-gc", anonId }).catch(() => void 0);
3395
+ await postEvent(baseUrl, { slug: marker.slug, action: "expiry-gc", anonId }).catch(() => void 0);
3396
3396
  cleared++;
3397
3397
  } catch (e) {
3398
3398
  log("debug", "trial gc failed", { slug: marker.slug, error: e.message });
@@ -3652,10 +3652,6 @@ async function runUpgrade(opts = {}) {
3652
3652
  print(` ${BINARY_DOWNLOAD_URL}`);
3653
3653
  return { exitCode: opts.run ? 2 : 1, lines };
3654
3654
  }
3655
- if (!plan.command) {
3656
- print("No upgrade command available for this install method.");
3657
- return { exitCode: 0, lines };
3658
- }
3659
3655
  const installRoot = method === "local-node-modules" ? localInstallRoot(argvPath) : null;
3660
3656
  const runSpec = method === "global-npm" ? { cmd: "npm", args: ["install", "-g", "@yawlabs/mcp@latest"] } : method === "pnpm-global" ? { cmd: "pnpm", args: ["add", "-g", "@yawlabs/mcp@latest"] } : method === "bun-global" ? { cmd: "bun", args: ["add", "-g", "@yawlabs/mcp@latest"] } : method === "local-node-modules" && installRoot !== null ? { cmd: "npm", args: ["install", "@yawlabs/mcp@latest"], cwd: installRoot } : null;
3661
3657
  if (!opts.run) {
@@ -3699,7 +3695,7 @@ async function runUpgrade(opts = {}) {
3699
3695
  return { exitCode: 3, lines };
3700
3696
  }
3701
3697
  function readCurrentVersion() {
3702
- return true ? "0.63.0" : "dev";
3698
+ return true ? "0.63.2" : "dev";
3703
3699
  }
3704
3700
 
3705
3701
  // src/usage-hints.ts
@@ -3761,7 +3757,7 @@ function selectFlakyNamespaces(entries, limit) {
3761
3757
  }
3762
3758
 
3763
3759
  // src/doctor-cmd.ts
3764
- var VERSION = true ? "0.63.0" : "dev";
3760
+ var VERSION = true ? "0.63.2" : "dev";
3765
3761
  function isPersistenceDisabled(env) {
3766
3762
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
3767
3763
  return raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
@@ -3804,11 +3800,13 @@ async function runDoctor(opts = {}) {
3804
3800
  renderEnvSection({ env, print });
3805
3801
  const persistenceDisabled = isPersistenceDisabled(env);
3806
3802
  const stateFilePath = join8(userConfigDir(home), STATE_FILENAME);
3807
- const persistedState = persistenceDisabled ? null : await loadState(stateFilePath);
3808
- await renderStateSection({
3803
+ const statePeek = persistenceDisabled ? null : await peekStateFile(stateFilePath);
3804
+ const persistedState = statePeek?.kind === "ok" ? await loadState(stateFilePath) : null;
3805
+ renderStateSection({
3809
3806
  filePath: stateFilePath,
3810
3807
  disabled: persistenceDisabled,
3811
3808
  persisted: persistedState,
3809
+ peek: statePeek,
3812
3810
  print
3813
3811
  });
3814
3812
  renderReliabilitySection({ disabled: persistenceDisabled, persisted: persistedState, print });
@@ -4028,16 +4026,15 @@ function renderEnvSection(opts) {
4028
4026
  }
4029
4027
  print("");
4030
4028
  }
4031
- async function renderStateSection(opts) {
4032
- const { filePath, disabled, persisted, print } = opts;
4029
+ function renderStateSection(opts) {
4030
+ const { filePath, disabled, persisted, peek, print } = opts;
4033
4031
  print("STATE");
4034
- if (disabled) {
4035
- print(" status: disabled via YAW_MCP_DISABLE_PERSISTENCE");
4032
+ if (disabled || !peek) {
4033
+ if (disabled) print(" status: disabled via YAW_MCP_DISABLE_PERSISTENCE");
4036
4034
  print("");
4037
4035
  return;
4038
4036
  }
4039
4037
  print(` path: ${filePath}`);
4040
- const peek = await peekStateFile(filePath);
4041
4038
  if (peek.kind === "malformed") {
4042
4039
  print(" status: corrupt -- file exists but JSON is unparseable");
4043
4040
  print(` fix: \`yaw-mcp reset-learning\` to clear, or open ${filePath} and fix by hand`);
@@ -4196,29 +4193,14 @@ function probeClients(opts) {
4196
4193
  continue;
4197
4194
  }
4198
4195
  const exists3 = existsSync4(resolved.absolute);
4199
- let hasMcpEntry = false;
4200
- let hasLegacyEntry = false;
4201
- let legacyEntryName = null;
4202
- let malformed = false;
4196
+ let classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
4203
4197
  if (exists3) {
4204
4198
  try {
4205
4199
  statSync(resolved.absolute);
4206
4200
  const raw = readFileSync(resolved.absolute, "utf8");
4207
- if (raw.trim().length > 0) {
4208
- const parsed = parseJsonc(raw);
4209
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
4210
- const container = walkContainer(parsed, resolved.containerPath);
4211
- if (container) {
4212
- hasMcpEntry = ENTRY_NAME in container;
4213
- legacyEntryName = findLegacyEntry(container);
4214
- hasLegacyEntry = legacyEntryName !== null;
4215
- }
4216
- } else {
4217
- malformed = true;
4218
- }
4219
- }
4201
+ classified = classifyProbeContent(raw, resolved.containerPath);
4220
4202
  } catch {
4221
- malformed = true;
4203
+ classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
4222
4204
  }
4223
4205
  }
4224
4206
  out.push({
@@ -4226,10 +4208,7 @@ function probeClients(opts) {
4226
4208
  scope: scope.scope,
4227
4209
  path: resolved.absolute,
4228
4210
  exists: exists3,
4229
- hasMcpEntry,
4230
- hasLegacyEntry,
4231
- legacyEntryName,
4232
- malformed,
4211
+ ...classified,
4233
4212
  unavailable: false
4234
4213
  });
4235
4214
  }
@@ -4245,6 +4224,30 @@ function walkContainer(root, path5) {
4245
4224
  if (typeof cur !== "object" || cur === null || Array.isArray(cur)) return null;
4246
4225
  return cur;
4247
4226
  }
4227
+ function classifyProbeContent(raw, containerPath) {
4228
+ if (raw.trim().length === 0) {
4229
+ return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
4230
+ }
4231
+ try {
4232
+ const parsed = parseJsonc(raw);
4233
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4234
+ return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
4235
+ }
4236
+ const container = walkContainer(parsed, containerPath);
4237
+ if (!container) {
4238
+ return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
4239
+ }
4240
+ const legacyEntryName = findLegacyEntry(container);
4241
+ return {
4242
+ hasMcpEntry: ENTRY_NAME in container,
4243
+ hasLegacyEntry: legacyEntryName !== null,
4244
+ legacyEntryName,
4245
+ malformed: false
4246
+ };
4247
+ } catch {
4248
+ return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
4249
+ }
4250
+ }
4248
4251
  async function probeClientsAsync(opts) {
4249
4252
  const result = [];
4250
4253
  for (const target of INSTALL_TARGETS) {
@@ -4264,38 +4267,28 @@ async function probeClientsAsync(opts) {
4264
4267
  continue;
4265
4268
  }
4266
4269
  for (const scope of target.scopes) {
4267
- const resolved = resolveInstallPath({
4268
- clientId: target.clientId,
4269
- scope: scope.scope,
4270
- os: opts.os,
4271
- home: opts.home,
4272
- projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
4273
- claudeConfigDir: opts.claudeConfigDir
4274
- });
4270
+ let resolved;
4271
+ try {
4272
+ resolved = resolveInstallPath({
4273
+ clientId: target.clientId,
4274
+ scope: scope.scope,
4275
+ os: opts.os,
4276
+ home: opts.home,
4277
+ projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
4278
+ claudeConfigDir: opts.claudeConfigDir
4279
+ });
4280
+ } catch {
4281
+ continue;
4282
+ }
4275
4283
  const exists3 = existsSync4(resolved.absolute);
4276
- let hasMcpEntry = false;
4277
- let hasLegacyEntry = false;
4278
- let legacyEntryName = null;
4279
- let malformed = false;
4284
+ let classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
4280
4285
  if (exists3) {
4281
4286
  try {
4282
4287
  await stat3(resolved.absolute);
4283
4288
  const raw = await readFile7(resolved.absolute, "utf8");
4284
- if (raw.trim().length > 0) {
4285
- const parsed = parseJsonc(raw);
4286
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
4287
- const container = walkContainer(parsed, resolved.containerPath);
4288
- if (container) {
4289
- hasMcpEntry = ENTRY_NAME in container;
4290
- legacyEntryName = findLegacyEntry(container);
4291
- hasLegacyEntry = legacyEntryName !== null;
4292
- }
4293
- } else {
4294
- malformed = true;
4295
- }
4296
- }
4289
+ classified = classifyProbeContent(raw, resolved.containerPath);
4297
4290
  } catch {
4298
- malformed = true;
4291
+ classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
4299
4292
  }
4300
4293
  }
4301
4294
  result.push({
@@ -4303,10 +4296,7 @@ async function probeClientsAsync(opts) {
4303
4296
  scope: scope.scope,
4304
4297
  path: resolved.absolute,
4305
4298
  exists: exists3,
4306
- hasMcpEntry,
4307
- hasLegacyEntry,
4308
- legacyEntryName,
4309
- malformed,
4299
+ ...classified,
4310
4300
  unavailable: false
4311
4301
  });
4312
4302
  }
@@ -5736,11 +5726,11 @@ function readPassphraseFromTTY(stdin, stdout, prompt = "Vault passphrase: ") {
5736
5726
  stdin.on("data", onData);
5737
5727
  });
5738
5728
  }
5739
- async function readStdinValue(io) {
5729
+ async function readStdinValue(io, forceRaw) {
5740
5730
  const stdin = io?.stdin ?? process.stdin;
5741
5731
  const stdout = io?.stdout ?? process.stdout;
5742
5732
  const isTTY = stdin.isTTY === true;
5743
- if (isTTY) {
5733
+ if (isTTY && !forceRaw) {
5744
5734
  stdout.write("Secret value: ");
5745
5735
  return readPassphraseFromTTY(stdin, stdout);
5746
5736
  }
@@ -5785,6 +5775,18 @@ async function runSecrets(opts, io = {
5785
5775
  }
5786
5776
  return { exitCode: 0 };
5787
5777
  }
5778
+ if (opts.action === "get" || opts.action === "remove") {
5779
+ const existingVault = await loadVault(path5);
5780
+ if (!existingVault || !(opts.name in existingVault.entries)) {
5781
+ const name = opts.name;
5782
+ const msg = `No secret named "${name}" in the vault.`;
5783
+ if (opts.json) io.err(`${JSON.stringify({ ok: false, error: msg })}
5784
+ `);
5785
+ else io.err(`yaw-mcp secrets: ${msg}
5786
+ `);
5787
+ return { exitCode: 1 };
5788
+ }
5789
+ }
5788
5790
  let vault = await loadVault(path5) ?? newVault();
5789
5791
  const isFresh = !existsSync6(path5);
5790
5792
  const passphrase = await resolvePassphrase(opts);
@@ -5811,7 +5813,7 @@ async function runSecrets(opts, io = {
5811
5813
  const name = opts.name;
5812
5814
  let value;
5813
5815
  if (opts.value !== void 0) value = opts.value;
5814
- else value = await readStdinValue(opts.io);
5816
+ else value = await readStdinValue(opts.io, opts.fromStdin);
5815
5817
  if (!value) {
5816
5818
  const msg = "Secret value cannot be empty.";
5817
5819
  if (opts.json) io.err(`${JSON.stringify({ ok: false, error: msg })}
@@ -6085,7 +6087,7 @@ function defaultSpawn2(cmd, args) {
6085
6087
  async function maybeAutoUpgrade(deps = {}) {
6086
6088
  const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
6087
6089
  if (optOut === "0" || optOut?.toLowerCase() === "false") return;
6088
- const current = deps.currentVersion ?? (true ? "0.63.0" : "dev");
6090
+ const current = deps.currentVersion ?? (true ? "0.63.2" : "dev");
6089
6091
  if (current === "dev") return;
6090
6092
  const method = (deps.isSeaImpl ? await deps.isSeaImpl() : await detectSea()) ? "binary" : detectInstallMethod(deps.argvPath ?? process.argv[1]);
6091
6093
  const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
@@ -8776,7 +8778,7 @@ function categorizeSpawnError(err) {
8776
8778
  }
8777
8779
  async function connectToUpstream(config, onDisconnect, onListChanged) {
8778
8780
  const client = new Client(
8779
- { name: "yaw-mcp", version: true ? "0.63.0" : "dev" },
8781
+ { name: "yaw-mcp", version: true ? "0.63.2" : "dev" },
8780
8782
  { capabilities: {} }
8781
8783
  );
8782
8784
  let transport;
@@ -9103,7 +9105,7 @@ var ConnectServer = class _ConnectServer {
9103
9105
  this.apiUrl = apiUrl5;
9104
9106
  this.token = token5;
9105
9107
  this.server = new Server(
9106
- { name: "yaw-mcp", version: true ? "0.63.0" : "dev" },
9108
+ { name: "yaw-mcp", version: true ? "0.63.2" : "dev" },
9107
9109
  {
9108
9110
  capabilities: {
9109
9111
  tools: { listChanged: true },
@@ -12191,13 +12193,28 @@ async function syncPull(opts, io, home) {
12191
12193
  }
12192
12194
  async function syncPush(opts, io, home) {
12193
12195
  const local = await readLocalBundles(home);
12196
+ const stripped = local.servers.map(stripEnvValues);
12197
+ if (opts.dryRun) {
12198
+ if (opts.json) {
12199
+ io.out(
12200
+ `${JSON.stringify({ ok: true, dryRun: true, serverCount: stripped.length }, null, 2)}
12201
+ `
12202
+ );
12203
+ } else {
12204
+ io.out(
12205
+ `[dry-run] would push ${stripped.length} server${stripped.length === 1 ? "" : "s"} (env values stripped); nothing sent.
12206
+ `
12207
+ );
12208
+ }
12209
+ return { exitCode: 0 };
12210
+ }
12194
12211
  const remote = await getResource(MCP_BUNDLES_RESOURCE, {
12195
12212
  home: opts.home,
12196
12213
  baseUrl: opts.baseUrl
12197
12214
  });
12198
12215
  const remoteServers = remote.data?.servers ?? [];
12199
- const stripped = mergeRemoteActive(local.servers.map(stripEnvValues), remoteServers);
12200
- const payload = { version: 1, servers: stripped };
12216
+ const merged = mergeRemoteActive(stripped, remoteServers);
12217
+ const payload = { version: 1, servers: merged };
12201
12218
  const syncState = await readSyncState(home);
12202
12219
  const lastPulled = syncState.mcp_bundles?.lastPulledVersion;
12203
12220
  const pushVersion = lastPulled ?? remote.version;
@@ -12207,10 +12224,10 @@ async function syncPush(opts, io, home) {
12207
12224
  });
12208
12225
  await writeSyncState(home, { mcp_bundles: { lastPulledVersion: res.version } });
12209
12226
  if (opts.json) {
12210
- io.out(`${JSON.stringify({ ok: true, serverCount: stripped.length, newVersion: res.version }, null, 2)}
12227
+ io.out(`${JSON.stringify({ ok: true, serverCount: merged.length, newVersion: res.version }, null, 2)}
12211
12228
  `);
12212
12229
  } else {
12213
- io.out(`Pushed ${stripped.length} server${stripped.length === 1 ? "" : "s"} -> mcp_bundles v${res.version}.
12230
+ io.out(`Pushed ${merged.length} server${merged.length === 1 ? "" : "s"} -> mcp_bundles v${res.version}.
12214
12231
  `);
12215
12232
  io.out("Env values stripped before upload; use `yaw-mcp secrets push` to sync secrets across machines.\n");
12216
12233
  }
@@ -12614,9 +12631,9 @@ if (subcommand === "compliance") {
12614
12631
  gate (default: a clearly-winning server is
12615
12632
  activated in the same call).
12616
12633
  YAW_MCP_AUTO_UPGRADE Set to \`0\` to disable the background
12617
- self-upgrade check at \`yaw-mcp serve\` startup
12618
- (default: stale global-npm installs are
12619
- upgraded in the background).
12634
+ self-upgrade check at server startup (default:
12635
+ stale global-npm installs are upgraded in the
12636
+ background).
12620
12637
  YAW_MCP_PRUNE_RESPONSES Set to \`0\` to disable response pruning.
12621
12638
  YAW_MCP_DISABLE_PERSISTENCE Disable cross-session learning state.
12622
12639
  YAW_MCP_CATALOG_URL Override the catalog \`add\`/\`try\` resolve slugs
@@ -12640,7 +12657,7 @@ if (subcommand === "compliance") {
12640
12657
  `);
12641
12658
  process.exit(0);
12642
12659
  } else if (subcommand === "--version" || subcommand === "-V") {
12643
- process.stdout.write(`yaw-mcp ${true ? "0.63.0" : "dev"}
12660
+ process.stdout.write(`yaw-mcp ${true ? "0.63.2" : "dev"}
12644
12661
  `);
12645
12662
  process.exit(0);
12646
12663
  } else if (subcommand && !subcommand.startsWith("-")) {
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcp",
3
- "version": "0.63.0",
3
+ "version": "0.63.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",