Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 160 additions & 69 deletions docs/development/upgrade-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,75 +10,119 @@ This guide covers breaking changes and migration steps when upgrading FastMCP.

## v3.0.0

Most servers need only one change: update your import from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP`. The sections below cover less common breaking changes.
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.

### Breaking Changes
<Prompt description="Copy this prompt into any LLM along with your server code to get automated migration guidance.">
You are migrating a FastMCP v2 server to FastMCP v3.0. Analyze the provided code and identify every change needed. The full upgrade guide is at https://gofastmcp.com/development/upgrade-guide — fetch it for complete context.

#### OAuth Storage Backend Changed (diskcache CVE)
BREAKING CHANGES (will crash at import or runtime):

The default OAuth storage has moved from `DiskStore` to `FileTreeStore` to address a pickle deserialization vulnerability in diskcache ([CVE-2025-69872](https://github.com/jlowin/fastmcp/issues/3166)).
1. IMPORT: "from mcp.server.fastmcp import FastMCP" must become "from fastmcp import FastMCP"

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. No code changes needed.
2. 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()

If you were passing a `DiskStore` explicitly, you have two options:
3. 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

1. **Keep using DiskStore** by adding the dependency yourself. This re-introduces the vulnerable `diskcache` package into your dependency tree:
4. 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.

```bash
pip install 'py-key-value-aio[disk]'
```
5. 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

2. **Switch to FileTreeStore** (recommended) or any other [storage backend](/servers/storage-backends):
6. AUTH PROVIDERS: No longer auto-load from env vars. Pass client_id, client_secret explicitly via os.environ.

```python
from pathlib import Path
from key_value.aio.stores.filetree import (
FileTreeStore,
FileTreeV1KeySanitizationStrategy,
FileTreeV1CollectionSanitizationStrategy,
)
7. WSTRANSPORT: Removed. Use StreamableHttpTransport.

storage_dir = Path("/var/lib/fastmcp/oauth")
store = FileTreeStore(
data_directory=storage_dir,
key_sanitization_strategy=FileTreeV1KeySanitizationStrategy(storage_dir),
collection_sanitization_strategy=FileTreeV1CollectionSanitizationStrategy(storage_dir),
)
```
8. OPENAPI: timeout parameter removed from OpenAPIProvider. Set timeout on the httpx.AsyncClient instead.

9. METADATA: Namespace changed from "_fastmcp" to "fastmcp" in tool.meta. The include_fastmcp_meta parameter is removed (always included).

10. ENV VAR: FASTMCP_SHOW_CLI_BANNER renamed to FASTMCP_SHOW_SERVER_BANNER.

11. 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: set FASTMCP_DECORATOR_MODE=object for v2 compat (itself deprecated).

#### WSTransport Removed
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]'.

Use `StreamableHttpTransport` instead.
DEPRECATIONS (still work but emit warnings):

#### Auth Provider Environment Variables Removed
- 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(...))

Auth providers no longer auto-load configuration. Read them explicitly:
For each issue found, show the original line, explain why it breaks, and provide the corrected code.
</Prompt>
Comment on lines +15 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Prompt component definition and examine the actual file

echo "=== Checking for custom component definitions ==="
find docs -type f \( -name "*.json" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) | head -20 | xargs grep -l "Prompt" 2>/dev/null || echo "No component defs found"

echo ""
echo "=== Checking docs configuration files ==="
find docs -maxdepth 2 \( -name "*.json" -o -name "*.config.*" \) -exec ls -la {} \;

echo ""
echo "=== Reading the upgrade-guide.mdx file ==="
wc -l docs/development/upgrade-guide.mdx
head -80 docs/development/upgrade-guide.mdx

Repository: jlowin/fastmcp

Length of output: 5068


🏁 Script executed:

#!/bin/bash
# Search for Prompt usage and component files in the project

echo "=== All MDX files in docs ==="
find docs -name "*.mdx" -o -name "*.md" | head -20

echo ""
echo "=== Search for Prompt component usage ==="
rg '<Prompt' --include="*.mdx" --include="*.md" -n

echo ""
echo "=== Check for custom component registration ==="
rg -n 'components\|Prompt\|<' docs -t json | head -30

echo ""
echo "=== Check ToolTransform usage in source ==="
fd -t f -e py | xargs rg 'ToolTransform' -A 3 2>/dev/null | head -50

Repository: jlowin/fastmcp

Length of output: 4756


🏁 Script executed:

#!/bin/bash
# Extract specific lines from the upgrade guide to verify code examples

echo "=== Lines 192-210 (WSTransport and OpenAPI sections) ==="
sed -n '192,210p' docs/development/upgrade-guide.mdx

echo ""
echo "=== Lines 280-295 (ToolTransform section) ==="
sed -n '280,295p' docs/development/upgrade-guide.mdx

echo ""
echo "=== Lines 98-110 (CVE-2025-69872 reference) ==="
sed -n '98,110p' docs/development/upgrade-guide.mdx

Repository: jlowin/fastmcp

Length of output: 2349


🏁 Script executed:

#!/bin/bash
# Find ToolTransform and ToolTransformConfig definitions

echo "=== Search for ToolTransform class definition ==="
fd -t f -e py | xargs grep -l 'class ToolTransform' | head -5

echo ""
echo "=== Extract ToolTransform constructor ==="
fd -t f -e py | xargs grep -A 10 'class ToolTransform' 2>/dev/null | head -30

echo ""
echo "=== Search for ToolTransformConfig ==="
fd -t f -e py | xargs grep -l 'class ToolTransformConfig' | head -5

echo ""
echo "=== Extract ToolTransformConfig constructor ==="
fd -t f -e py | xargs grep -A 10 'class ToolTransformConfig' 2>/dev/null | head -40

Repository: jlowin/fastmcp

Length of output: 2853


🏁 Script executed:

#!/bin/bash
# Check docs configuration and component definitions

echo "=== Read docs.json for configuration ==="
head -100 docs/docs.json

echo ""
echo "=== Search for any custom component files ==="
find docs -type f \( -name "*.tsx" -o -name "*.ts" -o -name "*.jsx" -o -name "*.js" \)

echo ""
echo "=== Check if Prompt is defined as HTML/React component ==="
fd -t f \( -name "*.mdx" -o -name "*.tsx" -o -name "*.jsx" \) | xargs grep -l 'export.*Prompt\|function.*Prompt\|const.*Prompt' 2>/dev/null

Repository: jlowin/fastmcp

Length of output: 2863


🏁 Script executed:

#!/bin/bash
# Verify the imports needed for OpenAPI and ToolTransform examples

echo "=== Check imports in fastmcp server module ==="
fd -t f -e py -path "*/fastmcp/server*" | xargs grep -n 'from.*ToolTransform\|import.*ToolTransform' 2>/dev/null | head -20

echo ""
echo "=== Check OpenAPIProvider imports ==="
fd -t f -e py | xargs grep -l 'class OpenAPIProvider' | head -3

echo ""
echo "=== Check imports/exports in __init__ files ==="
fd -t f -name "__init__.py" -path "*/fastmcp/server*" -exec cat {} \;

Repository: jlowin/fastmcp

Length of output: 658


🏁 Script executed:

#!/bin/bash
# Get full ToolTransform constructor signature

echo "=== Full ToolTransform class definition ==="
sed -n '/^class ToolTransform/,/^class /p' src/fastmcp/server/transforms/tool_transform.py | head -40

echo ""
echo "=== Look for __init__ method ==="
grep -A 20 'def __init__' src/fastmcp/server/transforms/tool_transform.py

echo ""
echo "=== Check OpenAPIProvider location and imports ==="
cat src/fastmcp/server/providers/openapi/provider.py | head -50

echo ""
echo "=== Look for standard Mintlify callout components in docs ==="
grep -r '<Note\|<Warning\|<Info\|<Tip\|<Check\|<Callout' docs --include="*.mdx" | head -10

Repository: jlowin/fastmcp

Length of output: 4343


🏁 Script executed:

#!/bin/bash
# Check what components are actually used in the upgrade guide

echo "=== All components used in upgrade-guide.mdx ==="
grep -o '<[A-Z][a-zA-Z]*' docs/development/upgrade-guide.mdx | sort | uniq -c

echo ""
echo "=== Check Mintlify documentation for available components ==="
web_search "Mintlify components list official documentation"

Repository: jlowin/fastmcp

Length of output: 253


🏁 Script executed:

#!/bin/bash
# Examine the actual section with ToolTransform in the upgrade guide

echo "=== Full Deprecations section with ToolTransform ==="
sed -n '270,300p' docs/development/upgrade-guide.mdx

echo ""
echo "=== Check what add_transform expects ==="
fd -t f -e py -path "*/fastmcp/server*" | xargs grep -B 2 -A 10 'def add_transform' 2>/dev/null | head -50

echo ""
echo "=== Verify httpx and OpenAPIProvider are imported in the codebase ==="
head -30 src/fastmcp/server/providers/openapi/provider.py

Repository: jlowin/fastmcp

Length of output: 1999


🏁 Script executed:

#!/bin/bash
# Final verification: check if <Prompt> is registered anywhere in the project

echo "=== Search for Prompt component registration in entire docs ==="
grep -r 'Prompt' docs --include="*.json" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | grep -v node_modules | head -20

echo ""
echo "=== Check all MDX files that use Prompt ==="
grep -r '<Prompt' docs --include="*.mdx" | head -20

Repository: jlowin/fastmcp

Length of output: 319


🌐 Web query:

Mintlify custom components guide documentation

💡 Result:

Key Mintlify docs for custom components (React/JSX in MDX):

  • React components in MDX (how to write components directly in an .mdx page, plus rules around importing reusable components from the snippets/ folder). [1]
  • Reusable snippets / JSX snippets (how to create reusable .mdx snippets and reusable .jsx “JSX snippets” in snippets/, then import and render them in pages). [2]
  • Built-in component library overview (what you can use without custom code: Tabs, Steps, Cards, Callouts, etc.). [3]
  • Custom scripts (custom CSS/JS + Tailwind styling notes) (how to add site-wide styling and scripts; Tailwind v3 classes; when to use style prop). [4]

Typical pattern: put reusable React components in snippets/*.jsx, export them, then import { MyComponent } from '/snippets/MyComponent.jsx' and render <MyComponent /> in your MDX page. [1][2]

Sources: [1][2][3][4]


Replace <Prompt> with a standard callout component (e.g., <Note> or <Info>), verify ToolTransform API usage, and add missing imports and code examples.

The <Prompt> component used on line 15 is not defined in the Mintlify component registry or anywhere in the project. It will fail to build or render as unstyled text, causing lines 15–69 to display incorrectly. Use a standard Mintlify callout like <Note> instead.

The add_tool_transformation()add_transform() deprecation code is incorrect. The example shows ToolTransform({"name": config}) but the actual constructor signature is ToolTransform(transforms: dict[str, ToolTransformConfig]), where keys are original tool names and values are ToolTransformConfig objects. Correct it to:

# New
from fastmcp.server.transforms import ToolTransform
from fastmcp.tools.tool_transform import ToolTransformConfig

mcp.add_transform(ToolTransform({
    "my_tool": ToolTransformConfig(name="renamed_tool")
}))

The WSTransport removed section (lines 192–194) provides only text description with no code example. Add a before/after block showing the removal.

The OpenAPI timeout parameter removed code example (lines 200–207) uses httpx.AsyncClient and OpenAPIProvider without importing them. Add imports at the top:

import httpx
from fastmcp.server.providers.openapi import OpenAPIProvider

# After
client = httpx.AsyncClient(base_url="https://api.example.com", timeout=60)
provider = OpenAPIProvider(spec, client)

Add a warning callout in the OAUTH STORAGE section (line 102) when mentioning pip install 'py-key-value-aio[disk]' to re-introduce the vulnerable diskcache package.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/development/upgrade-guide.mdx` around lines 15 - 69, Replace the
undefined <Prompt> callout with a standard Mintlify callout like <Note> for the
block (lines ~15–69) so the component renders; update the ToolTransform example
to use the correct constructor shape by importing ToolTransform and
ToolTransformConfig and calling mcp.add_transform(ToolTransform({ "my_tool":
ToolTransformConfig(...) })) instead of the invalid ToolTransform({"name":
config}) pattern; add a concise before/after code snippet showing removal of
WSTransport and the recommended replacement StreamableHttpTransport (referencing
WSTransport and StreamableHttpTransport) in the WSTransport section; add the
missing imports and usage for httpx.AsyncClient and OpenAPIProvider (import
httpx and from fastmcp.server.providers.openapi import OpenAPIProvider and pass
the AsyncClient to OpenAPIProvider) in the OpenAPI timeout example; and append a
visible warning callout next to the OAuth STORAGE disk cache note when
suggesting pip install 'py-key-value-aio[disk]' and reference DiskStore and
FileTreeStore so readers see the security implication and the safer default.


### 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
import os
# Before
mcp = FastMCP("server", host="0.0.0.0", port=8080)
mcp.run()

auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
)
# After
mcp = FastMCP("server")
mcp.run(transport="http", host="0.0.0.0", port=8080)
```

#### Component enable()/disable() Moved to Server
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/jlowin/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.

These methods moved from component objects to the server:
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>
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"])
server.disable(names={"my_tool"}, components={"tool"})
```

#### Listing Methods Renamed and Return Lists
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**

`get_tools()`, `get_resources()`, `get_prompts()`, and `get_resource_templates()` have been replaced by `list_tools()`, `list_resources()`, `list_prompts()`, and `list_resource_templates()`. The new methods return lists instead of dicts:
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
Expand All @@ -90,9 +134,9 @@ tools = await server.list_tools()
tool = next((t for t in tools if t.name == "my_tool"), None)
```

#### Prompts Use Message Class
**Prompts use Message class**

Use `Message` instead of `mcp.types.PromptMessage`:
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
Expand All @@ -110,9 +154,9 @@ def my_prompt() -> Message:
return Message("Hello")
```

#### Context State Methods Are Async
**Context state methods are async**

`ctx.set_state()` and `ctx.get_state()` are now async. State persists across the session:
`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
Expand All @@ -124,27 +168,48 @@ await ctx.set_state("key", "value")
value = await ctx.get_state("key")
```

#### State Values Must Be Serializable
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)
```

Session state values must now be JSON-serializable by default (dicts, lists, strings, numbers, etc.), since state is persisted across requests using a pluggable storage backend.
**Auth provider environment variables removed**

If you need to store non-serializable values (e.g., passing an HTTP client from middleware to a tool), use `serializable=False`. These values are request-scoped and only available during the current tool call, resource read, or prompt render:
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
# Middleware sets up a client for the current request
await ctx.set_state("client", my_http_client, serializable=False)
# 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

# Tool retrieves it in the same request
client = await ctx.get_state("client")
auth = GitHubProvider(
client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
)
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#### Server Banner Environment Variable Renamed
**WSTransport removed**

`FASTMCP_SHOW_CLI_BANNER` is now `FASTMCP_SHOW_SERVER_BANNER`.
The deprecated WebSocket client transport has been removed. Use `StreamableHttpTransport` instead:

#### OpenAPI `timeout` Parameter Removed
```python
# 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")
```

Configure timeout on the httpx client directly. The `client` parameter is now optional — when omitted, a default client is created from the spec's `servers` URL with a 30-second timeout.
**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
Expand All @@ -155,9 +220,9 @@ client = httpx.AsyncClient(base_url="https://api.example.com", timeout=60)
provider = OpenAPIProvider(spec, client)
```

#### Metadata Namespace Renamed
**Metadata namespace renamed**

The FastMCP metadata namespace changed from `_fastmcp` to `fastmcp`, and metadata is now always included. The `include_fastmcp_meta` parameter has been removed from `FastMCP()` and `to_mcp_tool()`—remove any usage of this parameter.
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
Expand All @@ -167,11 +232,15 @@ tags = tool.meta.get("_fastmcp", {}).get("tags", [])
tags = tool.meta.get("fastmcp", {}).get("tags", [])
```

### Behavior Changes
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
**Decorators return functions**

Decorators now return your original function instead of a component object. This means functions stay callable for testing:
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
Expand All @@ -181,13 +250,13 @@ def greet(name: str) -> str:
greet("World") # Works! Returns "Hello, World!"
```

If you relied on the old behavior (treating `greet` as a `FunctionTool`), set `FASTMCP_DECORATOR_MODE=object` for v2 compatibility.
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.

### Deprecated Features

These still work but emit warnings. Update when convenient.

#### mount() prefix → namespace
**mount() prefix → namespace**

```python
# Deprecated
Expand All @@ -197,22 +266,44 @@ main.mount(subserver, prefix="api")
main.mount(subserver, namespace="api")
```

#### include_tags/exclude_tags → enable()/disable()
**import_server() → mount()**

```python
# Deprecated
mcp = FastMCP("server", exclude_tags={"internal"})
main.import_server(subserver)

# New
mcp = FastMCP("server")
mcp.disable(tags={"internal"})
main.mount(subserver)
```

**Module import paths for proxy and OpenAPI**

The proxy and OpenAPI modules have moved under `providers` to reflect v3's provider-based architecture:

```python
# Deprecated
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
```

#### tool_serializer → ToolResult
`FastMCPOpenAPI` itself is deprecated — use `FastMCP` with an `OpenAPIProvider` instead:

```python
# Deprecated
from fastmcp.server.openapi import FastMCPOpenAPI
server = FastMCPOpenAPI(spec, client)

Return `ToolResult` from your tools for explicit serialization control instead of using the `tool_serializer` parameter.
# New
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import OpenAPIProvider
server = FastMCP("my_api", providers=[OpenAPIProvider(spec, client)])
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#### add_tool_transformation() → add_transform()
**add_tool_transformation() → add_transform()**

```python
# Deprecated
Expand All @@ -223,7 +314,7 @@ from fastmcp.server.transforms import ToolTransform
mcp.add_transform(ToolTransform({"name": config}))
```

#### FastMCP.as_proxy() → create_proxy()
**FastMCP.as_proxy() → create_proxy()**

```python
# Deprecated
Expand Down
Loading