chore: import upstream snapshot with attribution
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Install FastMCP and verify your setup
|
||||
icon: arrow-down-to-line
|
||||
---
|
||||
## Install FastMCP
|
||||
|
||||
We recommend using [uv](https://docs.astral.sh/uv/getting-started/installation/) to install and manage FastMCP.
|
||||
|
||||
```bash
|
||||
pip install fastmcp
|
||||
```
|
||||
|
||||
Or with uv:
|
||||
|
||||
```bash
|
||||
uv add fastmcp
|
||||
```
|
||||
|
||||
### Optional Dependencies
|
||||
|
||||
FastMCP provides optional extras for specific features. For example, to install the background tasks extra:
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[tasks]"
|
||||
```
|
||||
|
||||
See [Background Tasks](/servers/tasks) for details on the task system.
|
||||
|
||||
### Verify Installation
|
||||
|
||||
To verify that FastMCP is installed correctly, you can run the following command:
|
||||
|
||||
```bash
|
||||
fastmcp version
|
||||
```
|
||||
|
||||
You should see output like the following:
|
||||
|
||||
```bash
|
||||
$ fastmcp version
|
||||
|
||||
FastMCP version: 3.0.0
|
||||
MCP version: 1.25.0
|
||||
Python version: 3.12.2
|
||||
Platform: macOS-15.3.1-arm64-arm-64bit
|
||||
FastMCP root path: ~/Developer/fastmcp
|
||||
```
|
||||
|
||||
### Dependency Licensing
|
||||
|
||||
<Info>
|
||||
FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations.
|
||||
|
||||
If this is a concern, you can install Cyclopts v5 alpha which removes this dependency:
|
||||
|
||||
```bash
|
||||
pip install "cyclopts>=5.0.0a1"
|
||||
```
|
||||
|
||||
Alternatively, wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details.
|
||||
</Info>
|
||||
## Upgrading
|
||||
|
||||
### From FastMCP 2.0
|
||||
|
||||
See the [Upgrade Guide](/getting-started/upgrading/from-fastmcp-2) for a complete list of breaking changes and migration steps.
|
||||
|
||||
### From the MCP SDK
|
||||
|
||||
#### From FastMCP 1.0
|
||||
|
||||
If you're using FastMCP 1.0 via the `mcp` package (meaning you import FastMCP as `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.
|
||||
|
||||
#### From the Low-Level Server API
|
||||
|
||||
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.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `import fastmcp` fails after a pip upgrade
|
||||
|
||||
This affects one specific case: upgrading to FastMCP 3.3 or later from FastMCP 3.2 or earlier with `pip`. Fresh installs and `uv` upgrades are unaffected, so you can skip this unless you did exactly that.
|
||||
|
||||
If `import fastmcp` raises `ModuleNotFoundError`, or `from fastmcp import FastMCP` raises `ImportError`, immediately after the upgrade, your install is in a half-removed state. Reinstall in a single step:
|
||||
|
||||
```bash
|
||||
pip install --force-reinstall fastmcp
|
||||
```
|
||||
|
||||
If that doesn't resolve it, remove both distributions and reinstall from a clean state:
|
||||
|
||||
```bash
|
||||
pip uninstall -y fastmcp fastmcp-slim
|
||||
pip install fastmcp
|
||||
```
|
||||
|
||||
FastMCP 3.3 moved the importable code from the `fastmcp` distribution into `fastmcp-slim`. During a single-command `pip` upgrade, pip can install the new files and then delete them while uninstalling the old `fastmcp` distribution, whose file manifest still lists those paths. `uv` uninstalls before it installs, so it is unaffected.
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
FastMCP follows semantic versioning with pragmatic adaptations for the rapidly evolving MCP ecosystem. Breaking changes may occur in minor versions (e.g., 2.3.x to 2.4.0) when necessary to stay current with the MCP Protocol.
|
||||
|
||||
For production use, always pin to exact versions:
|
||||
```
|
||||
fastmcp==3.0.0 # Good
|
||||
fastmcp>=3.0.0 # Bad - may install breaking changes
|
||||
```
|
||||
|
||||
See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy.
|
||||
|
||||
## Contributing to FastMCP
|
||||
|
||||
Interested in contributing to FastMCP? See the [Contributing Guide](/development/contributing) for details on:
|
||||
- Setting up your development environment
|
||||
- Running tests and pre-commit hooks
|
||||
- Submitting issues and pull requests
|
||||
- Code standards and review process
|
||||
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Quickstart
|
||||
icon: rocket-launch
|
||||
---
|
||||
|
||||
Welcome! This guide will help you quickly set up FastMCP, run your first MCP server, give it a visual UI, and deploy it to Prefect Horizon.
|
||||
|
||||
If you haven't already installed FastMCP, follow the [installation instructions](/getting-started/installation).
|
||||
|
||||
## Create a FastMCP Server
|
||||
|
||||
A FastMCP server is a collection of tools, resources, and other MCP components. To create a server, start by instantiating the `FastMCP` class.
|
||||
|
||||
Create a new file called `my_server.py` and add the following code:
|
||||
|
||||
```python my_server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
```
|
||||
|
||||
|
||||
That's it! You've created a FastMCP server, albeit a very boring one. Let's add a tool to make it more interesting.
|
||||
|
||||
|
||||
## Add a Tool
|
||||
|
||||
To add a tool that returns a simple greeting, write a function and decorate it with `@mcp.tool` to register it with the server:
|
||||
|
||||
```python my_server.py {5-7}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
|
||||
## Run the Server
|
||||
|
||||
The simplest way to run your FastMCP server is to call its `run()` method. You can choose between different transports, like `stdio` for local servers, or `http` for remote access:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python my_server.py (stdio) {9, 10}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
```python my_server.py (HTTP) {9, 10}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
This lets us run the server with `python my_server.py`. The stdio transport is the traditional way to connect MCP servers to clients, while the HTTP transport enables remote connections.
|
||||
|
||||
<Tip>
|
||||
Why do we need the `if __name__ == "__main__":` block?
|
||||
|
||||
The `__main__` block is recommended for consistency and compatibility, ensuring your server works with all MCP clients that execute your server file as a script. Users who will exclusively run their server with the FastMCP CLI can omit it, as the CLI imports the server object directly.
|
||||
</Tip>
|
||||
|
||||
### Using the FastMCP CLI
|
||||
|
||||
You can also use the `fastmcp run` command to start your server. Note that the FastMCP CLI **does not** execute the `__main__` block of your server file. Instead, it imports your server object and runs it with whatever transport and options you provide.
|
||||
|
||||
For example, to run this server with the default stdio transport (no matter how you called `mcp.run()`), you can use the following command:
|
||||
```bash
|
||||
fastmcp run my_server.py:mcp
|
||||
```
|
||||
|
||||
To run this server with the HTTP transport, you can use the following command:
|
||||
```bash
|
||||
fastmcp run my_server.py:mcp --transport http --port 8000
|
||||
```
|
||||
|
||||
## Call Your Server
|
||||
|
||||
Once your server is running with HTTP transport, you can connect to it with a FastMCP client or any LLM client that supports the MCP protocol:
|
||||
|
||||
```python my_client.py
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
client = Client("http://localhost:8000/mcp")
|
||||
|
||||
async def call_tool(name: str):
|
||||
async with client:
|
||||
result = await client.call_tool("greet", {"name": name})
|
||||
print(result)
|
||||
|
||||
asyncio.run(call_tool("Ford"))
|
||||
```
|
||||
|
||||
Note that:
|
||||
- FastMCP clients are asynchronous, so we need to use `asyncio.run` to run the client
|
||||
- We must enter a client context (`async with client:`) before using the client
|
||||
- You can make multiple client calls within the same context
|
||||
|
||||
## Give Your Tool a UI
|
||||
|
||||
Tools normally return text, but any tool can return an interactive UI instead. Add `app=True` to your tool decorator and return a [Prefab](https://prefab.prefect.io) component — the host renders it as a chart, table, form, or any other visual element right in the conversation. This requires the `apps` extra (`pip install "fastmcp[apps]"`).
|
||||
|
||||
The `app=True` flag tells FastMCP to wire up the renderer and protocol metadata automatically. The tool still works like any other MCP tool — it receives arguments and returns a result — but the result is a component tree that the host displays visually instead of as plain text.
|
||||
|
||||
```python my_server.py
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, Text, Badge, Row
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def greet(name: str) -> PrefabApp:
|
||||
"""Greet someone with a visual card."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(f"Hello, {name}!")
|
||||
with Row(gap=2, align="center"):
|
||||
Text("Status")
|
||||
Badge("Greeted", variant="success")
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
You can preview app tools locally with `fastmcp dev apps my_server.py` — no MCP host required. See the [Apps overview](/apps/overview) for the full guide, including state management, forms, charts, and server-connected interactivity.
|
||||
|
||||
## Deploy to Prefect Horizon
|
||||
|
||||
[Prefect Horizon](https://horizon.prefect.io?utm_source=gofastmcp&utm_medium=docs) is the enterprise MCP platform built by the FastMCP team at [Prefect](https://www.prefect.io). It provides managed hosting, authentication, access control, and observability for MCP servers.
|
||||
|
||||
<Info>
|
||||
Horizon is **free for personal projects** and offers enterprise governance for teams.
|
||||
</Info>
|
||||
|
||||
To deploy your server, you'll need a [GitHub account](https://github.com). Once you have one, you can deploy your server in three steps:
|
||||
|
||||
1. Push your `my_server.py` file to a GitHub repository
|
||||
2. Sign in to [Prefect Horizon](https://horizon.prefect.io?utm_source=gofastmcp&utm_medium=docs) with your GitHub account
|
||||
3. Create a new project from your repository and enter `my_server.py:mcp` as the server entrypoint
|
||||
|
||||
That's it! Horizon will build and deploy your server, making it available at a URL like `https://your-project.fastmcp.app/mcp`. You can chat with it to test its functionality, or connect to it from any LLM client that supports the MCP protocol.
|
||||
|
||||
For more details, see the [Prefect Horizon guide](/deployment/prefect-horizon).
|
||||
@@ -0,0 +1,450 @@
|
||||
---
|
||||
title: Upgrading from FastMCP 2
|
||||
sidebarTitle: "From FastMCP 2"
|
||||
description: Migration instructions for upgrading between FastMCP versions
|
||||
icon: up
|
||||
---
|
||||
|
||||
This guide covers breaking changes and migration steps when upgrading FastMCP.
|
||||
|
||||
## v3.0.0
|
||||
|
||||
For most servers, upgrading to v3 is straightforward. The breaking changes below affect deprecated constructor kwargs, sync-to-async shifts, a few renamed methods, and some less commonly used features.
|
||||
|
||||
### 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 --upgrade fastmcp
|
||||
```
|
||||
|
||||
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 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
|
||||
```
|
||||
|
||||
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 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):
|
||||
|
||||
1. CONSTRUCTOR KWARGS REMOVED: FastMCP() no longer accepts these kwargs (raises TypeError):
|
||||
- Transport settings: host, port, log_level, debug, sse_path, streamable_http_path, json_response, stateless_http
|
||||
Fix: pass to run() or run_http_async() instead, e.g. mcp.run(transport="http", host="0.0.0.0", port=8080)
|
||||
- message_path: set via environment variable FASTMCP_MESSAGE_PATH only (not a run() kwarg)
|
||||
- Duplicate handling: on_duplicate_tools, on_duplicate_resources, on_duplicate_prompts
|
||||
Fix: use unified on_duplicate= parameter
|
||||
- Tool settings: tool_serializer, include_tags, exclude_tags, tool_transformations
|
||||
Fix: use ToolResult returns, server.enable()/disable(), server.add_transform()
|
||||
|
||||
2. COMPONENT METHODS REMOVED:
|
||||
- tool.enable()/disable() raises NotImplementedError
|
||||
Fix: server.disable(names={"tool_name"}, components={"tool"}) or server.disable(tags={"tag"})
|
||||
- get_tools()/get_resources()/get_prompts()/get_resource_templates() removed
|
||||
Fix: use list_tools()/list_resources()/list_prompts()/list_resource_templates() — these return lists, not dicts
|
||||
|
||||
3. ASYNC STATE: ctx.set_state() and ctx.get_state() are now async (must be awaited).
|
||||
State values must be JSON-serializable unless serializable=False is passed.
|
||||
Each FastMCP instance has its own state store, so serializable state set by parent middleware isn't visible to mounted tools by default.
|
||||
Fix: pass the same session_state_store to both servers, or use serializable=False (request-scoped state is always shared).
|
||||
|
||||
4. PROMPTS: mcp.types.PromptMessage 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
|
||||
Also: if prompts return raw dicts like `{"role": "user", "content": "..."}`, these must become Message objects.
|
||||
v2 silently coerced dicts; v3 requires typed Message objects or plain strings.
|
||||
|
||||
5. AUTH PROVIDERS: No longer auto-load from env vars. Pass client_id, client_secret explicitly via os.environ.
|
||||
|
||||
6. WSTRANSPORT: Removed. Use StreamableHttpTransport.
|
||||
|
||||
7. OPENAPI: timeout parameter removed from OpenAPIProvider. Set timeout on the httpx.AsyncClient instead.
|
||||
|
||||
8. METADATA: Namespace changed from "_fastmcp" to "fastmcp" in tool.meta. The include_fastmcp_meta parameter is removed (always included).
|
||||
|
||||
9. ENV VAR: FASTMCP_SHOW_CLI_BANNER renamed to FASTMCP_SHOW_SERVER_BANNER.
|
||||
|
||||
10. DECORATORS: @mcp.tool, @mcp.resource, @mcp.prompt now return the original function, not a component object. Code that accesses .name, .description, or other component attributes on the decorated result will crash with AttributeError.
|
||||
Fix: access component objects via the server (e.g. await mcp.get_tool("name")) instead of the decorated function. The FASTMCP_DECORATOR_MODE=object escape hatch that existed in v3 was removed in FastMCP 4.0.
|
||||
|
||||
11. 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 (with key/collection sanitization strategies) or add pip install 'py-key-value-aio[disk]'.
|
||||
|
||||
12. REPO MOVE: GitHub repository moved from jlowin/fastmcp to PrefectHQ/fastmcp. Update git remotes and dependency URLs that reference the old location.
|
||||
|
||||
13. 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")
|
||||
- import_server(sub) -> mount(sub)
|
||||
- FastMCP.as_proxy(url) -> from fastmcp.server import create_proxy; create_proxy(url)
|
||||
- from fastmcp.server.proxy -> from fastmcp.server.providers.proxy
|
||||
- from fastmcp.server.openapi import FastMCPOpenAPI -> from fastmcp.server.providers.openapi import OpenAPIProvider; use FastMCP("name", providers=[OpenAPIProvider(...)])
|
||||
- mcp.add_tool_transformation(name, cfg) -> from fastmcp.server.transforms import ToolTransform; mcp.add_transform(ToolTransform(...))
|
||||
|
||||
For each issue found, show the original line, explain why it breaks, and provide the corrected code.
|
||||
</Prompt>
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**Transport and server settings removed from constructor**
|
||||
|
||||
In v2, you could configure transport settings directly in the `FastMCP()` constructor. In v3, `FastMCP()` is purely about your server's identity and behavior — transport configuration happens when you actually start serving. Passing any of the old kwargs now raises `TypeError` with a migration hint.
|
||||
|
||||
```python
|
||||
# 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)
|
||||
```
|
||||
|
||||
The full list of removed kwargs and their replacements:
|
||||
|
||||
- `host`, `port`, `log_level`, `debug`, `sse_path`, `streamable_http_path`, `json_response`, `stateless_http` — pass to `run()`, `run_http_async()`, or `http_app()`, or set via environment variables (e.g. `FASTMCP_HOST`)
|
||||
- `message_path` — set via environment variable `FASTMCP_MESSAGE_PATH` only (not a `run()` kwarg)
|
||||
- `on_duplicate_tools`, `on_duplicate_resources`, `on_duplicate_prompts` — consolidated into a single `on_duplicate=` parameter
|
||||
- `tool_serializer` — return [`ToolResult`](/servers/tools#custom-serialization) from your tools instead
|
||||
- `include_tags` / `exclude_tags` — use `server.enable(tags=..., only=True)` / `server.disable(tags=...)` after construction
|
||||
- `tool_transformations` — use `server.add_transform(ToolTransform(...))` after construction
|
||||
|
||||
**OAuth storage backend changed (diskcache CVE)**
|
||||
|
||||
The default OAuth client storage has moved from `DiskStore` to `FileTreeStore` to address a pickle deserialization vulnerability in diskcache ([CVE-2025-69872](https://github.com/PrefectHQ/fastmcp/issues/3166)).
|
||||
|
||||
If you were using the default storage (i.e., not passing an explicit `client_storage`), clients will need to re-register on their first connection after upgrading. This happens automatically — no user action required, and it's the same flow that already occurs whenever a server restarts with in-memory storage.
|
||||
|
||||
If you were passing a `DiskStore` explicitly, you can either [switch to `FileTreeStore`](/servers/storage-backends) (recommended) or keep using `DiskStore` by adding the dependency yourself.
|
||||
|
||||
<Warning>
|
||||
When switching to `FileTreeStore`, you **must** configure key and collection sanitization strategies. Without them, keys containing special characters (such as URL-based OAuth client IDs) will cause filesystem errors. See the [File Storage](/servers/storage-backends#file-storage) section for the recommended setup.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
Keeping `DiskStore` requires `pip install 'py-key-value-aio[disk]'`, which re-introduces the vulnerable `diskcache` package into your dependency tree.
|
||||
</Warning>
|
||||
|
||||
**Component enable()/disable() moved to server**
|
||||
|
||||
In v2, you could enable or disable individual components by calling methods on the component object itself. In v3, visibility is controlled through the server (or provider), which lets you target components by name, tag, or type without needing a reference to the object:
|
||||
|
||||
```python
|
||||
# Before
|
||||
tool = await server.get_tool("my_tool")
|
||||
tool.disable()
|
||||
|
||||
# After
|
||||
server.disable(names={"my_tool"}, components={"tool"})
|
||||
```
|
||||
|
||||
Calling `.enable()` or `.disable()` on a component object now raises `NotImplementedError`. See [Visibility](/servers/visibility) for the full API, including tag-based filtering and per-session visibility.
|
||||
|
||||
**Listing methods renamed and return lists**
|
||||
|
||||
The `get_tools()`, `get_resources()`, `get_prompts()`, and `get_resource_templates()` methods have been renamed to `list_tools()`, `list_resources()`, `list_prompts()`, and `list_resource_templates()`. More importantly, they now return lists instead of dicts — so code that indexes by name needs to change:
|
||||
|
||||
```python
|
||||
# Before
|
||||
tools = await server.get_tools()
|
||||
tool = tools["my_tool"]
|
||||
|
||||
# After
|
||||
tools = await server.list_tools()
|
||||
tool = next((t for t in tools if t.name == "my_tool"), None)
|
||||
```
|
||||
|
||||
**Prompts use Message class**
|
||||
|
||||
Prompt functions now use FastMCP's `Message` class instead of `mcp.types.PromptMessage`. The new class is simpler — it accepts a plain string and defaults to `role="user"`, so most prompts become one-liners:
|
||||
|
||||
```python
|
||||
# Before
|
||||
from fastmcp.types import PromptMessage, TextContent
|
||||
|
||||
@mcp.prompt
|
||||
def my_prompt() -> PromptMessage:
|
||||
return PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
|
||||
|
||||
# After
|
||||
from fastmcp.prompts import Message
|
||||
|
||||
@mcp.prompt
|
||||
def my_prompt() -> Message:
|
||||
return Message("Hello")
|
||||
```
|
||||
|
||||
If your prompt functions return raw dicts with `role` and `content` keys, those also need to change. v2 silently coerced dicts into prompt messages, but v3 requires typed `Message` objects (or plain strings for single user messages):
|
||||
|
||||
```python
|
||||
# Before (v2 accepted this)
|
||||
@mcp.prompt
|
||||
def my_prompt():
|
||||
return [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "How can I help?"},
|
||||
]
|
||||
|
||||
# After
|
||||
from fastmcp.prompts import Message
|
||||
|
||||
@mcp.prompt
|
||||
def my_prompt() -> list[Message]:
|
||||
return [
|
||||
Message("Hello"),
|
||||
Message("How can I help?", role="assistant"),
|
||||
]
|
||||
```
|
||||
|
||||
**Context state methods are async**
|
||||
|
||||
`ctx.set_state()` and `ctx.get_state()` are now async because state in v3 is session-scoped and backed by a pluggable storage backend (rather than a simple dict). This means state persists across multiple tool calls within the same session:
|
||||
|
||||
```python
|
||||
# Before
|
||||
ctx.set_state("key", "value")
|
||||
value = ctx.get_state("key")
|
||||
|
||||
# After
|
||||
await ctx.set_state("key", "value")
|
||||
value = await ctx.get_state("key")
|
||||
```
|
||||
|
||||
State values must also be JSON-serializable by default (dicts, lists, strings, numbers, etc.). If you need to store non-serializable values like an HTTP client, pass `serializable=False` — these values are request-scoped and only available during the current tool call:
|
||||
|
||||
```python
|
||||
await ctx.set_state("client", my_http_client, serializable=False)
|
||||
```
|
||||
|
||||
**Mounted servers have isolated state stores**
|
||||
|
||||
Each `FastMCP` instance has its own state store. In v2 this wasn't noticeable because mounted tools ran in the parent's context, but in v3's provider architecture each server is isolated. Non-serializable state (`serializable=False`) is request-scoped and automatically shared across mount boundaries. For serializable state, pass the same `session_state_store` to both servers:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from key_value.aio.stores.memory import MemoryStore
|
||||
|
||||
store = MemoryStore()
|
||||
parent = FastMCP("Parent", session_state_store=store)
|
||||
child = FastMCP("Child", session_state_store=store)
|
||||
parent.mount(child, namespace="child")
|
||||
```
|
||||
|
||||
**Auth provider environment variables removed**
|
||||
|
||||
In v2, auth providers like `GitHubProvider` could auto-load configuration from environment variables with a `FASTMCP_SERVER_AUTH_*` prefix. This magic has been removed — pass values explicitly:
|
||||
|
||||
```python
|
||||
# Before (v2) — client_id and client_secret loaded automatically
|
||||
# from FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID, etc.
|
||||
auth = GitHubProvider()
|
||||
|
||||
# After (v3) — pass values explicitly
|
||||
import os
|
||||
from fastmcp.server.auth.providers.github import GitHubProvider
|
||||
|
||||
auth = GitHubProvider(
|
||||
client_id=os.environ["GITHUB_CLIENT_ID"],
|
||||
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
|
||||
)
|
||||
```
|
||||
|
||||
**WSTransport removed**
|
||||
|
||||
The deprecated WebSocket client transport has been removed. Use `StreamableHttpTransport` instead:
|
||||
|
||||
```python test="skip"
|
||||
# Before
|
||||
from fastmcp.client.transports import WSTransport
|
||||
transport = WSTransport("ws://localhost:8000/ws")
|
||||
|
||||
# After
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
transport = StreamableHttpTransport("http://localhost:8000/mcp")
|
||||
```
|
||||
|
||||
**OpenAPI `timeout` parameter removed**
|
||||
|
||||
`OpenAPIProvider` no longer accepts a `timeout` parameter. Configure timeout on the httpx client directly. The `client` parameter is also now optional — when omitted, a default client is created from the spec's `servers` URL with a 30-second timeout:
|
||||
|
||||
```python
|
||||
# Before
|
||||
provider = OpenAPIProvider(spec, client, timeout=60)
|
||||
|
||||
# After
|
||||
client = httpx.AsyncClient(base_url="https://api.example.com", timeout=60)
|
||||
provider = OpenAPIProvider(spec, client)
|
||||
```
|
||||
|
||||
**Metadata namespace renamed**
|
||||
|
||||
The FastMCP metadata key in component `meta` dicts changed from `_fastmcp` to `fastmcp`. If you read metadata from tool or resource objects, update the key:
|
||||
|
||||
```python
|
||||
# Before
|
||||
tags = tool.meta.get("_fastmcp", {}).get("tags", [])
|
||||
|
||||
# After
|
||||
tags = tool.meta.get("fastmcp", {}).get("tags", [])
|
||||
```
|
||||
|
||||
Metadata is now always included — the `include_fastmcp_meta` parameter has been removed from `FastMCP()` and `to_mcp_tool()`, so there is no way to suppress it.
|
||||
|
||||
**Server banner environment variable renamed**
|
||||
|
||||
`FASTMCP_SHOW_CLI_BANNER` is now `FASTMCP_SHOW_SERVER_BANNER`.
|
||||
|
||||
**Decorators return functions**
|
||||
|
||||
In v2, `@mcp.tool` transformed your function into a `FunctionTool` object. In v3, decorators return your original function unchanged — which means decorated functions stay callable for testing, reuse, and composition:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
greet("World") # Works! Returns "Hello, World!"
|
||||
```
|
||||
|
||||
If you have code that treats the decorated result as a `FunctionTool` (e.g., accessing `.name` or `.description`), the v2-compatible object-returning behavior was available in v3 via `FASTMCP_DECORATOR_MODE=object`. That escape hatch was removed in FastMCP 4.0 — decorators always return the original function now.
|
||||
|
||||
**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 were deprecated in v3. Items marked **Removed in v4** no longer work at all — update to the replacement shown. The rest still work but emit warnings; update when convenient.
|
||||
|
||||
**mount() prefix → namespace** (Removed in v4)
|
||||
|
||||
```python
|
||||
# Removed in v4
|
||||
main.mount(subserver, prefix="api")
|
||||
|
||||
# New
|
||||
main.mount(subserver, namespace="api")
|
||||
```
|
||||
|
||||
**import_server() → mount()** (Removed in v4)
|
||||
|
||||
```python
|
||||
# Removed in v4
|
||||
main.import_server(subserver)
|
||||
|
||||
# New
|
||||
main.mount(subserver)
|
||||
```
|
||||
|
||||
**Module import paths for proxy and OpenAPI**
|
||||
|
||||
The proxy and OpenAPI modules moved under `providers` to reflect v3's provider-based architecture. The old `fastmcp.server.proxy` and `fastmcp.server.openapi` compatibility shims were **removed in 4.0** — import from the `providers` location instead:
|
||||
|
||||
```python test="skip"
|
||||
# Removed in 4.0
|
||||
from fastmcp.server.proxy import FastMCPProxy
|
||||
from fastmcp.server.openapi import FastMCPOpenAPI
|
||||
|
||||
# New
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy
|
||||
from fastmcp.server.providers.openapi import OpenAPIProvider
|
||||
```
|
||||
|
||||
`FastMCPOpenAPI` was **removed in 4.0** — use `FastMCP` with an `OpenAPIProvider` instead:
|
||||
|
||||
```python test="skip"
|
||||
# Removed in 4.0
|
||||
from fastmcp.server.openapi import FastMCPOpenAPI
|
||||
server = FastMCPOpenAPI(spec, client)
|
||||
|
||||
# New
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.providers.openapi import OpenAPIProvider
|
||||
server = FastMCP("my_api", providers=[OpenAPIProvider(spec, client)])
|
||||
```
|
||||
|
||||
**add_tool_transformation() → add_transform()** (Removed in v4)
|
||||
|
||||
```python
|
||||
# Removed in v4
|
||||
mcp.add_tool_transformation("name", config)
|
||||
|
||||
# New
|
||||
from fastmcp.server.transforms import ToolTransform
|
||||
mcp.add_transform(ToolTransform({"name": config}))
|
||||
```
|
||||
|
||||
**FastMCP.as_proxy() → create_proxy()** (Removed in v4)
|
||||
|
||||
The proxy target is passed positionally in both APIs, so most calls migrate unchanged. If you passed the target by keyword, note that the parameter was renamed from `backend=` to `target=`.
|
||||
|
||||
```python
|
||||
# Removed in v4
|
||||
proxy = FastMCP.as_proxy("http://example.com/mcp")
|
||||
proxy = FastMCP.as_proxy(backend="http://example.com/mcp") # keyword form
|
||||
|
||||
# New
|
||||
from fastmcp.server import create_proxy
|
||||
proxy = create_proxy("http://example.com/mcp")
|
||||
proxy = create_proxy(target="http://example.com/mcp") # as_proxy(backend=X) → create_proxy(target=X)
|
||||
```
|
||||
|
||||
## v2.14.0
|
||||
|
||||
### OpenAPI Parser Promotion
|
||||
|
||||
The experimental OpenAPI parser is now standard. The `fastmcp.experimental.server.openapi` and `fastmcp.server.openapi` shims were both **removed in 4.0** — use `FastMCP` with an `OpenAPIProvider` instead:
|
||||
|
||||
```python test="skip"
|
||||
# Before
|
||||
from fastmcp.experimental.server.openapi import FastMCPOpenAPI
|
||||
|
||||
# After (removed in 4.0 — use OpenAPIProvider)
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.providers.openapi import OpenAPIProvider
|
||||
server = FastMCP("my_api", providers=[OpenAPIProvider(spec, client)])
|
||||
```
|
||||
|
||||
### Removed Deprecated Features
|
||||
|
||||
- `BearerAuthProvider` → use `JWTVerifier`
|
||||
- `Context.get_http_request()` → use `get_http_request()` from dependencies
|
||||
- `from fastmcp import Image` → use `from fastmcp.utilities.types import Image`
|
||||
- `FastMCP(dependencies=[...])` → use `fastmcp.json` configuration
|
||||
- `FastMCPProxy(client=...)` → use `client_factory=lambda: ...`
|
||||
- `output_schema=False` → use `output_schema=None`
|
||||
|
||||
## v2.13.0
|
||||
|
||||
### OAuth Token Key Management
|
||||
|
||||
The OAuth proxy now issues its own JWT tokens. For production, provide explicit keys:
|
||||
|
||||
```python
|
||||
auth = GitHubProvider(
|
||||
client_id=os.environ["GITHUB_CLIENT_ID"],
|
||||
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
|
||||
base_url="https://your-server.com",
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
client_storage=RedisStore(host="redis.example.com"),
|
||||
)
|
||||
```
|
||||
|
||||
See [OAuth Token Security](/deployment/http#oauth-token-security) for details.
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: Upgrading from FastMCP 3
|
||||
sidebarTitle: "From FastMCP 3.x"
|
||||
description: What changes when you upgrade to FastMCP 4, which builds on the MCP Python SDK v2
|
||||
icon: up
|
||||
---
|
||||
|
||||
FastMCP 4 builds on the MCP Python SDK v2, and that is the source of every change in this guide. The SDK v2 makes two sweeping changes to the protocol layer: it splits the protocol types out of `mcp.types` into a standalone `mcp_types` package, and it renames every protocol field from camelCase to snake_case (`inputSchema` → `input_schema`, `mimeType` → `mime_type`, `isError` → `is_error`, and so on).
|
||||
|
||||
FastMCP 4 absorbs almost all of this for you. Field access is bridged so your existing reads keep working, and the imports you were taught have a stable home in FastMCP itself. The sections below describe what FastMCP handles for you, the small number of changes you must make in your own code, and the deprecation timeline for the compatibility shims.
|
||||
|
||||
## Environment requirements
|
||||
|
||||
The SDK v2 raises FastMCP's dependency floors, which matters before any of your code runs.
|
||||
|
||||
**pydantic >= 2.12 is now the floor.** If your project pins an older pydantic (for example `pydantic==2.11.*`), installing this FastMCP release fails with an unsatisfiable-resolution error from your installer — bump your pin to `>=2.12` first. If you don't pin pydantic at all, installers upgrade it silently as part of the FastMCP upgrade.
|
||||
|
||||
**The server extra floors Starlette >= 1.0.** Modern FastAPI (0.11x and later) already runs on Starlette 1.x, so mounting a FastMCP server inside a FastAPI app coexists cleanly — verified with FastAPI 0.138.2. Only very old FastAPI versions pinned below Starlette 1.0 conflict; upgrade FastAPI if your resolver complains about Starlette.
|
||||
|
||||
## What FastMCP absorbs
|
||||
|
||||
### Legacy camelCase field access keeps working
|
||||
|
||||
Objects that FastMCP hands back to you — the results of `client.list_tools()`, `client.call_tool_mcp()`, `client.read_resource()`, and the parameter objects passed to your sampling and elicitation handlers — are SDK v2 objects with snake_case fields. FastMCP installs a compatibility bridge at import time that routes the old camelCase names to their new snake_case fields, so code written against FastMCP 2.x still reads correctly:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async with Client("my_mcp_server.py") as client:
|
||||
tools = await client.list_tools()
|
||||
schema = tools[0].inputSchema # still works, warns once
|
||||
```
|
||||
|
||||
Each bridged read emits a `FastMCPDeprecationWarning` pointing you at the snake_case name (`tools[0].input_schema` here). The bridge covers the fields users actually read: `inputSchema`/`outputSchema` on tools, `mimeType` on resources and content, `isError`/`structuredContent` on tool results, `nextCursor` on paginated results, `serverInfo`/`protocolVersion` on the initialize result, the sampling parameter fields (`systemPrompt`, `maxTokens`, `stopSequences`, `modelPreferences`, `toolChoice`), and `requestedSchema` on elicitation parameters.
|
||||
|
||||
The bridge is controlled by the `mcp_camelcase_compat` setting, which defaults to on. Set it to `False` (or the environment variable `FASTMCP_MCP_CAMELCASE_COMPAT=false`) to turn the shims off, in which case only the snake_case names resolve:
|
||||
|
||||
```python
|
||||
import fastmcp
|
||||
|
||||
fastmcp.settings.mcp_camelcase_compat = False
|
||||
```
|
||||
|
||||
See [Settings](/more/settings) for the full reference.
|
||||
|
||||
### Imports have a stable home
|
||||
|
||||
The `mcp.types` module no longer exists. FastMCP re-exports the protocol types you're most likely to use — `TextContent`, `ImageContent`, `Tool`, `ErrorData`, `Icon`, `PromptMessage`, `SamplingMessage`, `ToolAnnotations`, and around two dozen others — from `fastmcp.types`. Update your imports to point there:
|
||||
|
||||
```python
|
||||
from fastmcp.types import TextContent, Tool, ToolAnnotations
|
||||
```
|
||||
|
||||
For protocol types FastMCP does not re-export (notification and request wrapper types like `ToolListChangedNotification` or `ServerNotification`), import them from `mcp_types` directly:
|
||||
|
||||
```python
|
||||
import mcp_types
|
||||
|
||||
notification = mcp_types.ToolListChangedNotification()
|
||||
```
|
||||
|
||||
### `McpError` has an alias
|
||||
|
||||
`fastmcp.exceptions.McpError` is an alias of the SDK's `MCPError`. Catching errors is unchanged — `except McpError` still catches SDK-raised errors, and reading `err.error.code` still works:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import McpError
|
||||
|
||||
try:
|
||||
...
|
||||
except McpError as err:
|
||||
print(err.error.code)
|
||||
```
|
||||
|
||||
### Behavior preserved across the SDK boundary
|
||||
|
||||
A few client behaviors that touch the SDK are preserved so you don't have to change anything:
|
||||
|
||||
- `Client(timeout=...)` accepts both a `timedelta` and a plain float number of seconds, as before.
|
||||
- `client.ping()` returns a `bool`.
|
||||
- `client.transport.get_session_id()` returns `None` on protocol eras that have no session, rather than raising. (The SDK v2 removed session-id access from its streamable HTTP transport; FastMCP reconstructs it on the transport object.)
|
||||
|
||||
## What you must change
|
||||
|
||||
Three things are on you.
|
||||
|
||||
**Your own `mcp.types` imports.** FastMCP can re-export types, but it can't rewrite imports in your code. Any `from mcp.types import X` or `import mcp.types` in your server or client fails at import time with:
|
||||
|
||||
```
|
||||
ModuleNotFoundError: No module named 'mcp.types'
|
||||
```
|
||||
|
||||
The raw message gives no hint toward the fix, so if you see it after upgrading, this is why. Switch to `from fastmcp.types import X` for the common types, or `import mcp_types` for the rest.
|
||||
|
||||
**`McpError` construction.** The v1 pattern of wrapping an `ErrorData` and passing it positionally fails under SDK v2 with:
|
||||
|
||||
```
|
||||
TypeError: MCPError.__init__() missing 1 required positional argument: 'message'
|
||||
```
|
||||
|
||||
Note the message prints the class as `MCPError` (uppercase) even though your code wrote `McpError` — the old name is an alias for the SDK's renamed class. Construct the error with keyword arguments instead:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import McpError
|
||||
|
||||
# Before (raises TypeError under SDK v2):
|
||||
# raise McpError(ErrorData(code=-32000, message="Client not supported"))
|
||||
|
||||
# After:
|
||||
raise McpError(code=-32000, message="Client not supported")
|
||||
```
|
||||
|
||||
Catching and `err.error.code` are unchanged — only construction moved.
|
||||
|
||||
**Raw session access sees v2 objects.** If you reach past FastMCP's client and server surfaces into `client.session`, `ctx.session`, or the internals of `ctx.request_context`, you're now holding raw SDK v2 objects with snake_case fields and the v2 method signatures. FastMCP does not wrap these; code that depends on their v1 shape needs updating.
|
||||
|
||||
## Deprecation timeline
|
||||
|
||||
The camelCase bridge is a migration aid, not a permanent fixture. It works today and warns on every bridged read so you can find and update the affected call sites. Plan to migrate your reads to snake_case: the shims will be removed in a future release, after which only the snake_case names resolve — the same state you get today by setting `mcp_camelcase_compat = False`. Turning the setting off is a good way to surface every remaining camelCase read in your code as a hard `AttributeError` before the shims go away.
|
||||
|
||||
## SDK deprecation warnings you may see
|
||||
|
||||
Ordinary use of `ctx.info` (client logging) and `ctx.sample` now emits an SDK-level `MCPDeprecationWarning`:
|
||||
|
||||
```
|
||||
The logging/sampling capability is deprecated as of 2026-07-28 (SEP-2577)
|
||||
```
|
||||
|
||||
These warnings come from the MCP SDK, not from FastMCP. For logging they are benign: `ctx.info` keeps working on session-based connections exactly as the protocol table below describes, and the SDK is only signaling the protocol's direction. For sampling, FastMCP additionally emits its own `FastMCPDeprecationWarning`: `ctx.sample` and `ctx.sample_step` are deprecated and slated for removal, so treat that warning as a prompt to migrate to server-side LLM calls rather than as informational.
|
||||
|
||||
## Protocol version support
|
||||
|
||||
FastMCP servers built on the SDK v2 serve multiple protocol eras from the same server. The SDK negotiates the era each client speaks: the sessionless `2026-07-28` era (which discovers capabilities through `server/discover`) and earlier session-based handshake versions are all handled simultaneously. This formally supersedes FastMCP's earlier "latest protocol only" stance — a single server now works with clients across the protocol transition.
|
||||
|
||||
Not every Context feature is available on every era yet. The push-style interactions that require the server to call back into the client — elicitation, sampling, and listing roots — depend on the session-based request/response flow of the earlier eras. On a `2026-07-28` connection these raise a clear, era-aware error rather than reaching the client. Logging notifications and the request/response features flow on every era.
|
||||
|
||||
Sampling is the exception that does not come back. `ctx.sample` and `ctx.sample_step` are **deprecated** and will be removed in a future FastMCP release: server-initiated sampling was removed from the wire by SEP-2577, and unlike elicitation it has no multi-round-trip replacement (the agentic loop would exhaust the round-trip budget). The migration is to call an LLM directly from your server rather than borrowing the client's model. See [Sampling](/servers/sampling) for details.
|
||||
|
||||
| Context feature | Earlier eras (session-based) | `2026-07-28` (sessionless) |
|
||||
| --- | --- | --- |
|
||||
| `ctx.info` / logging notifications | Supported | Supported |
|
||||
| Tools, resources, prompts, completions | Supported | Supported |
|
||||
| `ctx.elicit` | Supported | Not yet — MRTR rewrite pending |
|
||||
| `ctx.sample` / `ctx.sample_step` | Supported (deprecated) | Removed — call an LLM server-side |
|
||||
| `ctx.list_roots` | Supported | Not yet — MRTR rewrite pending |
|
||||
| Tasks (via the FastMCP client) | Supported | Not yet |
|
||||
|
||||
If your tools rely on `ctx.elicit` or `ctx.list_roots`, they continue to work against clients on the earlier eras, and the sessionless replacements will expand this table as they land. Sampling is deprecated on every era and will not return on modern connections — migrate those tools to server-side LLM calls.
|
||||
@@ -0,0 +1,594 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
## Why now is the moment to switch
|
||||
|
||||
MCP SDK v2 landed sweeping breaking changes on the low-level `Server`: the protocol types moved out of `mcp.types` into a separate `mcp_types` package, every field was renamed from camelCase to snake_case, the `Server` class was rebuilt, `McpError` was renamed, and sessions were removed on the new sessionless protocol era. If you build directly on the low-level SDK, all of that lands on you — you have to rewrite your imports, your handler signatures, and your error construction to match the new surface.
|
||||
|
||||
Adopting FastMCP is the easier path. FastMCP 4 runs on SDK v2 and hides that entire surface behind a high-level API that did not change. You write `@mcp.tool` and never touch the renamed internals — FastMCP derives the protocol layer from your function signatures, so the SDK v2 rename simply isn't something your code has to know about. Migrating low-level-SDK-v1 code to FastMCP is less work than migrating it to raw SDK v2, and you come out the other side with the whole framework: composition, middleware, proxies, authentication, and testing. The SDK v2 break is the natural moment to make the jump.
|
||||
|
||||
<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 4. 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/composition)** — 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](/servers/testing)** — Test your server directly in Python without running a subprocess
|
||||
|
||||
Explore the full documentation at [gofastmcp.com](https://gofastmcp.com).
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
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 4. 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 or returning dicts):
|
||||
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.
|
||||
Also: if prompts return raw dicts like `{"role": "user", "content": "..."}`, these must become Message objects or plain strings.
|
||||
The MCP SDK's FastMCP 1.0 silently coerced dicts; standalone FastMCP requires typed returns.
|
||||
|
||||
STEP 4 — OTHER MCP IMPORTS (only if importing from mcp.* directly):
|
||||
FastMCP now builds on MCP SDK v2, which removed the `mcp.types` module — protocol types live in the standalone `mcp_types` package. FastMCP re-exports the common ones from `fastmcp.types`. Update any `from mcp.types import X` to `from fastmcp.types import X` (or `import mcp_types`). Prefer FastMCP's own APIs where equivalents exist:
|
||||
- fastmcp.types.TextContent for tool returns → just return plain Python values (str, int, dict, etc.)
|
||||
- fastmcp.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 or raw dicts with `role`/`content` keys, you'll need to upgrade to FastMCP's `Message` class. Or just return a plain string — it's automatically wrapped as a user message. The MCP SDK's bundled FastMCP 1.0 silently coerced dicts into messages; standalone FastMCP requires typed `Message` objects or strings.
|
||||
|
||||
```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
|
||||
|
||||
FastMCP now builds on MCP SDK v2. The `mcp.types` module no longer exists — protocol types moved to a standalone `mcp_types` package, and the field names were renamed from camelCase to snake_case (`inputSchema` → `input_schema`, `mimeType` → `mime_type`, and so on). FastMCP re-exports the types you're most likely to use from `fastmcp.types`, so update `from mcp.types import X` to `from fastmcp.types import X`. For the full picture, see [Upgrading from FastMCP 3](/getting-started/upgrading/from-fastmcp-3).
|
||||
|
||||
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 protocol types without a FastMCP equivalent, import them from `fastmcp.types` when re-exported there, otherwise from `mcp_types` directly.
|
||||
|
||||
### 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.
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: "Welcome to FastMCP"
|
||||
sidebarTitle: "Welcome!"
|
||||
description: The fast, Pythonic way to build MCP servers, clients, and applications.
|
||||
icon: hand-wave
|
||||
mode: center
|
||||
---
|
||||
{/* <img
|
||||
src="/assets/brand/f-watercolor-waves-4.png"
|
||||
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl block dark:hidden"
|
||||
/>
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves-4-dark.png"
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl hidden dark:block"
|
||||
/>
|
||||
|
||||
|
||||
*/}
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
className="rounded-2xl block dark:hidden"
|
||||
src="/assets/brand/f-watercolor-waves-4-animated.mp4"
|
||||
></video>
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
className="rounded-2xl hidden dark:block"
|
||||
src="/assets/brand/f-watercolor-waves-4-dark-animated.mp4"
|
||||
></video>
|
||||
|
||||
|
||||
**FastMCP is the standard framework for building MCP applications.** The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) connects LLMs to tools and data. FastMCP gives you everything you need to go from prototype to production — build servers that expose capabilities, connect clients to any MCP service, and give your tools interactive UIs:
|
||||
|
||||
```python {1}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Demo 🚀")
|
||||
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
|
||||
## Move Fast and Make Things
|
||||
|
||||
The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) lets you give agents access to your tools and data. But building an effective MCP application is harder than it looks.
|
||||
|
||||
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 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:
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Servers" img="/assets/images/servers-card.png" href="/servers/server">
|
||||
Expose tools, resources, and prompts to LLMs.
|
||||
</Card>
|
||||
<Card title="Apps" img="/assets/images/apps-card.png" href="/apps/overview">
|
||||
Give your tools interactive UIs rendered directly in the conversation.
|
||||
</Card>
|
||||
<Card title="Clients" img="/assets/images/clients-card.png" href="/clients/client">
|
||||
Connect to any MCP server — local or remote, programmatic or CLI.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
**[Servers](/servers/server)** wrap your Python functions into MCP-compliant tools, resources, and prompts. **[Clients](/clients/client)** connect to any server with full protocol support. And **[Apps](/apps/overview)** give your tools interactive UIs rendered directly in the conversation.
|
||||
|
||||
Ready to build? Start with the [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart).
|
||||
|
||||
FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
|
||||
|
||||
## Run FastMCP in production with Horizon
|
||||
|
||||
FastMCP is the standard way to build MCP servers. **[Prefect Horizon](https://www.prefect.io/horizon?utm_source=gofastmcp&utm_medium=docs&utm_campaign=docs_welcome&utm_content=welcome_body)** is the enterprise MCP gateway for running them safely.
|
||||
|
||||
Built by the FastMCP team, Horizon packages the best practices we've learned shipping the world's most popular MCP framework.
|
||||
|
||||
Deploy FastMCP servers from GitHub with branch previews and instant rollback. Create a private registry of every MCP your company uses. Secure access with SSO and tool-level RBAC. Get audit logs, observability, and governance across your MCP stack. Remix approved tools into purpose-built endpoints for teams and agents.
|
||||
|
||||
Start with FastMCP. [Scale with Horizon →](https://www.prefect.io/horizon?utm_source=gofastmcp&utm_medium=docs&utm_campaign=docs_welcome&utm_content=welcome_cta)
|
||||
|
||||
<Tip>
|
||||
**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 3.0.0`) to indicate when they were introduced. Note that this may include features that are not yet released.
|
||||
</Tip>
|
||||
|
||||
## LLM-Friendly Docs
|
||||
|
||||
The FastMCP documentation is available in multiple LLM-friendly formats:
|
||||
|
||||
### MCP Server
|
||||
|
||||
The FastMCP docs are accessible via MCP! The server URL is `https://gofastmcp.com/mcp`.
|
||||
|
||||
In fact, you can use FastMCP to search the FastMCP docs:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
async with Client("https://gofastmcp.com/mcp") as client:
|
||||
result = await client.call_tool(
|
||||
name="search_fast_mcp",
|
||||
arguments={"query": "deploy a FastMCP server"}
|
||||
)
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Text Formats
|
||||
|
||||
The docs are also available in [llms.txt format](https://llmstxt.org/):
|
||||
- [llms.txt](https://gofastmcp.com/llms.txt) - A sitemap listing all documentation pages
|
||||
- [llms-full.txt](https://gofastmcp.com/llms-full.txt) - The entire documentation in one file (may exceed context windows)
|
||||
|
||||
Any page can be accessed as markdown by appending `.md` to the URL. For example, this page becomes `https://gofastmcp.com/getting-started/welcome.md`.
|
||||
|
||||
You can also copy any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.
|
||||
Reference in New Issue
Block a user