You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: run the MCP server on mcp 2.x while keeping 1.x working (#537)
mcp 2.0.0 moved `mcp.server.fastmcp`'s FastMCP to `mcp.server.mcpserver`'s
MCPServer and shipped no compat shim, so server.py's single `from mcp` import
raised and the server exited 1 before registering a tool. #542 mitigated the
three paths that led users to 2.x; this migrates onto it.
server.py imports whichever class is present, preferring 2.x, and
requirements.txt pins mcp[cli]==2.0.0. Supporting both is the point rather
than a hedge: the plugin updates through the marketplace but the venv is
updated by hand, so a hard cutover would have killed the server on every
existing install the moment the plugin updated, with no action by the user.
The migration is far smaller than #537 anticipated, and that was measured
rather than assumed. Every registration site is a bare `@mcp.tool()` with no
kwargs, both SDKs build schemas via `inspect.signature(fn, eval_str=True)`,
and MCPServer takes the same constructor name, `.tool()` decorator and
`.run(transport="stdio")`. All 91 tools produce byte-identical `tools/list`
wire schemas on 1.28.1 and 2.0.0; a real stdio handshake returns the same
protocol version and capabilities on both. The issue's central worry — that
install_error_boundary could keep working while silently changing all 91
generated schemas — does not occur.
- serverInfo.version now reports the plugin version on 2.x. 1.x has no such
parameter and hardcodes the SDK's own version, so it is passed only where
accepted and 1.x behaviour is unchanged.
- A golden file locks the 91 schemas (tests/fixtures/tool_schemas.json). This
is the schema-parity check #537 asked for, and it keeps earning its place:
drop the @functools.wraps out of the error boundary and every tool collapses
to one async_wrapper taking (*args, **kwargs) — handlers keep working, the
suite stays green, clients lose every parameter. That is #443's failure mode
and nothing else sees it. Verified by mutation.
- A `MCP Server Boot (mcp 1.x fallback)` CI job covers the fallback branch,
which every other job misses since they all install requirements.txt. It
boots on the 1.28.1 floor and re-checks the golden, holding the cross-SDK
guarantee.
- The dependabot mcp-major ignore comes out, as its own comment specified.
- Install advice and all four readiness probes accept either SDK line; a probe
naming one module calls a working install broken.
- `_MCPServer` is annotated Any so mypy's verdict does not depend on which SDK
the machine running it has installed.
Verified: full suite 4514 passed on both 1.28.1 and 2.0.0; ruff/bandit/mypy
clean; mypy clean in all three states (no mcp, 1.x, 2.x); e2e mcp_boot_check
passes through the real launcher on both lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6-1Lines changed: 6 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,6 +11,11 @@ This project uses [Conventional Commits](https://conventionalcommits.org/) and [
11
11
- Frontmatter `genre:` on a track is optional and absent by default; nothing changes for albums that do not use it. It is a **musical descriptor only** — the album's `genre` stays the parent directory name, because ~7 handler sites resolve album paths from it, so it has to keep matching the directory. `templates/track.md` carries the key commented out and `reference/state-schema.md` documents it on the track table. Both indexing paths carry it — the full scan and the incremental re-parse — held together by a parity test, since a field written by one path and dropped by the other is the drift [#523](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/523) fixed for `tracks_completed`.
12
12
13
13
### Changed
14
+
-**The MCP server runs on `mcp` 2.x, and still runs on 1.x** ([#537](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/537)) — `mcp` 2.0.0 moved `mcp.server.fastmcp`'s `FastMCP` to `mcp.server.mcpserver`'s `MCPServer` with no compat shim. `server.py` now imports whichever is present, preferring 2.x, and `requirements.txt` pins `mcp[cli]==2.0.0`. Supporting both is the point rather than a hedge: the plugin updates through the marketplace but the venv is updated by hand, so a hard cutover would have killed the server on every existing install the moment the plugin updated, with no action by the user. `check_venv_health` reports the pin drift and people upgrade on their own schedule. The install advice and all four readiness probes accept either line — a probe naming only one module calls a perfectly working install broken, which is the same false verdict in the opposite direction from the one #542 fixed.
15
+
-**The migration is smaller than it looked, and that was measured rather than assumed.** The concern on the issue was that `_shared.install_error_boundary` monkey-patches `mcp.tool` across all 91 tools and could keep working while silently changing every generated schema. It does not: both SDKs build schemas from the handler signature via `inspect.signature(fn, eval_str=True)`, every registration site is a bare `@mcp.tool()` with no keyword arguments, and `MCPServer` takes the same constructor name, `.tool()` decorator and `.run(transport="stdio")`. Dumped side by side, all 91 tools produce byte-identical `tools/list` wire schemas on 1.28.1 and 2.0.0, and a real stdio handshake returns the same protocol version and capabilities on both.
16
+
-**`serverInfo.version` now reports the plugin version on 2.x.** 1.x has no `version` parameter and hardcodes the SDK's own version — telling a client `1.28.1`, which describes the SDK rather than this server — while 2.x would otherwise default it to the empty string. It is passed only where it is accepted, so 1.x behaviour is unchanged.
17
+
-**A golden file locks the 91 generated schemas** (`tests/fixtures/tool_schemas.json`, checked by `tests/unit/state/test_tool_schema_parity.py`). This is the schema-parity check the issue asked for, and it keeps earning its place after the migration: drop the `@functools.wraps` out of the error boundary and every tool collapses to a single `async_wrapper` taking `(*args, **kwargs)` — handlers keep working, the rest of the suite stays green, and clients lose every parameter. That is [#443](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/443)'s failure mode, and nothing else in the suite sees it. Verified by mutation: removing the decorator turns the parity test red. Tool *descriptions* are deliberately outside the golden so ordinary docstring edits do not force a regeneration.
18
+
-**A `MCP Server Boot (mcp 1.x fallback)` CI job covers the fallback branch.** Every other job installs `requirements.txt` and therefore only ever exercises 2.x, which would leave the 1.x path uncovered from the day it was written — the way compat shims rot. The job installs the pinned set, downgrades mcp alone to the 1.28.1 floor, fails loudly if the downgrade did not take, boots the server through the same `mcp-launch` launcher `.mcp.json` uses, and re-checks the schema golden. That last step is what holds the cross-SDK guarantee: the golden is generated on one line and must reproduce byte-for-byte on the other. Linux-only, since the variable under test is the SDK and not the OS.
14
19
-**Dependency bumps that the grouped PR could not deliver: `ruff` 0.16.0 → 0.16.2, `boto3` 1.43.56 → 1.43.69, `playwright` 1.61.0 → 1.62.0** — these three were safe the whole time, but rode in a `pip-all` group PR alongside two upgrades that cannot land, so all three sat unmerged. Split out and verified on their own. `ruff` matters most of the three: it is one of the exactly-pinned gate tools from [#532](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/532), so its verdict changes on unchanged code — 0.16.2 was run against the full tree (`tools/`, `servers/`, `hooks/`, plus the scoped `PLW1514` preview pass) and reports no new findings.
15
20
- **`librosa` 1.0+ is blocked in `.github/dependabot.yml`, on the same Python-floor grounds as `scipy` and `numpy`** — `librosa` 1.0.0 declares `Requires-Python >=3.12` and the plugin supports 3.11, so pip cannot resolve it at all: `No matching distribution found for librosa==1.0.0`. That is worse than a failing test. Every job that installs `requirements.txt` dies at the install step, `pip-audit` included, which is why the group PR carrying it failed 11 checks rather than the 6 that mcp alone accounts for. It joins the existing ignore block, whose comment now also records *why* this class of upgrade is blocked rather than merely which packages are affected. ([#532](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/532)) — `requirements.txt` pinned all 16 runtime deps with `==`, but every entry in `requirements-test.txt` used `>=`, so `ruff`, `mypy` and `bandit` resolved to whatever was newest on PyPI at the moment CI ran. Those three decide the Lint and Security Scan verdicts, and unlike a test runner they change their answer on unchanged code — a new rule or a widened check reddens a commit nobody touched, and re-running an old green build no longer reproduces it. The drift was already visible: the file read `ruff>=0.15.21` while CI had been installing `0.16.0`, which is why Dependabot closed #522 as redundant. `ruff`, `mypy` and `bandit` are now `==` pins (`cache: 'pip'` never mitigated this — it caches wheels, but pip still resolves to newest). The `pytest` stack stays on `>=`: it changes what runs, not what counts as a violation. A parametrized test in `tests/unit/shared/test_pinned_dependencies.py` keeps the three from silently loosening again.
16
21
@@ -22,7 +27,7 @@ This project uses [Conventional Commits](https://conventionalcommits.org/) and [
22
27
- The lexical guard is now a single `_reject_unsafe_segments` helper, and the two truncations of the same layout — `_albums_dir` and `_genre_dir` — call it too. Both interpolated their segments straight in; `_albums_dir` deliberately, on the documented grounds that `artist` is trusted config. That is true, and it is also the same component `_album_dir` guards, so which of the two helpers a caller happened to reach for decided whether the guard applied — the failure mode #529 was opened about. No call site changes behaviour, since both are reached only from config values; a bad value now returns the structured JSON error the MCP boundary produces rather than a wrong path.
23
28
-**`PATH_ESCAPES_ROOT` now reads `Path escapes root directory`** rather than `Resolved path escapes root directory`. #534 made it the message for lexical rejections too, where nothing has been resolved, so the first word described one of the ways to trigger it and misdescribed the rest. User-visible wording only — it surfaces through the MCP error boundary, and nothing branches on the string.
24
29
-**mcp 2.x can no longer reach the plugin through Dependabot, the printed install advice, or the readiness probe** ([#537](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/537)) — `mcp` 2.0.0 restructured `mcp.server.fastmcp`'s `FastMCP` into `mcp.server.mcpserver`'s `MCPServer` and shipped no compat shim, so `server.py`'s single `from mcp` import raises and the server exits 1 before it registers a tool. `requirements.txt` pins `mcp[cli]==1.28.1`, so no correctly-installed user was ever affected — but three unpinned paths led straight to 2.x anyway:
25
-
-**The weekly `pip-all` group PR.** Three of them ([#536](https://github.com/bitwize-music-studio/claude-ai-music-skills/pull/536), [#540](https://github.com/bitwize-music-studio/claude-ai-music-skills/pull/540), [#541](https://github.com/bitwize-music-studio/claude-ai-music-skills/pull/541)) failed Tests and MCP Server Boot on all three runners, and since the group is `patterns: ["*"]`, one un-mergeable major held every unrelated bump hostage with it — #541 was carrying ruff, pypdf, boto3 and playwright updates that had nothing to do with mcp. `.github/dependabot.yml`now ignores mcp majors, scoped to the major so 1.x patches keep flowing. It comes out together with the 2.0 migration, not before.
30
+
-**The weekly `pip-all` group PR.** Three of them ([#536](https://github.com/bitwize-music-studio/claude-ai-music-skills/pull/536), [#540](https://github.com/bitwize-music-studio/claude-ai-music-skills/pull/540), [#541](https://github.com/bitwize-music-studio/claude-ai-music-skills/pull/541)) failed Tests and MCP Server Boot on all three runners, and since the group is `patterns: ["*"]`, one un-mergeable major held every unrelated bump hostage with it — #541 was carrying ruff, pypdf, boto3 and playwright updates that had nothing to do with mcp. `.github/dependabot.yml`ignored mcp majors as a stopgap, scoped to the major so 1.x patches kept flowing. That ignore came back out in the same release, once the 2.0 migration below landed and made the major mergeable.
26
31
-**The install advice printed by the ImportError handler itself.** Five strings across `server.py` and the server README read `mcp[cli]>=1.2.0` or a bare `pipx install mcp`, all of which resolve to 2.0.0 today. Two of them are printed *by the handler that fires when the import fails* — so someone whose server would not start followed the instructions on screen, installed the one version that cannot start, and got the same message back. A setup loop, shown precisely when the user is already stuck. All five are now bounded `<2`, with the floor moved to the version actually pinned and tested rather than the `>=1.2.0` the server has carried since the MCP server first shipped.
27
32
-**The session-start readiness probe.**`python3 -c "import mcp"` succeeds on 2.x — the removed module is the *submodule* — so the gate `CLAUDE.md` says must halt the session reported `✅ MCP ready` on exactly the install where the server was dead, while the server's own stderr said `ERROR: MCP SDK not installed`. All four probes (`CLAUDE.md`, `skills/session-start`, `skills/setup`, `reference/workflows/error-recovery.md`) now import `mcp.server.fastmcp`, which is what `server.py` imports, and report `MCP unusable` rather than `MCP missing` — the remedy is the same `/bitwize-music:setup mcp` either way, but "missing" is wrong about a package that is installed.
- If MCP unusable → **Stop immediately** and suggest: `/bitwize-music:setup mcp` (either the SDK is missing, or mcp 2.x is installed — it dropped `mcp.server.fastmcp`, so bare `import mcp`would report healthy on an install the server cannot boot on)
77
+
- If MCP unusable → **Stop immediately** and suggest: `/bitwize-music:setup mcp` (the SDK is missing or predates 1.28.1). Probe both server modules, never bare `import mcp`: 2.x serves `MCPServer` from `mcp.server.mcpserver` and 1.x serves `FastMCP` from `mcp.server.fastmcp`, and the server accepts either — but a bare `import mcp`succeeds even when neither module is present, reporting healthy on an install the server cannot boot on
78
78
- If config missing → suggest: `/bitwize-music:configure`
79
79
- Don't proceed with session start until setup is complete
Copy file name to clipboardExpand all lines: reference/workflows/error-recovery.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -316,7 +316,13 @@ This document covers edge cases and recovery procedures for common workflow issu
316
316
**Prevention**: Install and upgrade via the marketplace (`claude plugin update bitwize-music`) rather than editing the cached plugin. Keep the venv in sync (session-start venv check / `check_venv_health`).
317
317
318
318
**Recovery Steps**:
319
-
1. If MCP tools are entirely unavailable, the server didn't start — run `/bitwize-music:setup` (or `/bitwize-music:setup mcp`) to detect the Python environment and reinstall dependencies. Quick check: `~/.bitwize-music/venv/bin/python3 -c "import mcp.server.fastmcp"` (macOS/Linux/WSL) or `~/.bitwize-music/venv/Scripts/python.exe -c "import mcp.server.fastmcp"` (Windows; cmd/PowerShell: `%USERPROFILE%\.bitwize-music\venv\Scripts\python.exe`). Probe the submodule, not bare `mcp` — mcp 2.x imports fine but dropped `mcp.server.fastmcp`, so a bare `import mcp` reports healthy on an install the server cannot boot on ([#537](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/537))
319
+
1. If MCP tools are entirely unavailable, the server didn't start — run `/bitwize-music:setup` (or `/bitwize-music:setup mcp`) to detect the Python environment and reinstall dependencies. Quick check (macOS/Linux/WSL; on Windows swap in `~/.bitwize-music/venv/Scripts/python.exe`, or `%USERPROFILE%\.bitwize-music\venv\Scripts\python.exe` for cmd/PowerShell):
Probe both server modules, never bare `mcp`. The server takes either SDK line — 2.x serves `MCPServer` from `mcp.server.mcpserver`, 1.x serves `FastMCP` from `mcp.server.fastmcp` — but a bare `import mcp` succeeds even when neither is present, reporting healthy on an install the server cannot boot on ([#537](https://github.com/bitwize-music-studio/claude-ai-music-skills/issues/537))
320
326
2. If the server runs but skills are missing/ghost, `health_check` will say so — run `claude plugin update bitwize-music` to refresh the plugin cache, then restart the session
321
327
3. If `health_check` reports skills `no_cache`, the plugin isn't installed via the marketplace — reinstall it (or use `--plugin-dir` for local development)
322
328
4. If the venv is stale or missing (`check_venv_health` → `stale`/`no_venv`), run the reported `pip install -r requirements.txt` fix, or `/bitwize-music:setup` to rebuild the venv
0 commit comments