Add upgrade guides for users coming from the MCP SDK (#3215)

* Add upgrade guides for users coming from the MCP SDK

* Fix incorrect Image import path in LLM migration prompt

* Align LLM prompts with prose across all three upgrade guides

* Move upgrade guides under getting-started/upgrading, add install section and --upgrade flag
This commit is contained in:
Jeremiah Lowin 2026-02-18 14:30:07 -05:00 committed by GitHub
commit 1eb57ba262
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 820 additions and 24 deletions

View file

@ -203,7 +203,7 @@ FastMCP 3.0 rebuilds the framework around three primitives: components, provider
🔐 **Component Authorization** via `@tool(auth=require_scopes("admin"))` and `AuthMiddleware` for server-wide policies.
Breaking changes are minimal: for most servers, updating the import statement is all you need. See the [migration guide](https://github.com/prefecthq/fastmcp/blob/main/docs/development/upgrade-guide.mdx) for details.
Breaking changes are minimal: for most servers, updating the import statement is all you need. See the [migration guide](https://github.com/prefecthq/fastmcp/blob/main/docs/getting-started/upgrading/from-fastmcp-2.mdx) for details.
## What's Changed
### New Features 🎉

View file

@ -88,8 +88,18 @@
"pages": [
"getting-started/welcome",
"getting-started/installation",
"development/upgrade-guide",
"getting-started/quickstart"
"getting-started/quickstart",
{
"collapsed": true,
"group": "Upgrade",
"icon": "up",
"tag": "NEW",
"pages": [
"getting-started/upgrading/from-fastmcp-2",
"getting-started/upgrading/from-mcp-sdk",
"getting-started/upgrading/from-low-level-sdk"
]
}
]
},
{
@ -940,6 +950,18 @@
{
"destination": "/servers/transforms/transforms",
"source": "/patterns/tool-transformation"
},
{
"destination": "/getting-started/upgrading/from-fastmcp-2",
"source": "/development/upgrade-guide"
},
{
"destination": "/getting-started/upgrading/from-mcp-sdk",
"source": "/getting-started/upgrading-from-sdk"
},
{
"destination": "/getting-started/upgrading/from-low-level-sdk",
"source": "/getting-started/low-level-sdk"
}
],
"search": {

View file

@ -64,25 +64,15 @@ Alternatively, wait for the stable v5 release. See [this issue](https://github.c
### From FastMCP 2.x
See the [Upgrade Guide](/development/upgrade-guide) for a complete list of breaking changes and migration steps.
See the [Upgrade Guide](/getting-started/upgrading/from-fastmcp-2) for a complete list of breaking changes and migration steps.
### From the Official MCP SDK
### From FastMCP 1.0 (in the Low-Level SDK)
Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 3.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
If you're using FastMCP 1.0 via the `mcp` package (`from mcp.server.fastmcp import FastMCP`), upgrading is straightforward — for most servers, it's a single import change. See the [full upgrade guide](/getting-started/upgrading/from-mcp-sdk) for details.
```python {5}
# Before
# from mcp.server.fastmcp import FastMCP
### From the Low-Level Server API
# After
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
```
<Warning>
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 3.x. Please refer to this documentation for details on new capabilities.
</Warning>
If you built your server directly on the `mcp` package's `Server` class — with `list_tools()`/`call_tool()` handlers and hand-written JSON Schema — see the [migration guide](/getting-started/upgrading/from-low-level-sdk) for a full walkthrough.
## Versioning Policy

View file

@ -1,6 +1,6 @@
---
title: Upgrade Guide
sidebarTitle: Upgrade Guide
title: Upgrading from FastMCP 2
sidebarTitle: "From FastMCP 2"
description: Migration instructions for upgrading between FastMCP versions
icon: up
tag: NEW
@ -12,8 +12,20 @@ This guide covers breaking changes and migration steps when upgrading FastMCP.
For most servers, upgrading to v3 requires a single change: swap `from mcp.server.fastmcp import FastMCP` for `from fastmcp import FastMCP`. Everything below covers the less common cases.
### Install
Since you already have `fastmcp` installed, you need to explicitly request the new version — `pip install fastmcp` won't upgrade an existing installation:
```bash
pip install --upgrade fastmcp
# or
uv add fastmcp@latest
```
If you pin versions in a requirements file or `pyproject.toml`, update your pin to `fastmcp>=3.0.0,<4`.
<Info>
**New repository home.** As part of the v3 release, FastMCP's GitHub repository now lives at [`prefecthq/fastmcp`](https://github.com/prefecthq/fastmcp) under [Prefect](https://prefect.io)'s stewardship. GitHub automatically redirects existing clones and bookmarks, so nothing breaks — but you can update your local remote whenever convenient:
**New repository home.** As part of the v3 release, FastMCP's GitHub repository has moved from `jlowin/fastmcp` to [`prefecthq/fastmcp`](https://github.com/prefecthq/fastmcp) under [Prefect](https://prefect.io)'s stewardship. GitHub automatically redirects existing clones and bookmarks, so nothing breaks — but you can update your local remote whenever convenient:
```bash
git remote set-url origin https://github.com/prefecthq/fastmcp.git
@ -22,8 +34,8 @@ git remote set-url origin https://github.com/prefecthq/fastmcp.git
If you reference the repository URL in dependency specifications (e.g., `git+https://github.com/jlowin/fastmcp.git`), update those to the new location.
</Info>
<Prompt description="Copy this prompt into any LLM along with your server code to get automated migration guidance.">
You are migrating a FastMCP v2 server to FastMCP v3.0. Analyze the provided code and identify every change needed. The full upgrade guide is at https://gofastmcp.com/development/upgrade-guide — fetch it for complete context.
<Prompt description="Copy this prompt into any LLM along with your server code to get automated upgrade guidance.">
You are upgrading a FastMCP v2 server to FastMCP v3.0. Analyze the provided code and identify every change needed. The full upgrade guide is at https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2 and the complete FastMCP documentation is at https://gofastmcp.com — fetch these for complete context.
BREAKING CHANGES (will crash at import or runtime):
@ -66,6 +78,10 @@ BREAKING CHANGES (will crash at import or runtime):
12. OAUTH STORAGE: Default OAuth client storage changed from DiskStore to FileTreeStore due to pickle deserialization vulnerability in diskcache (CVE-2025-69872). Clients using default storage will re-register automatically on first connection. If using DiskStore explicitly, switch to FileTreeStore or add pip install 'py-key-value-aio[disk]'.
13. REPO MOVE: GitHub repository moved from jlowin/fastmcp to prefecthq/fastmcp. Update git remotes and dependency URLs that reference the old location.
14. BACKGROUND TASKS: FastMCP's background task system (SEP-1686) is now an optional dependency. If the code uses task=True or TaskConfig, add pip install "fastmcp[tasks]".
DEPRECATIONS (still work but emit warnings):
- mount(prefix="x") -> mount(namespace="x")
@ -262,6 +278,16 @@ greet("World") # Works! Returns "Hello, World!"
If you have code that treats the decorated result as a `FunctionTool` (e.g., accessing `.name` or `.description`), set `FASTMCP_DECORATOR_MODE=object` for v2 compatibility. This escape hatch is itself deprecated and will be removed in a future release.
**Background tasks require optional dependency**
FastMCP's background task system (SEP-1686) is now behind an optional extra. If your server uses background tasks, install with:
```bash
pip install "fastmcp[tasks]"
```
Without the extra, configuring a tool with `task=True` or `TaskConfig` will raise an import error at runtime. See [Background Tasks](/servers/tasks) for details.
### Deprecated Features
These still work but emit warnings. Update when convenient.

View file

@ -0,0 +1,593 @@
---
title: Upgrading from the MCP Low-Level SDK
sidebarTitle: "From MCP Low-Level SDK"
description: Upgrade your MCP server from the low-level Python SDK's Server class to FastMCP
icon: up
tag: NEW
---
If you've been building MCP servers directly on the `mcp` package's `Server` class — writing `list_tools()` and `call_tool()` handlers, hand-crafting JSON Schema dicts, and wiring up transport boilerplate — this guide is for you. FastMCP replaces all of that machinery with a declarative, Pythonic API where your functions *are* the protocol surface.
The core idea: instead of telling the SDK what your tools look like and then separately implementing them, you write ordinary Python functions and let FastMCP derive the protocol layer from your code. Type hints become JSON Schema. Docstrings become descriptions. Return values are serialized automatically. The plumbing you wrote to satisfy the protocol just disappears.
<Note>
This guide covers upgrading from **v1** of the `mcp` package. We'll provide a separate guide when v2 ships.
</Note>
<Note>
Already using FastMCP 1.0 via `from mcp.server.fastmcp import FastMCP`? Your upgrade is simpler — see the [FastMCP 1.0 upgrade guide](/getting-started/upgrading/from-mcp-sdk) instead.
</Note>
<Prompt description="Copy this prompt into any LLM along with your server code to get automated upgrade guidance.">
You are upgrading an MCP server from the `mcp` package's low-level Server class (v1) to FastMCP 3.0. The server currently uses `mcp.server.Server` (or `mcp.server.lowlevel.server.Server`) with manual handler registration. Analyze the provided code and rewrite it using FastMCP's high-level API. The full guide is at https://gofastmcp.com/getting-started/upgrading/from-low-level-sdk and the complete FastMCP documentation is at https://gofastmcp.com — fetch these for complete context.
UPGRADE RULES:
1. IMPORTS: Replace all `mcp.*` imports with FastMCP equivalents.
- `from mcp.server import Server` or `from mcp.server.lowlevel.server import Server` → `from fastmcp import FastMCP`
- `import mcp.types as types` → remove (not needed for most code)
- `from mcp.server.stdio import stdio_server` → remove (handled by mcp.run())
- `from mcp.server.sse import SseServerTransport` → remove (handled by mcp.run())
2. SERVER: Replace `Server("name")` with `FastMCP("name")`.
3. TOOLS: Replace the list_tools + call_tool handler pair with individual @mcp.tool decorators.
- Delete the `@server.list_tools()` handler entirely
- Delete the `@server.call_tool()` handler entirely
- For each tool that was listed in list_tools and dispatched in call_tool, create a new function:
- Decorate it with `@mcp.tool`
- Use the tool name as the function name (or pass name= to the decorator)
- Use the docstring for the description (or pass description= to the decorator)
- Convert the inputSchema JSON Schema into typed Python parameters (e.g., `{"type": "integer"}` → `int`, `{"type": "string"}` → `str`, `{"type": "array", "items": {"type": "string"}}` → `list[str]`)
- Return plain Python values (`str`, `int`, `dict`, etc.) instead of `list[types.TextContent(...)]`
- If the tool returned `types.ImageContent` or `types.EmbeddedResource`, use `from fastmcp.utilities.types import Image` or return the appropriate type
4. RESOURCES: Replace the list_resources + list_resource_templates + read_resource handler trio with individual @mcp.resource decorators.
- Delete all three handlers
- For each static resource, create a function decorated with `@mcp.resource("uri://...")`
- For each resource template, use `@mcp.resource("uri://{param}/path")` with `{param}` in the URI and a matching function parameter
- Return str for text content, bytes for binary content
- Set `mime_type=` in the decorator if needed
5. PROMPTS: Replace the list_prompts + get_prompt handler pair with individual @mcp.prompt decorators.
- Delete both handlers
- For each prompt, create a function decorated with `@mcp.prompt`
- Convert PromptArgument definitions into typed function parameters
- Return str for simple single-message prompts (auto-wrapped as user message)
- Return `list[Message]` for multi-message prompts: `from fastmcp.prompts import Message`
- `Message("text")` defaults to `role="user"`; use `Message("text", role="assistant")` for assistant messages
6. TRANSPORT: Replace all transport boilerplate with mcp.run().
- `async with stdio_server() as (r, w): await server.run(r, w, ...)` → `mcp.run()` (`stdio` is the default)
- SSE/Starlette setup → `mcp.run(transport="sse", host="...", port=...)`
- Streamable HTTP setup → `mcp.run(transport="http", host="...", port=...)`
- Delete asyncio.run(main()) boilerplate — use `if __name__ == "__main__": mcp.run()`
7. CONTEXT: Replace `server.request_context` with FastMCP's Context parameter.
- Add `from fastmcp import Context` and add a `ctx: Context` parameter to any tool that needs it
- `server.request_context.session.send_log_message(...)` → `await ctx.info("message")` or `await ctx.warning("message")`
- Progress reporting → `await ctx.report_progress(current, total)`
For each change, show the original code, explain what it did, and provide the FastMCP equivalent.
</Prompt>
## Install
```bash
pip install --upgrade fastmcp
# or
uv add fastmcp
```
FastMCP includes the `mcp` package as a transitive dependency, so you don't lose access to anything.
## Server and Transport
The `Server` class requires you to choose a transport, connect streams, build initialization options, and run an event loop. FastMCP collapses all of that into a constructor and a `run()` call.
<CodeGroup>
```python Before
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
server = Server("my-server")
# ... register handlers ...
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
asyncio.run(main())
```
```python After
from fastmcp import FastMCP
mcp = FastMCP("my-server")
# ... register tools, resources, prompts ...
if __name__ == "__main__":
mcp.run()
```
</CodeGroup>
Need HTTP instead of stdio? With the `Server` class, you'd wire up Starlette routes and `SseServerTransport` or `StreamableHTTPSessionManager`. With FastMCP:
```python
mcp.run(transport="http", host="0.0.0.0", port=8000)
```
## Tools
This is where the difference is most dramatic. The `Server` class requires two handlers — one to describe your tools (with hand-written JSON Schema) and another to dispatch calls by name. FastMCP eliminates both by deriving everything from your function signature.
<CodeGroup>
```python Before
import mcp.types as types
from mcp.server import Server
server = Server("math")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="add",
description="Add two numbers",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["a", "b"],
},
),
types.Tool(
name="multiply",
description="Multiply two numbers",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["a", "b"],
},
),
]
@server.call_tool()
async def call_tool(
name: str, arguments: dict
) -> list[types.TextContent]:
if name == "add":
result = arguments["a"] + arguments["b"]
return [types.TextContent(type="text", text=str(result))]
elif name == "multiply":
result = arguments["a"] * arguments["b"]
return [types.TextContent(type="text", text=str(result))]
raise ValueError(f"Unknown tool: {name}")
```
```python After
from fastmcp import FastMCP
mcp = FastMCP("math")
@mcp.tool
def add(a: float, b: float) -> float:
"""Add two numbers"""
return a + b
@mcp.tool
def multiply(a: float, b: float) -> float:
"""Multiply two numbers"""
return a * b
```
</CodeGroup>
Each `@mcp.tool` function is self-contained: its name becomes the tool name, its docstring becomes the description, its type annotations become the JSON Schema, and its return value is serialized automatically. No routing. No schema dictionaries. No content-type wrappers.
### Type Mapping
When converting your `inputSchema` to Python type hints:
| JSON Schema | Python Type |
|---|---|
| `{"type": "string"}` | `str` |
| `{"type": "number"}` | `float` |
| `{"type": "integer"}` | `int` |
| `{"type": "boolean"}` | `bool` |
| `{"type": "array", "items": {"type": "string"}}` | `list[str]` |
| `{"type": "object"}` | `dict` |
| Optional property (not in `required`) | `param: str \| None = None` |
### Return Values
With the `Server` class, tools return `list[types.TextContent | types.ImageContent | ...]`. In FastMCP, return plain Python values — strings, numbers, dicts, lists, dataclasses, Pydantic models — and serialization is handled for you.
For images or other non-text content, FastMCP provides helpers:
```python
from fastmcp import FastMCP
from fastmcp.utilities.types import Image
mcp = FastMCP("media")
@mcp.tool
def create_chart(data: list[float]) -> Image:
"""Generate a chart from data."""
png_bytes = generate_chart(data) # your logic
return Image(data=png_bytes, format="png")
```
## Resources
The `Server` class uses three handlers for resources: `list_resources()` to enumerate them, `list_resource_templates()` for URI templates, and `read_resource()` to serve content — all with manual routing by URI. FastMCP replaces all three with per-resource decorators.
<CodeGroup>
```python Before
import json
import mcp.types as types
from mcp.server import Server
from pydantic import AnyUrl
server = Server("data")
@server.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=AnyUrl("config://app"),
name="app_config",
description="Application configuration",
mimeType="application/json",
),
types.Resource(
uri=AnyUrl("config://features"),
name="feature_flags",
description="Active feature flags",
mimeType="application/json",
),
]
@server.list_resource_templates()
async def list_resource_templates() -> list[types.ResourceTemplate]:
return [
types.ResourceTemplate(
uriTemplate="users://{user_id}/profile",
name="user_profile",
description="User profile by ID",
),
types.ResourceTemplate(
uriTemplate="projects://{project_id}/status",
name="project_status",
description="Project status by ID",
),
]
@server.read_resource()
async def read_resource(uri: AnyUrl) -> str:
uri_str = str(uri)
if uri_str == "config://app":
return json.dumps({"debug": False, "version": "1.0"})
if uri_str == "config://features":
return json.dumps({"dark_mode": True, "beta": False})
if uri_str.startswith("users://"):
user_id = uri_str.split("/")[2]
return json.dumps({"id": user_id, "name": f"User {user_id}"})
if uri_str.startswith("projects://"):
project_id = uri_str.split("/")[2]
return json.dumps({"id": project_id, "status": "active"})
raise ValueError(f"Unknown resource: {uri}")
```
```python After
import json
from fastmcp import FastMCP
mcp = FastMCP("data")
@mcp.resource("config://app", mime_type="application/json")
def app_config() -> str:
"""Application configuration"""
return json.dumps({"debug": False, "version": "1.0"})
@mcp.resource("config://features", mime_type="application/json")
def feature_flags() -> str:
"""Active feature flags"""
return json.dumps({"dark_mode": True, "beta": False})
@mcp.resource("users://{user_id}/profile")
def user_profile(user_id: str) -> str:
"""User profile by ID"""
return json.dumps({"id": user_id, "name": f"User {user_id}"})
@mcp.resource("projects://{project_id}/status")
def project_status(project_id: str) -> str:
"""Project status by ID"""
return json.dumps({"id": project_id, "status": "active"})
```
</CodeGroup>
Static resources and URI templates use the same `@mcp.resource` decorator — FastMCP detects `{placeholders}` in the URI and automatically registers a template. The function parameter `user_id` maps directly to the `{user_id}` placeholder.
## Prompts
Same pattern: the `Server` class uses `list_prompts()` and `get_prompt()` with manual routing. FastMCP uses one decorator per prompt.
<CodeGroup>
```python Before
import mcp.types as types
from mcp.server import Server
server = Server("prompts")
@server.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return [
types.Prompt(
name="review_code",
description="Review code for issues",
arguments=[
types.PromptArgument(
name="code",
description="The code to review",
required=True,
),
types.PromptArgument(
name="language",
description="Programming language",
required=False,
),
],
)
]
@server.get_prompt()
async def get_prompt(
name: str, arguments: dict[str, str] | None
) -> types.GetPromptResult:
if name == "review_code":
code = (arguments or {}).get("code", "")
language = (arguments or {}).get("language", "")
lang_note = f" (written in {language})" if language else ""
return types.GetPromptResult(
description="Code review prompt",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"Please review this code{lang_note}:\n\n{code}",
),
)
],
)
raise ValueError(f"Unknown prompt: {name}")
```
```python After
from fastmcp import FastMCP
mcp = FastMCP("prompts")
@mcp.prompt
def review_code(code: str, language: str | None = None) -> str:
"""Review code for issues"""
lang_note = f" (written in {language})" if language else ""
return f"Please review this code{lang_note}:\n\n{code}"
```
</CodeGroup>
Returning a `str` from a prompt function automatically wraps it as a user message. For multi-turn prompts, return a `list[Message]`:
```python
from fastmcp import FastMCP
from fastmcp.prompts import Message
mcp = FastMCP("prompts")
@mcp.prompt
def debug_session(error: str) -> list[Message]:
"""Start a debugging conversation"""
return [
Message(f"I'm seeing this error:\n\n{error}"),
Message("I'll help you debug that. Can you share the relevant code?", role="assistant"),
]
```
## Request Context
The `Server` class exposes request context through `server.request_context`, which gives you the raw `ServerSession` for sending notifications. FastMCP replaces this with a typed `Context` object injected into any function that declares it.
<CodeGroup>
```python Before
import mcp.types as types
from mcp.server import Server
server = Server("worker")
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "process_data":
ctx = server.request_context
await ctx.session.send_log_message(
level="info", data="Starting processing..."
)
# ... do work ...
await ctx.session.send_log_message(
level="info", data="Done!"
)
return [types.TextContent(type="text", text="Processed")]
```
```python After
from fastmcp import FastMCP, Context
mcp = FastMCP("worker")
@mcp.tool
async def process_data(ctx: Context) -> str:
"""Process data with progress logging"""
await ctx.info("Starting processing...")
# ... do work ...
await ctx.info("Done!")
return "Processed"
```
</CodeGroup>
The `Context` object provides logging (`ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`), progress reporting (`ctx.report_progress()`), resource subscriptions, session state, and more. See [Context](/servers/context) for the full API.
## Complete Example
A full server upgrade, showing how all the pieces fit together:
<CodeGroup>
```python Before expandable
import asyncio
import json
import mcp.types as types
from mcp.server import Server
from mcp.server.stdio import stdio_server
from pydantic import AnyUrl
server = Server("demo")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="greet",
description="Greet someone by name",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"},
},
"required": ["name"],
},
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "greet":
return [types.TextContent(type="text", text=f"Hello, {arguments['name']}!")]
raise ValueError(f"Unknown tool: {name}")
@server.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=AnyUrl("info://version"),
name="version",
description="Server version",
)
]
@server.read_resource()
async def read_resource(uri: AnyUrl) -> str:
if str(uri) == "info://version":
return json.dumps({"version": "1.0.0"})
raise ValueError(f"Unknown resource: {uri}")
@server.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return [
types.Prompt(
name="summarize",
description="Summarize text",
arguments=[
types.PromptArgument(name="text", required=True)
],
)
]
@server.get_prompt()
async def get_prompt(
name: str, arguments: dict[str, str] | None
) -> types.GetPromptResult:
if name == "summarize":
return types.GetPromptResult(
description="Summarize text",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"Summarize:\n\n{(arguments or {}).get('text', '')}",
),
)
],
)
raise ValueError(f"Unknown prompt: {name}")
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream, write_stream,
server.create_initialization_options(),
)
asyncio.run(main())
```
```python After
import json
from fastmcp import FastMCP
mcp = FastMCP("demo")
@mcp.tool
def greet(name: str) -> str:
"""Greet someone by name"""
return f"Hello, {name}!"
@mcp.resource("info://version")
def version() -> str:
"""Server version"""
return json.dumps({"version": "1.0.0"})
@mcp.prompt
def summarize(text: str) -> str:
"""Summarize text"""
return f"Summarize:\n\n{text}"
if __name__ == "__main__":
mcp.run()
```
</CodeGroup>
## What's Next
Once you've upgraded, you have access to everything FastMCP provides beyond the basics:
- **[Server composition](/servers/providers/mounting)** — Mount sub-servers to build modular applications
- **[Middleware](/servers/middleware)** — Add logging, rate limiting, error handling, and caching
- **[Proxy servers](/servers/providers/proxy)** — Create a proxy to any existing MCP server
- **[OpenAPI integration](/integrations/openapi)** — Generate an MCP server from an OpenAPI spec
- **[Authentication](/servers/auth/authentication)** — Built-in OAuth and token verification
- **[Testing](/patterns/testing)** — Test your server directly in Python without running a subprocess
Explore the full documentation at [gofastmcp.com](https://gofastmcp.com).

View file

@ -0,0 +1,165 @@
---
title: Upgrading from the MCP SDK
sidebarTitle: "From MCP SDK"
description: Upgrade from FastMCP in the MCP Python SDK to the standalone FastMCP framework
icon: up
tag: NEW
---
If your server starts with `from mcp.server.fastmcp import FastMCP`, you're using FastMCP 1.0 — the version bundled with v1 of the `mcp` package. Upgrading to the standalone FastMCP framework is easy. **For most servers, it's a single import change.**
```python
# Before
from mcp.server.fastmcp import FastMCP
# After
from fastmcp import FastMCP
```
That's it. Your `@mcp.tool`, `@mcp.resource`, and `@mcp.prompt` decorators, your `mcp.run()` call, and the rest of your server code all work as-is.
<Tip>
**Why upgrade?** FastMCP 1.0 pioneered the Pythonic MCP server experience, and we're proud it was bundled into the `mcp` package. The standalone FastMCP project has since grown into a full framework for taking MCP servers from prototype to production — with composition, middleware, proxy servers, authentication, and much more. Upgrading gives you access to all of that, plus ongoing updates and fixes.
</Tip>
## Install
```bash
pip install --upgrade fastmcp
# or
uv add fastmcp
```
FastMCP includes the `mcp` package as a dependency, so you don't lose access to anything. Update your import, run your server, and if your tools work, you're done.
<Prompt description="Copy this prompt into any LLM along with your server code to get automated upgrade guidance.">
You are upgrading an MCP server from FastMCP 1.0 (bundled in the `mcp` package v1) to standalone FastMCP 3.0. Analyze the provided code and identify every change needed. The full upgrade guide is at https://gofastmcp.com/getting-started/upgrading/from-mcp-sdk and the complete FastMCP documentation is at https://gofastmcp.com — fetch these for complete context.
STEP 1 — IMPORT (required for all servers):
Change "from mcp.server.fastmcp import FastMCP" to "from fastmcp import FastMCP".
STEP 2 — CONSTRUCTOR KWARGS (only if FastMCP() receives transport settings):
FastMCP() no longer accepts: host, port, log_level, debug, sse_path, streamable_http_path, json_response, stateless_http.
Fix: pass these to run() instead.
Before: `mcp = FastMCP("server", host="0.0.0.0", port=8080); mcp.run()`
After: `mcp = FastMCP("server"); mcp.run(transport="http", host="0.0.0.0", port=8080)`
STEP 3 — PROMPTS (only if using PromptMessage directly):
mcp.types.PromptMessage is replaced by fastmcp.prompts.Message.
Before: `PromptMessage(role="user", content=TextContent(type="text", text="Hello"))`
After: `Message("Hello")` — role defaults to "user", accepts plain strings.
STEP 4 — OTHER MCP IMPORTS (only if importing from mcp.* directly):
Direct imports from the `mcp` package (e.g., `import mcp.types`, `from mcp.server.stdio import stdio_server`) still work because FastMCP includes `mcp` as a dependency. However, prefer FastMCP's own APIs where equivalents exist:
- mcp.types.TextContent for tool returns → just return plain Python values (str, int, dict, etc.)
- mcp.types.ImageContent → fastmcp.utilities.types.Image
- from mcp.server.stdio import stdio_server → not needed, mcp.run() handles transport
STEP 5 — DECORATORS (only if treating decorated functions as objects):
@mcp.tool, @mcp.resource, @mcp.prompt now return the original function, not a component object. Code that accesses .name or .description on the decorated result needs updating. Set FASTMCP_DECORATOR_MODE=object temporarily to restore v1 behavior (this compat setting is itself deprecated).
For each issue found, show the original line, explain what changed, and provide the corrected code.
</Prompt>
## What Might Need Updating
Most servers need nothing beyond the import change. Skim the sections below to see if any apply.
### Constructor Settings
If you passed transport settings like `host` or `port` directly to `FastMCP()`, those now belong on `run()`. This keeps your server definition independent of how it's deployed:
```python
# Before
mcp = FastMCP("my-server", host="0.0.0.0", port=8080)
mcp.run()
# After
mcp = FastMCP("my-server")
mcp.run(transport="http", host="0.0.0.0", port=8080)
```
If you pass the old kwargs, you'll get a clear `TypeError` with a migration hint.
### Prompts
If your prompt functions return `mcp.types.PromptMessage` objects, you can upgrade to FastMCP's simpler `Message` class. Or just return a plain string — it's automatically wrapped as a user message:
```python
from fastmcp import FastMCP
mcp = FastMCP("prompts")
@mcp.prompt
def review(code: str) -> str:
"""Review code for issues"""
return f"Please review this code:\n\n{code}"
```
For multi-turn prompts:
```python
from fastmcp.prompts import Message
@mcp.prompt
def debug(error: str) -> list[Message]:
"""Start a debugging session"""
return [
Message(f"I'm seeing this error:\n\n{error}"),
Message("I'll help debug that. Can you share the relevant code?", role="assistant"),
]
```
### Other `mcp.*` Imports
If your server imports directly from the `mcp` package — like `import mcp.types` or `from mcp.server.stdio import stdio_server` — those still work. FastMCP includes `mcp` as a dependency, so nothing breaks.
Where FastMCP provides its own API for the same thing, it's worth switching over:
| mcp Package | FastMCP Equivalent |
|---|---|
| `mcp.types.TextContent(type="text", text=str(x))` | Just return `x` from your tool |
| `mcp.types.ImageContent(...)` | `from fastmcp.utilities.types import Image` |
| `mcp.types.PromptMessage(...)` | `from fastmcp.prompts import Message` |
| `from mcp.server.stdio import stdio_server` | Not needed — `mcp.run()` handles transport |
For anything without a FastMCP equivalent (e.g., specific protocol types you use directly), the `mcp.*` import is fine to keep.
### Decorated Functions
In FastMCP 1.0, `@mcp.tool` returned a `FunctionTool` object. Now decorators return your original function unchanged — so decorated functions stay callable for testing, reuse, and composition:
```python
@mcp.tool
def greet(name: str) -> str:
"""Greet someone"""
return f"Hello, {name}!"
# This works now — the function is still a regular function
assert greet("World") == "Hello, World!"
```
If you have code that accesses `.name`, `.description`, or other attributes on the decorated result, that will need updating. This is uncommon — most servers don't interact with the tool object directly. If you need the old behavior temporarily, set `FASTMCP_DECORATOR_MODE=object` to restore it (this compatibility setting is itself deprecated and will be removed in a future release).
## Verify the Upgrade
```bash
# Install
pip install --upgrade fastmcp
# Check version
fastmcp version
# Run your server
python my_server.py
```
You can also inspect your server's registered components with the FastMCP CLI:
```bash
fastmcp inspect my_server.py
```
## Looking Ahead
The MCP ecosystem is evolving fast. Part of FastMCP's job is to absorb that complexity on your behalf — as the protocol and its tooling grow, we do the work so your server code doesn't have to change.

View file

@ -43,7 +43,7 @@ The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) lets you gi
FastMCP handles all of it. Declare a tool with a Python function, and the schema, validation, and documentation are generated automatically. Connect to a server with a URL, and transport negotiation, authentication, and protocol lifecycle are managed for you. You focus on your logic, and the MCP part just works: **with FastMCP, best practices are built in.**
**That's why FastMCP is the standard framework for working with MCP.** FastMCP 1.0 was incorporated into the official MCP SDK in 2024. Today, the actively maintained standalone project is downloaded a million times a day, and some version of FastMCP powers 70% of MCP servers across all languages.
**That's why FastMCP is the standard framework for working with MCP.** FastMCP 1.0 was incorporated into the official MCP Python SDK in 2024. Today, the actively maintained standalone project is downloaded a million times a day, and some version of FastMCP powers 70% of MCP servers across all languages.
FastMCP has three pillars: