MCP Python SDK v2 shipped stable on July 27. If you have a Python MCP server — including one built last week following a tutorial — you have breaking changes waiting. FastMCP is gone, import paths moved, and every field flipped to snake_case. The good news: most migrations take five minutes. Here’s exactly what to change.
The Rename That Breaks Everything First
The most immediate change is the class rename: FastMCP is now MCPServer. There is no deprecation shim. There is no backward-compatible alias. The old import path simply does not exist in v2. The official migration guide covers the full list — but the rename is the one that breaks your server at startup, before anything else runs.
# Before — MCP Python SDK v1
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
# After — MCP Python SDK v2
from mcp.server.mcpserver import MCPServer, Context
mcp = MCPServer("my-server")
Everything under mcp.server.fastmcp.* moved to mcp.server.mcpserver.*. The ctx.fastmcp property is now ctx.mcp_server. One more detail worth catching: the default server name string changes from "FastMCP" to "mcp-server" — pass an explicit name if you rely on serverInfo.name in your client.
snake_case Is Now Everywhere
All Pydantic model fields switched from camelCase to snake_case for Python access. The JSON wire format is unchanged — but the Python attributes are not. This one hides well: your server may start fine and only fail at runtime when code reads a tool or resource field.
tool.inputSchema→tool.input_schemaresult.isError→result.is_errorlisting.nextCursor→listing.next_cursorcontent.mimeType→content.mime_type
If you serialize models for wire format, pass by_alias=True: tool.model_dump(by_alias=True, mode="json") still produces the camelCase JSON that clients expect.
Context Injection Replaces get_context()
Handler context is now injected as an explicit parameter. mcp.get_context() is removed. The fix is a one-line signature change — but it’s pervasive if your tools use progress reporting or logging:
# Before
@mcp.tool()
async def search(query: str) -> str:
ctx = mcp.get_context()
await ctx.report_progress(0, 100)
return f"Results for: {query}"
# After
@mcp.tool()
async def search(query: str, ctx: Context) -> str:
await ctx.report_progress(0, 100)
return f"Results for: {query}"
The @mcp.tool() decorator stays. Only the context acquisition changes.
Transport Parameters Move to run()
Constructor-level transport arguments no longer exist. Move them to the run() call:
# Before
mcp = FastMCP("Demo", json_response=True, stateless_http=True)
mcp.run(transport="streamable-http")
# After
mcp = MCPServer("Demo")
mcp.run(transport="streamable-http", json_response=True, stateless_http=True)
Not Ready Yet? Pin Your Version
If your team cannot migrate immediately, pin before an unpinned install auto-upgrades:
# requirements.txt
mcp>=1.28.1,<2
This is exactly what IBM’s mcp-context-forge team did. SDK v1.x continues to receive critical bug fixes and security patches. The deprecated features — sampling, roots, logging — have a one-year grace period, and a v2 server falls back to legacy behavior for older clients. Pinning is not a cop-out; it’s responsible dependency management while your team plans the migration.
Why FastMCP Had to Go
The rename reflects a deeper protocol shift. The MCP 2026-07-28 specification eliminates the initialize handshake and session IDs entirely — any server instance can handle any request. Round-robin load balancers work without sticky sessions. Serverless deployments become straightforward. “FastMCP” made sense as a high-level layer on top of session-based MCP. Once sessions disappear from the protocol, all MCP servers are stateless by design — the name stops meaning anything, and MCPServer is simply what it is.
Migration Checklist
- Update package:
pip install "mcp>=2.0" - Replace all
FastMCPimports →MCPServerfrommcp.server.mcpserver - Update any
mcp.server.fastmcp.*submodule paths - Replace
ctx = mcp.get_context()withctx: Contextparameter in handler signatures - Search codebase for
inputSchema,isError,nextCursor,mimeType— rename each - Move constructor transport params to
run() - If using HTTP transport directly: swap
httpxforhttpx2
If you built a v1 server from our MCP Server in Python guide, the structure still holds — apply these changes on top. For anything beyond the mechanical renames, the full v2 changelog covers the low-level server API, union type adapter changes, and the new multi-round-trip request pattern.













