| name | fastmcp-v3-migration |
| description | Production-ready migration from FastMCP v2.x to v3.0. Use when upgrading MCP servers built on FastMCP to v3, migrating legacy FastMCP code, converting v2 patterns to v3 architecture (providers/transforms), or when user mentions FastMCP upgrade, migration, or v3. Covers breaking changes, deprecated patterns, new v3 features (providers, transforms, versioning, authorization), and testing strategies. Ensures zero-regression migrations with user confirmation checkpoints. |
FastMCP v3 Migration Skill
Migrate FastMCP MCP servers from v2.x to v3.0 with zero breaking changes or regressions.
Critical: User Confirmation Protocol
Use AskUserQuestionTool to confirm before any destructive or ambiguous operation:
- Before modifying any file, confirm the migration scope
- Before changing imports, confirm which v2 patterns are in use
- Before removing deprecated code, confirm replacement approach
- Before testing, confirm the test strategy
- After migration, confirm all features work as expected
Quick Start Migration
Most servers need only one change:
from mcp.server.fastmcp import FastMCP
from fastmcp import FastMCP
If that's the only change needed, the migration is complete. Run tests to verify.
Migration Workflow
Phase 1: Assessment
- Identify FastMCP version: Check
pip show fastmcp or requirements.txt
- Scan for breaking patterns: See Breaking Changes Reference
- Use AskUserQuestionTool: Confirm migration scope with user
- Create backup: Ensure git commit or file backup exists
Phase 2: Core Migration
Apply changes in order of priority:
- Update imports (required)
- Fix breaking changes (if any patterns detected)
- Update deprecated patterns (optional but recommended)
- Adopt new v3 features (optional enhancement)
Phase 3: Validation
- Run existing tests
- Test with FastMCP Client
- Verify with MCP Inspector:
fastmcp dev server.py
- Use AskUserQuestionTool: Confirm migration success with user
Breaking Changes Reference
Import Change (Required)
from mcp.server.fastmcp import FastMCP
from fastmcp import FastMCP
WSTransport Removed
from fastmcp.client.transports import WSTransport
from fastmcp.client.transports import StreamableHttpTransport
Auth Provider Environment Variables
Auth providers no longer auto-load from environment:
auth = GitHubProvider()
import os
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
)
Component enable()/disable() Moved to Server
tool = await server.get_tool("my_tool")
tool.disable()
server.disable(names={"my_tool"}, components=["tool"])
Listing Methods Return Lists (Not Dicts)
tools = await server.get_tools()
tool = tools["my_tool"]
tools = await server.get_tools()
tool = next((t for t in tools if t.name == "my_tool"), None)
Prompts Use Message Class
from mcp.types import PromptMessage, TextContent
@mcp.prompt
def my_prompt() -> PromptMessage:
return PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
from fastmcp.prompts import Message
@mcp.prompt
def my_prompt() -> Message:
return Message("Hello")
Context State Methods Are Async
ctx.set_state("key", "value")
value = ctx.get_state("key")
await ctx.set_state("key", "value")
value = await ctx.get_state("key")
Metadata Namespace Renamed
tags = tool.meta.get("_fastmcp", {}).get("tags", [])
tags = tool.meta.get("fastmcp", {}).get("tags", [])
Server Banner Environment Variable
FASTMCP_SHOW_CLI_BANNER=false
FASTMCP_SHOW_SERVER_BANNER=false
Deprecated Patterns (Update When Convenient)
These still work but emit warnings:
mount() prefix → namespace
main.mount(subserver, prefix="api")
main.mount(subserver, namespace="api")
include_tags/exclude_tags → enable()/disable()
mcp = FastMCP("server", exclude_tags={"internal"})
mcp = FastMCP("server")
mcp.disable(tags={"internal"})
tool_serializer → ToolResult
from fastmcp.tools import ToolResult
@mcp.tool
def my_tool() -> ToolResult:
return ToolResult(
content=[TextContent(type="text", text="Done")],
structured_content={"status": "success"}
)
add_tool_transformation() → add_transform()
mcp.add_tool_transformation("name", config)
from fastmcp.server.transforms import ToolTransform
mcp.add_transform(ToolTransform({"name": config}))
FastMCP.as_proxy() → create_proxy()
proxy = FastMCP.as_proxy("http://example.com/mcp")
from fastmcp.server import create_proxy
proxy = create_proxy("http://example.com/mcp")
Behavior Changes
Decorators Return Functions
In v3, decorated functions stay callable (like Flask/FastAPI):
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
greet("World")
For v2 compatibility (if code relies on FunctionTool objects):
export FASTMCP_DECORATOR_MODE=object
Sync Tools Run in Threadpool
v3 automatically dispatches sync tools to a threadpool:
@mcp.tool
def slow_tool():
time.sleep(10)
return "done"
New v3 Features
For detailed v3 feature adoption, see: references/v3-features.md
Quick overview of new capabilities:
- Providers: Source components from anywhere (LocalProvider, FileSystemProvider, OpenAPIProvider, ProxyProvider, SkillsProvider)
- Transforms: Middleware for component modification (Namespace, ToolTransform, VersionFilter, Visibility)
- Component Versioning: Multiple versions of same tool with automatic routing
- Authorization: Per-component auth with require_auth, require_scopes
- Session State: Async state that persists across tool calls
- Visibility System: Dynamic enable/disable by tags, names, sessions
- OpenTelemetry: Native tracing instrumentation
- Background Tasks: SEP-1686 long-running operations
- Hot Reload:
fastmcp run --reload for development
Testing Strategy
Using FastMCP Client
import pytest
from fastmcp.client import Client
@pytest.fixture
async def client():
from my_server import mcp
async with Client(transport=mcp) as client:
yield client
async def test_tools_available(client):
tools = await client.list_tools()
assert len(tools) > 0
async def test_tool_execution(client):
result = await client.call_tool("my_tool", {"arg": "value"})
assert result.data is not None
Direct Function Testing (v3 Feature)
from my_server import greet
def test_greet():
assert greet("World") == "Hello, World!"
MCP Inspector
fastmcp dev server.py
Migration Checklist
Use AskUserQuestionTool to walk through with user:
Common Migration Scenarios
Scenario 1: Simple Tool Server
Minimal migration - just update import:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
Scenario 2: Server with Auth
Update imports and explicit auth config:
from mcp.server.fastmcp import FastMCP
from fastmcp.server.auth import GitHubProvider
mcp = FastMCP("AuthServer")
auth = GitHubProvider()
import os
from fastmcp import FastMCP
from fastmcp.server.auth import GitHubProvider
mcp = FastMCP("AuthServer")
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
)
Scenario 3: Server with Session State
Update to async state methods:
@mcp.tool
def counter(ctx: Context) -> int:
count = ctx.get_state("count") or 0
ctx.set_state("count", count + 1)
return count + 1
@mcp.tool
async def counter(ctx: Context) -> int:
count = await ctx.get_state("count") or 0
await ctx.set_state("count", count + 1)
return count + 1
Scenario 4: Server with Mounted Sub-servers
Update prefix → namespace:
main = FastMCP("Main")
sub = FastMCP("Sub")
main.mount(sub, prefix="api")
main = FastMCP("Main")
sub = FastMCP("Sub")
main.mount(sub, namespace="api")
Scenario 5: Server with Tag Filtering
Update to visibility system:
mcp = FastMCP("Server", exclude_tags={"internal"}, include_tags={"public"})
mcp = FastMCP("Server")
mcp.disable(tags={"internal"})
mcp.enable(tags={"public"}, only=True)
Troubleshooting
Import Error After Upgrade
ModuleNotFoundError: No module named 'mcp.server.fastmcp'
Solution: Update import to from fastmcp import FastMCP
State Methods Not Async Error
TypeError: object NoneType can't be used in 'await' expression
Solution: Ensure ctx.get_state/set_state are awaited and function is async
Decorator Returns Function Instead of Tool
In v3, @mcp.tool returns the original function, not a FunctionTool object.
Solution: If code depends on FunctionTool properties, either:
- Set
FASTMCP_DECORATOR_MODE=object
- Use
await mcp.get_tool("name") to get the Tool object
List Changed Notifications Not Firing
v3 sends notifications automatically. Manual calls may be redundant.
Solution: Remove manual notification code unless custom logic requires it
Reference Documentation
For comprehensive details, see reference files: