Skip to content

[v2.0.x] Point imports of mcp.server.fastmcp at the migration guide - #3393

Merged
maxisbey merged 3 commits into
v2.0.xfrom
backport-2.0-fastmcp-import-tombstone
Aug 26, 2026
Merged

[v2.0.x] Point imports of mcp.server.fastmcp at the migration guide#3393
maxisbey merged 3 commits into
v2.0.xfrom
backport-2.0-fastmcp-import-tombstone

Conversation

@maxisbey

Copy link
Copy Markdown
Contributor

Backport of #3388 onto v2.0.0 so it can ship as 2.0.1. v2.0.x was cut at the v2.0.0 tag; this PR is the only thing on top of it.

Three cherry-picks (-x, all applied cleanly):

Motivation and Context

See #3388. A 2.0.1 reaches installs pinned to mcp==2.0.* / <2.1; everyone unpinned gets it from the next release off main.

How Has This Been Tested?

On this branch: ./scripts/test (5585 passed, 100% coverage, strict-no-cover clean), pyright, ruff, scripts/docs/build_config.py (no fastmcp page), and the import by hand. uv-dynamic-versioning resolves the branch as 2.0.1.devN, so a v2.0.1 tag here yields 2.0.1.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Release: gh release create v2.0.1 --target <merge sha on v2.0.x> --latest=false so 2.1.0 stays "Latest". deploy-docs.yml only fires from main, so the docs site is unaffected by this branch. If you'd rather keep #3380 as its own commit on v2.0.x, rebase-merge instead of squash.

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.

The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.

(cherry picked from commit b189452)
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.

(cherry picked from commit 3330346)

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the inline finding, I also checked whether shipping the tombstone as a real module file breaks spec-based introspection — importlib.util.find_spec("mcp.server.fastmcp") now returns a ModuleSpec where it previously returned None, but this is an inherent, low-impact tradeoff of the tombstone approach (find_spec does not execute the module) and was ruled out as a bug.

Extended reasoning...

A finding on docs/migration.md's new claim about except ImportError fallbacks is posted inline, so approval is off the table. Separately, two candidate issues about the stub file's visibility to importlib.util.find_spec were investigated and ruled out: find_spec locating the module without executing it means code probing for the v1 module via spec lookup sees it as present, but that is a known cost of any tombstone module rather than a defect in this change, and the common dual-major pattern (try-import with a ModuleNotFoundError fallback, covered by the new tests in tests/server/test_fastmcp.py) works as intended.

Comment thread docs/migration.md
- `ToolError`, `ResourceError` — from `mcp.server.mcpserver.exceptions`
- `MCPServerError` (renamed from `FastMCPError`) — from `mcp.server.mcpserver.exceptions`

Importing `mcp.server.fastmcp`, or anything below it, raises `ModuleNotFoundError` (newer 2.x releases include a link to this section in its message), so existing `except ImportError` or `except ModuleNotFoundError` fallbacks around the v1 import keep working.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 This new claim is false for the from mcp.server import fastmcp spelling (valid in v1, whose mcp/server/__init__.py imported .fastmcp): CPython's _handle_fromlist swallows a fromlist submodule's ModuleNotFoundError exactly when exc.name matches the dotted path and the module is absent from sys.modules — both true for the shim — so the user gets a plain ImportError: cannot import name 'fastmcp' from 'mcp.server' with no migration pointer, and an except ModuleNotFoundError fallback does NOT catch it (behavior itself is unchanged from base; the new doc sentence and the shim docstring in src/mcp/server/fastmcp.py are what overclaim). Scope the claim to import mcp.server.fastmcp / from mcp.server.fastmcp import ..., or note that only except ImportError covers every spelling.

Extended reasoning...

Path: from mcp.server import fastmcp compiles to import('mcp.server', fromlist=['fastmcp']). mcp.server (v2) has no fastmcp attribute, so _handle_fromlist runs import_('mcp.server.fastmcp'); the shim at src/mcp/server/fastmcp.py:16 raises ModuleNotFoundError(name='mcp.server.fastmcp'), and the failed exec removes the entry from sys.modules. _handle_fromlist's backwards-compat guard — if exc.name == from_ and sys.modules.get(from_, _ERR_MSG_PREFIX) is not None: continue — matches on both conditions (name set to name on purpose, module absent so .get returns the non-None sentinel), so the shim's exception is silently discarded. IMPORT_FROM then raises ImportError('cannot import name fastmcp from mcp.server'), which is not a ModuleNotFoundError and carries no migration pointer. Consequence: a v1-compat guard try: from mcp.server import fastmcp / except ModuleNotFoundError: crashes despite migration.md:670's assurance that such fallbacks 'keep working', and the whole point of the shim (the pointer message) never surfaces for this spelling. Same final exception as base 2

Verification: nit — The mechanism is real and the new doc sentence is false for one valid v1 spelling, but runtime behavior is unchanged from the base branch. from mcp.server import fastmcp (valid in v1, whose mcp/server/__init__.py did from .fastmcp import FastMCP, binding the submodule attribute) compiles to __import__('mcp.server', fromlist=('fastmcp',)). v2's src/mcp/server/__init__.py binds no `f

@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3393.mcp-python-docs.pages.dev
Deployment https://ce54ef17.mcp-python-docs.pages.dev
Commit df20ae0
Triggered by @maxisbey
Updated 2026-08-26 10:24:38 UTC

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/migration.md">

<violation number="1" location="docs/migration.md:670">
P2: Scope this fallback claim to `import mcp.server.fastmcp` and `from mcp.server.fastmcp import ...`, or tell users to catch `ImportError`: `from mcp.server import fastmcp` suppresses the shim's `ModuleNotFoundError` and raises `ImportError` instead.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/migration.md
- `ToolError`, `ResourceError` — from `mcp.server.mcpserver.exceptions`
- `MCPServerError` (renamed from `FastMCPError`) — from `mcp.server.mcpserver.exceptions`

Importing `mcp.server.fastmcp`, or anything below it, raises `ModuleNotFoundError` (newer 2.x releases include a link to this section in its message), so existing `except ImportError` or `except ModuleNotFoundError` fallbacks around the v1 import keep working.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Scope this fallback claim to import mcp.server.fastmcp and from mcp.server.fastmcp import ..., or tell users to catch ImportError: from mcp.server import fastmcp suppresses the shim's ModuleNotFoundError and raises ImportError instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/migration.md, line 670:

<comment>Scope this fallback claim to `import mcp.server.fastmcp` and `from mcp.server.fastmcp import ...`, or tell users to catch `ImportError`: `from mcp.server import fastmcp` suppresses the shim's `ModuleNotFoundError` and raises `ImportError` instead.</comment>

<file context>
@@ -667,6 +667,8 @@ All submodules under `mcp.server.fastmcp.*` are now under `mcp.server.mcpserver.
 - `ToolError`, `ResourceError` — from `mcp.server.mcpserver.exceptions`
 - `MCPServerError` (renamed from `FastMCPError`) — from `mcp.server.mcpserver.exceptions`
 
+Importing `mcp.server.fastmcp`, or anything below it, raises `ModuleNotFoundError` (newer 2.x releases include a link to this section in its message), so existing `except ImportError` or `except ModuleNotFoundError` fallbacks around the v1 import keep working.
+
 ### What is unchanged on `MCPServer`
</file context>
Suggested change
Importing `mcp.server.fastmcp`, or anything below it, raises `ModuleNotFoundError` (newer 2.x releases include a link to this section in its message), so existing `except ImportError` or `except ModuleNotFoundError` fallbacks around the v1 import keep working.
+Importing `mcp.server.fastmcp`, or anything below it, raises `ModuleNotFoundError` (newer 2.x releases include a link to this section in its message), so fallbacks around `import mcp.server.fastmcp` or `from mcp.server.fastmcp import ...` keep working.

@maxisbey
maxisbey merged commit 8b191a4 into v2.0.x Aug 26, 2026
39 checks passed
@maxisbey
maxisbey deleted the backport-2.0-fastmcp-import-tombstone branch August 26, 2026 10:35
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.

1 participant