---
summary: "`zod` → peerDep (consumers must `bun add zod`). MCPB bundle support (`bundle` script + `lint:packaging`). `field-test`: zsh `status` clash + per-agent helper paths. `multi-server-orchestration` skill + `list-skills`. `format-parity` walks each union branch. `fast-xml-parser` unpinned `^5.8.0`. Dep refresh: `@hono/mcp` `^0.3.0`, vitest `^4.1.7`."
breaking: true
security: false
---

# 0.9.2 — 2026-05-22

Breaking: `zod` moves to `peerDependencies` ([#143](https://github.com/cyanheads/mcp-ts-core/issues/143)) — existing consumers must `bun add zod@^4.4.3` to upgrade. Highlights: MCPB bundle support for one-click Claude Desktop installs, per-client connect-card tabs on the landing page (Codex/Cursor/Gemini join the existing set; defaults to Claude), the `multi-server-orchestration` skill + `list-skills` helper for sub-agent fanouts, and two long-standing `field-test` helper fixes (zsh `status` clash, concurrent-session collisions).

## Added

- **MCPB bundle support for consumer servers** ([#137](https://github.com/cyanheads/mcp-ts-core/issues/137)) — `bundle` template script (`npm run build && npx -y @anthropic-ai/mcpb pack . dist/<NAME>.mcpb`), `templates/manifest.json` scaffold, and `_.mcpbignore` (covers `.mcpregistry_*` and `.claude/` so credential files don't ship in the bundle) all delivered by `bunx @cyanheads/mcp-ts-core init` for one-click `.mcpb` install in Claude Desktop. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected; consumers who skip MCPB can delete `manifest.json` + `.mcpbignore` and `lint:packaging` skips cleanly. New `scripts/lint-packaging.ts` (`bun run lint:packaging`, also wired into `devcheck`) validates env-var alignment between `manifest.json` `mcp_config.env` and `server.json` stdio `environmentVariables[]` — adding an env var now requires updating both. `templates/CLAUDE.md` + `AGENTS.md` add a **Bundling** section with install-badge templates (Claude Desktop, Cursor, VS Code) and the env-var alignment requirement.
- **`multi-server-orchestration` skill** — workflow for parallel sub-agent fanouts across MCP server projects (greenfield, maintenance, release passes). Per-scenario references at `skills/multi-server-orchestration/references/`. Sub-agents spawned via the Agent tool don't inherit the parent's skill registry, so the skill mandates an orient block in every sub-agent prompt that runs the new `list-skills` script.
- **`scripts/list-skills.ts`** — parses YAML frontmatter from project-local `SKILL.md` files and prints an index. Wired into `package.json` `files:` and exposed as `bun run list-skills` in `templates/`.
- **`audit:refresh` npm script** — `rm -f bun.lock && bun install && bun audit`, added to root `package.json` and `templates/package.json`. Use when `bun audit` (via `devcheck`) flags a transitive advisory: Bun's `bun update` is sticky on transitive resolutions, so a stale-lockfile false positive can disguise an already-patched dep. If the advisory survives the refresh, it's real.
- **`GET /robots.txt`** — small crawl directive: `Allow: /`, `Disallow: <mcpHttpEndpointPath>` (the JSON-RPC endpoint only accepts POST and would waste bot traffic on 405s). Mounted only when `landing.enabled` — API-only deployments are unchanged. 24-hour public cache; `X-Content-Type-Options: nosniff`.
- **Landing-page `<head>` SEO meta** — new `<link rel="canonical">`, `<meta property="og:site_name">`, `<meta property="og:locale" content="en_US">`, and a `<meta name="generator" content="<framework.name> <framework.version>">`. Generator meta is gated on `landing.attribution` (opt out of the visible "Built on …" badge also opts out of the machine-readable footprint). Locale is hardcoded since the page body is English-only; lift to a `landing.locale` option if i18n is wired later.

## Changed

- **`zod` promoted `dependencies` → `peerDependencies`** ([#143](https://github.com/cyanheads/mcp-ts-core/issues/143)) — required, not optional (the framework re-exports `z` from the main entry). Fixes TS2883 in consumers building with `declaration: true` (the framework default) without a top-level `zod` — the emitted `.d.ts` referenced `ZodObject` from the framework's nested `node_modules/zod`, which the consumer's `tsc` couldn't name portably. **Migration:** `bun add zod@^4.4.3` (or a matching range); `bunx @cyanheads/mcp-ts-core init` scaffolds it automatically for new projects.
- **`format-parity` lint walks each union branch.** Previously sampled only the first branch of `z.union` / `z.discriminatedUnion`, so a field missing from a later list-vs-detail formatter branch slipped through. The walker now produces one synthetic sample per branch, runs `format()` against each, and dedupes diagnostics by output path. `format-parity-walk-failed` and `format-parity-threw` warnings stay total — the first variant that throws short-circuits the variant loop.
- **Fuzz generators recognize Zod 4 string-format subclasses.** `z.email()`, `z.url()`, and `z.uuid()` in Zod 4 are no longer `instanceof ZodString`. `zodToArbitrary` falls back to a string-typed arbitrary when `_def.type === 'string'`, so fuzz suites stop generating raw integers/booleans against URL- or email-shaped inputs. `schema-format-portability` still flags `z.url()` by default — the linter and the fuzz generator answer different questions.
- **`polish-docs-meta` convention refresh** — Framework badge promoted to its own spotlight row (recolored cyan-300 `67E8F9`) on consumer READMEs; remaining badges grouped across two subsequent rows. README header tagline, `package.json` `description`, and `server.json` `description` switch to **action-first** phrasing — lead with verbs (`"Search projects, manage tasks, track teams via MCP. STDIO or Streamable HTTP."`) instead of `"MCP server for/that …"` framings. `server.json` drops the `via MCP` suffix (registry context already implies MCP). Framework's own README updated to demonstrate.
- **Connect card — per-client tabs, Claude default, refined active styling** ([#136](https://github.com/cyanheads/mcp-ts-core/issues/136)) — landing-page connect card adds dedicated `Codex`, `Cursor`, `Gemini` tabs alongside the existing `Claude`, `STDIO`, `HTTP`, `curl`. Tab order is named-clients-first (alphabetical) then transport/protocol fallbacks; `claude` is the default checked panel — most common visitor conversion path. Active-tab styling: brighter accent gradient (`var(--accent-soft) → var(--accent-softer)`), bolder weight (`600 → 700`), and an inset glow outline (`box-shadow` adapting to light/dark via `color-mix(in oklab, var(--fg), …)`). Per-client commands all target the live HTTP endpoint: `codex mcp add <name> --url <url>` (writes `~/.codex/config.toml`), `gemini mcp add --transport http <name> <url>` (writes `~/.gemini/settings.json`), and a Cursor `mcpServers` JSON block (paste into `~/.cursor/mcp.json` or per-project `.cursor/mcp.json`). Operator overrides via `landing.connectSnippets.{codex,cursor,gemini}` are recognized by `landing-connect-snippets-keys`.
- **HTTP landing page refactor** — pulls toward [#94](https://github.com/cyanheads/mcp-ts-core/issues/94) (not closing). Single ambient accent wash (was two radials), mono lowercase section heads with thin accent bar, card radius shrunk, padding tightened, mutability spine `3px → 2px`, no hover lift, no footer divider, description clamp `3 → 2` lines. Tool-card pills split into mutability (meta strip alongside scope chips) and auxiliary (open-world/task/app, beside the title). Source links moved off the header — tool: actions row; resource/prompt: optional footer.
- **`fast-xml-parser` unpinned to `^5.8.0`** across `peerDependencies` and `devDependencies`; `resolutions` entry dropped; `devcheck.config.json` `outdated.allowlist` cleared. Framework only uses `XMLParser` (never `XMLValidator`) with `processEntities: false, htmlEntities: false` — neither 5.8.0's `XMLValidator` sibling-package extraction notice nor tightened DOCTYPE entity validation can reach our call sites. Reverses the defensive `0.9.1` pin. Refs [cyanheads/pubmed-mcp-server#55](https://github.com/cyanheads/pubmed-mcp-server/issues/55).
- **Skill bumps:**
  - `field-test` `2.4 → 2.5` — see Fixed entries.
  - `maintenance` `2.1 → 2.2` — Step 7 documents the `audit:refresh` triage flow for transitive advisory false positives.
  - `polish-docs-meta` `1.8 → 1.9` — see Changed entry.
  - `release-and-publish` `2.2 → 2.4` — new Step 6 attaches `.mcpb` to GitHub Release (`gh release create --verify-tag --notes-from-tag dist/*.mcpb`); falls back to `gh release upload --clobber` on re-invocation. Stable `releases/latest/download/<name>.mcpb` URL powers the install badge. Old Steps 6/7 renumbered to 7/8 (Docker / report).
- **Runtime dependency bumps:**
  - `@hono/mcp` `^0.2.5 → ^0.3.0` — relaxes default `Accept` header validation to accept `application/json`, `text/event-stream`, or `*/*` individually (broader Gemini CLI / Java SDK / Open WebUI / curl compat). New `strictAcceptHeader: true` opt-in restores strict MCP-spec mode; framework picks up the relaxed default.
  - `@hono/node-server` `^2.0.2 → ^2.0.3`
  - `@modelcontextprotocol/ext-apps` `^1.7.1 → ^1.7.2`
  - `hono` `^4.12.18 → ^4.12.22`
- **Dev dependency bumps:**
  - `@cloudflare/vitest-pool-workers` `^0.16.6 → ^0.16.9`
  - `@cloudflare/workers-types` `^4.20260515.1 → ^4.20260523.1`
  - `@duckdb/node-api` `^1.5.2-r.1 → ^1.5.3-r.1` (tracks DuckDB engine `1.5.2 → 1.5.3` patch)
  - `@supabase/supabase-js` `^2.105.4 → ^2.106.1`
  - `@types/node` `^25.8.0 → ^25.9.1`
  - `@vitest/coverage-istanbul` `4.1.6 → 4.1.7`
  - `@vitest/ui` `4.1.6 → 4.1.7`
  - `openai` `^6.37.0 → ^6.39.0`
  - `vite` `8.0.13 → 8.0.14`
  - `vitest` `^4.1.6 → ^4.1.7`

## Fixed

- **`field-test` zsh `local status` clash** ([#117](https://github.com/cyanheads/mcp-ts-core/issues/117)) — `mcp_init` and `mcp_call` declared `local status`, colliding with zsh's read-only `$status` builtin (alias for `$?`) and breaking the helper on default macOS via the documented `. /tmp/mcp-field-test.sh` sourcing pattern. Renamed to `local code`.
- **`field-test` concurrent same-project sessions collide** ([#144](https://github.com/cyanheads/mcp-ts-core/issues/144), generalizes [#90](https://github.com/cyanheads/mcp-ts-core/issues/90)) — every per-session path (`PREFIX`, `STATE_FILE`, `BUILD_LOG`, `SERVER_LOG`, and the helper script itself at `/tmp/mcp-field-test.sh`) was derived from `$PWD` or hardcoded, so two agents in one project tree raced on all of them. Fix: (1) the agent generates a 10-char alphanumeric ID and writes the helper to `/tmp/<project>-field-test-<ID>.sh`, sourcing that exact path on every later call; (2) the helper itself is now **stateless** — every function takes its IDs as positional args (`mcp_init <url>`, `mcp_call <url> <sid> <method> [params]`, `mcp_log <log>`, `mcp_stop <pid> <log>`), `mcp_start` prints `ready pid=<n> url=<u> port=<n> log=<path>`, and the agent threads them through. Server logs and call scratch use `mktemp` for per-process uniqueness.
- **`devcheck` strips cosmetic `Registry URL must be` noise** emitted by Bun's node-shim when depcheck encounters `cloudflare:*` virtual-module specifiers in Workers tests. The diagnostic never affected the depcheck report — `devcheck.config.json` already lists `cloudflare:*` in `depcheck.ignores` — but the stderr leaked into the run summary.
