From 1eb57ba262ec9489f224d1598526fc0e8220d642 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:30:07 -0500
Subject: [PATCH] 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
---
docs/changelog.mdx | 2 +-
docs/docs.json | 26 +-
docs/getting-started/installation.mdx | 20 +-
.../upgrading/from-fastmcp-2.mdx} | 36 +-
.../upgrading/from-low-level-sdk.mdx | 593 ++++++++++++++++++
.../upgrading/from-mcp-sdk.mdx | 165 +++++
docs/getting-started/welcome.mdx | 2 +-
7 files changed, 820 insertions(+), 24 deletions(-)
rename docs/{development/upgrade-guide.mdx => getting-started/upgrading/from-fastmcp-2.mdx} (89%)
create mode 100644 docs/getting-started/upgrading/from-low-level-sdk.mdx
create mode 100644 docs/getting-started/upgrading/from-mcp-sdk.mdx
diff --git a/docs/changelog.mdx b/docs/changelog.mdx
index 9dc246f28..3fb0d7e0e 100644
--- a/docs/changelog.mdx
+++ b/docs/changelog.mdx
@@ -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 🎉
diff --git a/docs/docs.json b/docs/docs.json
index f0b08380e..4732cf9f3 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -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": {
diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx
index 1815d177b..657b995fc 100644
--- a/docs/getting-started/installation.mdx
+++ b/docs/getting-started/installation.mdx
@@ -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")
-```
-
-
-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.
-
+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
diff --git a/docs/development/upgrade-guide.mdx b/docs/getting-started/upgrading/from-fastmcp-2.mdx
similarity index 89%
rename from docs/development/upgrade-guide.mdx
rename to docs/getting-started/upgrading/from-fastmcp-2.mdx
index ef21caac7..91fac0c97 100644
--- a/docs/development/upgrade-guide.mdx
+++ b/docs/getting-started/upgrading/from-fastmcp-2.mdx
@@ -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`.
+
-**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.
-
-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.
+
+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.
diff --git a/docs/getting-started/upgrading/from-low-level-sdk.mdx b/docs/getting-started/upgrading/from-low-level-sdk.mdx
new file mode 100644
index 000000000..e2be2630e
--- /dev/null
+++ b/docs/getting-started/upgrading/from-low-level-sdk.mdx
@@ -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.
+
+
+This guide covers upgrading from **v1** of the `mcp` package. We'll provide a separate guide when v2 ships.
+
+
+
+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.
+
+
+
+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.
+
+
+## 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.
+
+
+
+```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()
+```
+
+
+
+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.
+
+
+
+```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
+```
+
+
+
+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.
+
+
+
+```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"})
+```
+
+
+
+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.
+
+
+
+```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}"
+```
+
+
+
+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.
+
+
+
+```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"
+```
+
+
+
+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:
+
+
+
+```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()
+```
+
+
+
+## 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).
diff --git a/docs/getting-started/upgrading/from-mcp-sdk.mdx b/docs/getting-started/upgrading/from-mcp-sdk.mdx
new file mode 100644
index 000000000..ddf9c9c81
--- /dev/null
+++ b/docs/getting-started/upgrading/from-mcp-sdk.mdx
@@ -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.
+
+
+**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.
+
+
+## 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.
+
+
+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.
+
+
+## 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.
diff --git a/docs/getting-started/welcome.mdx b/docs/getting-started/welcome.mdx
index 4f5d8af9e..9ffd0f7d3 100644
--- a/docs/getting-started/welcome.mdx
+++ b/docs/getting-started/welcome.mdx
@@ -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: