fastmcp-v3-migration — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited fastmcp-v3-migration (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Migrate FastMCP MCP servers from v2.x to v3.0 with zero breaking changes or regressions.
Use AskUserQuestionTool to confirm before any destructive or ambiguous operation:
Most servers need only one change:
# BEFORE (v2.x)
from mcp.server.fastmcp import FastMCP
# AFTER (v3.0)
from fastmcp import FastMCPIf that's the only change needed, the migration is complete. Run tests to verify.
pip show fastmcp or requirements.txtApply changes in order of priority:
fastmcp dev server.py# v2.x
from mcp.server.fastmcp import FastMCP
# v3.0
from fastmcp import FastMCP# v2.x (removed)
from fastmcp.client.transports import WSTransport
# v3.0 replacement
from fastmcp.client.transports import StreamableHttpTransportAuth providers no longer auto-load from environment:
# v2.x (auto-loaded)
auth = GitHubProvider()
# v3.0 (explicit)
import os
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
)# v2.x
tool = await server.get_tool("my_tool")
tool.disable()
# v3.0
server.disable(names={"my_tool"}, components=["tool"])# v2.x
tools = await server.get_tools()
tool = tools["my_tool"]
# v3.0
tools = await server.get_tools()
tool = next((t for t in tools if t.name == "my_tool"), None)# v2.x
from mcp.types import PromptMessage, TextContent
@mcp.prompt
def my_prompt() -> PromptMessage:
return PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
# v3.0
from fastmcp.prompts import Message
@mcp.prompt
def my_prompt() -> Message:
return Message("Hello")# v2.x (sync)
ctx.set_state("key", "value")
value = ctx.get_state("key")
# v3.0 (async)
await ctx.set_state("key", "value")
value = await ctx.get_state("key")# v2.x
tags = tool.meta.get("_fastmcp", {}).get("tags", [])
# v3.0
tags = tool.meta.get("fastmcp", {}).get("tags", [])# v2.x
FASTMCP_SHOW_CLI_BANNER=false
# v3.0
FASTMCP_SHOW_SERVER_BANNER=falseThese still work but emit warnings:
# Deprecated
main.mount(subserver, prefix="api")
# Recommended
main.mount(subserver, namespace="api")# Deprecated
mcp = FastMCP("server", exclude_tags={"internal"})
# Recommended
mcp = FastMCP("server")
mcp.disable(tags={"internal"})# Deprecated: tool_serializer parameter
# Recommended: Return ToolResult for explicit serialization
from fastmcp.tools import ToolResult
@mcp.tool
def my_tool() -> ToolResult:
return ToolResult(
content=[TextContent(type="text", text="Done")],
structured_content={"status": "success"}
)# Deprecated
mcp.add_tool_transformation("name", config)
# Recommended
from fastmcp.server.transforms import ToolTransform
mcp.add_transform(ToolTransform({"name": config}))# Deprecated
proxy = FastMCP.as_proxy("http://example.com/mcp")
# Recommended
from fastmcp.server import create_proxy
proxy = create_proxy("http://example.com/mcp")In v3, decorated functions stay callable (like Flask/FastAPI):
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
# v3.0: Can call directly for testing
greet("World") # Returns "Hello, World!"For v2 compatibility (if code relies on FunctionTool objects):
export FASTMCP_DECORATOR_MODE=objectv3 automatically dispatches sync tools to a threadpool:
@mcp.tool
def slow_tool():
time.sleep(10) # No longer blocks event loop
return "done"For detailed v3 feature adoption, see: references/v3-features.md
Quick overview of new capabilities:
fastmcp run --reload for developmentimport 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# v3 decorated functions are callable
from my_server import greet
def test_greet():
assert greet("World") == "Hello, World!"fastmcp dev server.pyUse AskUserQuestionTool to walk through with user:
from fastmcp import FastMCP_fastmcp → fastmcp (if accessed)Minimal migration - just update import:
# BEFORE
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
# AFTER
from fastmcp import FastMCP # Only change needed
mcp = FastMCP("MyServer")
@mcp.tool
def add(a: int, b: int) -> int:
return a + bUpdate imports and explicit auth config:
# BEFORE
from mcp.server.fastmcp import FastMCP
from fastmcp.server.auth import GitHubProvider
mcp = FastMCP("AuthServer")
auth = GitHubProvider() # Auto-loaded from env
# AFTER
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"],
)Update to async state methods:
# BEFORE
@mcp.tool
def counter(ctx: Context) -> int:
count = ctx.get_state("count") or 0
ctx.set_state("count", count + 1)
return count + 1
# AFTER
@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 + 1Update prefix → namespace:
# BEFORE
main = FastMCP("Main")
sub = FastMCP("Sub")
main.mount(sub, prefix="api")
# AFTER
main = FastMCP("Main")
sub = FastMCP("Sub")
main.mount(sub, namespace="api")Update to visibility system:
# BEFORE
mcp = FastMCP("Server", exclude_tags={"internal"}, include_tags={"public"})
# AFTER
mcp = FastMCP("Server")
mcp.disable(tags={"internal"})
mcp.enable(tags={"public"}, only=True) # Allowlist modeModuleNotFoundError: No module named 'mcp.server.fastmcp'Solution: Update import to from fastmcp import FastMCP
TypeError: object NoneType can't be used in 'await' expressionSolution: Ensure ctx.get_state/set_state are awaited and function is async
In v3, @mcp.tool returns the original function, not a FunctionTool object.
Solution: If code depends on FunctionTool properties, either:
FASTMCP_DECORATOR_MODE=objectawait mcp.get_tool("name") to get the Tool objectv3 sends notifications automatically. Manual calls may be redundant.
Solution: Remove manual notification code unless custom logic requires it
For comprehensive details, see reference files:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.