Skip to main content

The MCP servers stopped starting, and our own install command was the cause

· 2 min read
Anchor maintainers

If python server.py gave you ModuleNotFoundError: No module named 'mcp.server.fastmcp', this was us.

All three Anchor MCP servers — model-fleet, anchor-prompts, and project-orchestrator — opened with:

from mcp.server.fastmcp import FastMCP

MCP Python SDK 2.0 removed that module and renamed FastMCP to MCPServer. Our pyproject.toml files declared mcp[cli]>=1.2.0 with no upper bound, so a fresh install resolved 2.x and every server died at import. Worse, the install command printed in each README and module docstring was the unbounded pip install "mcp[cli]" — we were handing people the command that broke them.

The fix​

The decorator surface (tool, prompt, resource) and run() are unchanged across the rename, so this needed two names, not a rewrite:

try: # SDK 2.x
from mcp.server import MCPServer as MCPServerClass
except ImportError: # SDK 1.x
from mcp.server.fastmcp import FastMCP as MCPServerClass

The dependency is now bounded >=1.2.0,<3. A fresh install gets 2.x and works; an operator pinned to 1.x keeps working. Both were verified by importing all three servers against 1.29.0 and 2.0.0.

Why it took so long to notice​

Two things hid it, and both are worth knowing about independently.

Nothing in CI installed the SDK. No test ever imported a server, so no test could fail.

The tests that should have covered it skipped. They used pytest.importorskip("mcp.server.fastmcp"), and since CI never had the SDK, that skip was permanent rather than situational — a test that never runs looks exactly like a test that passes.

What replaces them​

tests/test_mcp_servers_import.py imports each server against the actually installed SDK, in a subprocess run from outside the repo so the directory shadowing cannot fool it, and asserts the child resolved a real site-packages mcp before trusting the result. It also fails if any server declares an unbounded SDK range again, or reaches for the removed module outside a guarded fallback. requirements-dev.txt now installs the SDK, so CI exercises it.

We kept a separate set of tests that stub the SDK — those cover the delegate gate without needing an install. They are useful and they could never have caught this: a stub satisfies the import no matter how broken the real path is. Both kinds are needed, for different jobs.