Zuplo
On this page
MCP error

Import fails after MCP Python SDK 2.0

The string your client printed
ModuleNotFoundError: No module named 'mcp.server.fastmcp'

Your server code imports mcp.server.fastmcp, but the installed mcp package is 2.x, where that module no longer exists — so the process dies at import, before it speaks any MCP.

MCP specification
2026-07-28

Is this you?

Your client shows a transport error rather than the traceback, so read the installed version and the module layout directly. Together these separate the three things that look identical from the client's side: the mcp package on 2.x under 1.x-era code, a 1.x pin that never took effect, and the separately published fastmcp package, which is not this problem.

TerminalRun this to check — it changes nothing
# 1. Which mcp is installed, and which module path does it have?
python -c "
import importlib.metadata as md, importlib.util as iu
print('mcp', md.version('mcp'))
for name in ('mcp.server.fastmcp', 'mcp.server.mcpserver'):
    print(name, iu.find_spec(name) is not None)
"

# 2. Is the standalone fastmcp distribution involved? It is a different
#    package with a different import root. Not installed is the common
#    answer and rules it out, so catch rather than traceback.
python -c "
import importlib.metadata as md
try:
    print('fastmcp', md.version('fastmcp'))
except md.PackageNotFoundError:
    print('fastmcp not installed')
"

# 3. Which requirement pulled mcp in?
uv tree --invert
This error
mcp 2.0.0 or later with mcp.server.fastmcp False, while your code still imports it. Step 3 then shows the requirement that let it through, and it is usually not your own — a dependency of a dependency declaring mcp>=1.0.0 resolves the same way. A PackageNotFoundError from step 2 means the standalone fastmcp distribution is not installed, which rules it out.
Then the fix is the next section.
Working
Either mcp 1.x with mcp.server.fastmcp True and mcp.server.mcpserver False, which is the 1.x line your imports expect, or mcp 2.x with those two reversed and code that imports MCPServer. Both are working states. In step 3, the requirement that names mcp carries an upper bound.
Then this isn't your problem — try the error reference.
3 other strings clients print for this
  • MCP error -32000: Connection closed
  • Failed to reconnect to plugin:<plugin>:<server>: -32000
  • AttributeError: 'Server' object has no attribute 'list_tools'

Dotted spans such as <url> are placeholders — your client prints your own value there.

Fix it

Decide whether you are staying on 1.x or moving to 2.x; the fix is one line either way. To stay, bound the requirement to mcp>=1.28,<2 and re-resolve, so the lockfile actually moves back. To migrate, change from mcp.server.fastmcp import FastMCP to from mcp.server.mcpserver import MCPServer and rename the constructor call — your decorators keep the same arguments and handler signatures.

Have your agent fix this

Copy a prompt that sends your coding agent to this page to find and apply the right fix for your project.

The cause depends on your platform. Pick yours.

Pin to mcp 1.x

If you need the server starting again and are not ready to change code

  1. 1 Add the upper bound to the requirement itself, not just to the lockfile. The SDK's migration guide states the rule plainly: "If your package depends on mcp, keep a <2 upper bound until you've migrated." The v2.0.0 release notes give mcp>=1.28,<2 as the worked example.
  2. 2 Re-resolve afterwards, because the bound alone does not move an environment that already installed 2.x. Run uv lock and then uv sync, or pip install -e . for a pip project. Confirm you landed on 1.x rather than assuming it.
  3. 3 Find the requirement that actually pulled mcp in before you edit anything. uv tree --invert names it, and when the unbounded requirement belongs to a dependency rather than to you, editing your own pyproject.toml changes nothing — pin it in that project, or constrain it from yours.
  4. 4 Treat the pin as temporary. The 1.x branch receives security fixes and critical bug fixes only, so the migration is still ahead of you; the pin buys the time to schedule it rather than removing the work.
  5. 5 For a launcher you do not control the manifest of, bound it at the launch command. Adding "--with", "mcp<2" to a uvx server's arguments is the documented shape of this workaround, and it can be dropped once the package itself declares the bound. For a pipx install, pipx runpip <venv> install 'mcp<2' does the same to an existing environment.
toml
# pyproject.toml — the bound the SDK's migration guide recommends.
dependencies = [
    "mcp>=1.28,<2",
]
MCP Python SDK: v1 to v2 migration guide

MCP Python SDK (migrate to 2.x)

If you would rather move to the 2.x line than carry a pin

  1. 1 Change the import and the class name. FastMCP is now MCPServer, and the migration guide states that "All submodules under mcp.server.fastmcp.* are now under mcp.server.mcpserver.* with the same structure" — so a submodule import is the same rewrite one level deeper.
  2. 2 Leave your handlers alone. Under what is unchanged on MCPServer, the guide is explicit: "@mcp.tool(), @mcp.resource(), @mcp.prompt(), and @mcp.completion() take the same arguments and handler signatures as v1." For most servers the whole diff is the import line and the constructor.
  3. 3 Set the floor to 2 and keep an upper bound — mcp>=2,<3 — so the next major cannot do this to you again.
  4. 4 Expect to serve older clients without configuring anything. The v2.0.0 release notes say v2 "still serves every 2025-era client from the same MCPServer, over Streamable HTTP and stdio, with nothing to configure", so migrating the server does not require your callers to move first.
  5. 5 Read the known gaps before you commit to a date. The release notes exclude the tasks extension (SEP-2663) from 2.0.0, and on the client leave out DPoP proof binding (SEP-1932) and the workload-identity jwt-bearer grant. If you depend on one of those, the pin is the better answer this month.
python
# mcp 2.x. The import root and the class name change; the decorators do not.
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("Demo")


@mcp.tool()
def add(a: int, b: int) -> int:
    return a + b
MCP Python SDK v2.0.0 release notes

FastMCP (the standalone package)

If your imports read from fastmcp import FastMCP rather than naming mcp

  1. 1 Check which FastMCP you have, because there are two and only one is affected. The class this error is about lived at mcp.server.fastmcp inside the mcp package. The separately published fastmcp distribution has its own import root, and a from fastmcp import FastMCP line cannot raise this error.
  2. 2 Expect fastmcp 3.x not to drag mcp 2.x in either. On PyPI today, fastmcp 3.4.5 requires fastmcp-slim[client,server]==3.4.5, whose client and server extras both declare mcp<2.0,>=1.24.0 — already bounded, so a fresh resolve of that line cannot reach 2.x.
  3. 3 Look elsewhere in the tree if you see this error in a project that also uses fastmcp. Something else is requiring mcp without a bound; uv tree --invert names it.
  4. 4 Know which line is built on SDK v2 before you upgrade to reach it. The fastmcp 4.0.0b1 release, published 2026-07-28, requires mcp>=2.0.0,<3.0.0 and mcp-types>=2.0.0,<3.0.0, and its notes state that MCP Python SDK v2 "rewrote the protocol layer end to end". It is a beta, so treat it as one.
PyPI: fastmcp

Something else

This failure has one cause and two exits. An unbounded mcp requirement resolved to 2.x under code written for 1.x, so either bound the requirement below 2 and re-resolve, or move the import to mcp.server.mcpserver and the class to MCPServer. Establish which requirement pulled mcp in first: when it belongs to a dependency rather than to your project, the pin has to go where that requirement is declared, or be applied as a constraint from yours. Check the version before you debug anything else in the launch path — the process dies at import, so a token, a wrapper script, or a transport setting cannot be the cause no matter how recently you touched it.

Why it happens

MCP Python SDK 2.0.0, published 2026-07-28, moved every submodule under mcp.server.fastmcp.* to mcp.server.mcpserver.* and renamed the FastMCP class to MCPServer. Its release notes state that pip install mcp now installs 2.x.

So a project that declared mcp>=1.0.0 or mcp>=1.28.1 with no upper bound resolves straight through a breaking major the first time anything re-resolves. That is why the failure looks intermittent and machine-specific when it is neither: a warm cache keeps working, and the next lock refresh, fresh clone, or container build breaks.

The 1.x line is maintenance-only — the release notes say it "will only receive security fixes from now on" and lives on the v1.x branch.

The same resolution breaks servers built on the low-level API with a different string: those raise AttributeError: 'Server' object has no attribute 'list_tools' from the decorator call instead of failing the import.

The exchange

  • MCP client Claude Code, VS Code
  • Server process your MCP server
  • Package index PyPI
  1. This is the whole cause, and it is not in your repository. The requirement was already unbounded; 2.0.0 becoming the newest release is what changed.

    Server process to Package index

    resolve mcp>=1.28.1 no upper bound declared
  2. Package index to Server process

    mcp 2.0.0 published 2026-07-28
  3. The process exits before the first MCP message, so the client has no protocol error to report — only that the transport closed. The traceback is on the subprocess stderr.

    MCP client to Server process

    spawn stdio subprocess uvx your-mcp-server
  4. Inside Server process

    import mcp.server.fastmcp (this step fails) module removed in 2.0.0

    Flow stops here

The MCP server's dependency resolution asks the package index for a requirement declared as mcp>=1.28.1 with no upper bound, and the index returns mcp 2.0.0, published on 2026-07-28. The MCP client then spawns the server as a stdio subprocess, and the process dies importing mcp.server.fastmcp, a module 2.0.0 removed. The client never receives a single MCP message, so it can only report a transport error.
Zuplo

A version bound got the process to start; nothing about who may call it is in the SDK.

The server starts. What stops an unauthenticated caller reaching it?

mcp-oauth-inbound

The route in front of it: the gateway acts as the OAuth 2.1 resource server and authorization server, delegating browser login to an OIDC provider, so the server process never implements a token flow.

Which of our tools can a caller that did authenticate actually call?

mcp-capability-filter-inbound

The ones the route allows and no others: anything outside its allowlist is refused with MethodNotFound before the request reaches your handler, and accessControl.mode: rolesAndGroups narrows the list per caller.

An agent is looping on one tool. Can we cap it before it costs us?

rate-limit-inbound

Yes — per caller, on the route: the looping client runs out of its own budget without the tool being taken away from everyone else.

After the next SDK bump, which callers stopped succeeding?

capability_invocation

Each tool call emits its own event with the caller, the capability, and the outcome, so a regression resolves to a named tool rather than to a drop in traffic.

All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps the code and the authentication it has today.

Stop debugging auth on every MCP server

A gateway in front of your MCP servers handles discovery, audience binding, and token validation once — for every server and every client.