> ## Documentation Index
> Fetch the complete documentation index at: https://gofastmcp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading from FastMCP 2

> Migration instructions for upgrading between FastMCP versions

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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  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](https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2) and the complete FastMCP documentation is at [https://gofastmcp.com](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: set FASTMCP\_DECORATOR\_MODE=object for v2 compat (itself deprecated).

  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 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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>
  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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Before
from mcp.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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@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`), set `FASTMCP_DECORATOR_MODE=object` for v2 compatibility. This escape hatch is itself deprecated and will be removed in a future release.

**Background tasks require optional dependency**

FastMCP's background task system (SEP-1686) is now behind an optional extra. If your server uses background tasks, install with:

```bash  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
pip install "fastmcp[tasks]"
```

Without the extra, configuring a tool with `task=True` or `TaskConfig` will raise an import error at runtime. See [Background Tasks](/servers/tasks) for details.

### Deprecated Features

These still work but emit warnings. Update when convenient.

**mount() prefix → namespace**

```python  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Deprecated
main.mount(subserver, prefix="api")

# New
main.mount(subserver, namespace="api")
```

**import\_server() → mount()**

```python  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Deprecated
main.import_server(subserver)

# New
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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# 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
```

`FastMCPOpenAPI` itself is deprecated — use `FastMCP` with an `OpenAPIProvider` instead:

```python  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Deprecated
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()**

```python  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Deprecated
mcp.add_tool_transformation("name", config)

# New
from fastmcp.server.transforms import ToolTransform
mcp.add_transform(ToolTransform({"name": config}))
```

**FastMCP.as\_proxy() → create\_proxy()**

```python  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Deprecated
proxy = FastMCP.as_proxy("http://example.com/mcp")

# New
from fastmcp.server import create_proxy
proxy = create_proxy("http://example.com/mcp")
```

## v2.14.0

### OpenAPI Parser Promotion

The experimental OpenAPI parser is now standard. Update imports:

```python  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Before
from fastmcp.experimental.server.openapi import FastMCPOpenAPI

# After
from fastmcp.server.openapi import FastMCPOpenAPI
```

### 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  theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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.
