Skip to content

Fix startup break on mcp 2.0.0, bound deps, add fresh-resolution CI - #22

Merged
pragprogrammer merged 3 commits into
mainfrom
fix/mcp-2-migration
Aug 4, 2026
Merged

Fix startup break on mcp 2.0.0, bound deps, add fresh-resolution CI#22
pragprogrammer merged 3 commits into
mainfrom
fix/mcp-2-migration

Conversation

@pragprogrammer

Copy link
Copy Markdown
Owner

@
Fixes #21.

What broke

mcp-ynab declared mcp[cli]>=1.26.0 with no upper bound. The MCP Python SDK
released 2.0.0 on 2026-07-28 and removed mcp.server.fastmcp. Since resolution
happens at install time, the unchanged 1.0.3 wheel started failing at import for
everyone installing fresh, before any of our code ran.

Same wheel, only the resolver date differing:

Install Resolves Result
--exclude-newer 2026-07-27 mcp 1.28.1 starts, 47 tools
today mcp 2.0.0 ModuleNotFoundError: No module named mcp.server.fastmcp

Local dev and CI both missed it because uv.lock pinned mcp 1.26.0, and
uv sync / uv run install from the lock. 1.29.0 and 2.0.0 shipped the same
day and 1.29.0 emits no deprecation warning, so nothing signalled it either.

The fix

FastMCP is MCPServer in 2.x. For this codebase it is a rename: the 47 tool
registrations, @handle_errors, and mcp.run(transport="stdio") are unchanged,
and there is only one import site.

Dependencies now carry upper bounds. httpx was the next one positioned to do
this to us: it is 0.x, so a 0.29 can break by convention, 1.0 prereleases are on
PyPI, and upstream development moved to Pydantic under the httpx2 name.
aiosqlite is deliberately left unbounded since it is never imported directly.
The pydantic floor moves to 2.12.0 to match what mcp 2.x already requires.

Preventing a repeat

scripts/smoke_test.py installs a built wheel into a clean venv with no
lockfile and drives the real entry point through an MCP stdio handshake. It
resolves from the declared constraints, which is the only way to see what a user
actually gets.

  • ci.yml (new): push and PR. The repo previously had no CI except on release.
  • canary.yml (new): weekly fresh resolve against live PyPI, plus a
    lowest-direct leg proving the declared floors are installable. Opens an
    issue on failure. This is the job that should have caught 2.0.0 on 07-28
    rather than a user catching it on 07-31.
  • publish.yml: now gated on the smoke test, not just the locked test run.

Verification

Check Result
uv run pytest 105 passed
Smoke, highest resolution mcp 2.0.0, 47 tools, exit 0
Smoke, lowest-direct pydantic 2.12.0 / sqlalchemy 2.0.41 / aiosqlite 0.21.0, exit 0
Smoke against published 1.0.3 wheel exit 1 with the original traceback
uvx --from dist/*.whl mcp-ynab initializes and serves
Live get_user / list_plans against real API valid JSON, correct shape
Live get_plan with bad id returns {"error": ...} string, not a protocol error

The third row is the one that matters: the new test was checked against the
actually-broken wheel to confirm it fails, not only that it passes on the fix.
@

@
Migrate to mcp SDK 2.x and bound dependencies

mcp 2.0.0 removed mcp.server.fastmcp, so every fresh install since
2026-07-28 died at import. FastMCP is now MCPServer; the 47 tool
decorators and stdio setup are unchanged.

Adds upper bounds to the deps that can repeat this. pydantic floor
moves to 2.12.0 to match what mcp 2.x already requires.

Fixes #21
@
@
Add fresh-resolution smoke test

Installs a built wheel into a clean venv and runs an MCP stdio
handshake against the real entry point. Resolves from pyproject
constraints rather than uv.lock, which is the only way to see what
users actually get.

Checked against the published 1.0.3 wheel: fails with the original
traceback.
@
@
Add CI and weekly dependency canary

The repo had no push or PR CI, only a release workflow, and that
workflow tested against uv.lock. Adds ci.yml (locked tests plus the
smoke test), canary.yml (weekly fresh resolve, opens an issue on
failure), and gates publish on the smoke test.
@
@pragprogrammer
pragprogrammer merged commit 16e9ef2 into main Aug 4, 2026
2 checks passed
@pragprogrammer
pragprogrammer deleted the fix/mcp-2-migration branch August 4, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ModuleNotFoundError: mcp.server.fastmcp with mcp 2.0.0 — unbounded mcp[cli]>=1.26.0 pin

1 participant