mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
776 lines
23 KiB
Text
776 lines
23 KiB
Text
---
|
|
title: v3.0 Feature Tracking
|
|
---
|
|
|
|
This document tracks major features in FastMCP v3.0 for release notes preparation.
|
|
|
|
## Provider-Based Architecture
|
|
|
|
v3.0 introduces a provider-based component system that replaces v2's static-only registration ([#2622](https://github.com/jlowin/fastmcp/pull/2622)). Providers dynamically source tools, resources, templates, and prompts at runtime.
|
|
|
|
**Core abstraction** (`src/fastmcp/server/providers/base.py`):
|
|
```python
|
|
class Provider:
|
|
async def list_tools(self) -> Sequence[Tool]: ...
|
|
async def get_tool(self, name: str) -> Tool | None: ...
|
|
async def list_resources(self) -> Sequence[Resource]: ...
|
|
async def get_resource(self, uri: str) -> Resource | None: ...
|
|
async def list_resource_templates(self) -> Sequence[ResourceTemplate]: ...
|
|
async def get_resource_template(self, uri: str) -> ResourceTemplate | None: ...
|
|
async def list_prompts(self) -> Sequence[Prompt]: ...
|
|
async def get_prompt(self, name: str) -> Prompt | None: ...
|
|
```
|
|
|
|
Providers support:
|
|
- **Lifecycle management**: `async def lifespan()` for setup/teardown
|
|
- **Visibility control**: `enable()` / `disable()` with keys, tags, and allowlist mode
|
|
- **Transform stacking**: `provider.add_transform(Namespace(...))`, `provider.add_transform(ToolTransform(...))`
|
|
|
|
### LocalProvider
|
|
|
|
`LocalProvider` (`src/fastmcp/server/providers/local_provider.py`) manages components registered via decorators. Can be used standalone and attached to multiple servers:
|
|
|
|
```python
|
|
from fastmcp.server.providers import LocalProvider
|
|
|
|
provider = LocalProvider()
|
|
|
|
@provider.tool
|
|
def greet(name: str) -> str:
|
|
return f"Hello, {name}!"
|
|
|
|
# Attach to multiple servers
|
|
server1 = FastMCP("Server1", providers=[provider])
|
|
server2 = FastMCP("Server2", providers=[provider])
|
|
```
|
|
|
|
### ProxyProvider
|
|
|
|
`ProxyProvider` (`src/fastmcp/server/providers/proxy.py`) proxies components from remote MCP servers via a client factory. Used by `create_proxy()` and `FastMCP.mount()` for remote server integration.
|
|
|
|
```python
|
|
from fastmcp.server import create_proxy
|
|
|
|
# Create proxy to remote server
|
|
server = create_proxy("http://remote-server/mcp")
|
|
```
|
|
|
|
### OpenAPIProvider
|
|
|
|
`OpenAPIProvider` (`src/fastmcp/server/providers/openapi/provider.py`) creates MCP components from OpenAPI specifications. Routes map HTTP operations to tools, resources, or templates based on configurable rules.
|
|
|
|
```python
|
|
from fastmcp.server.providers.openapi import OpenAPIProvider
|
|
import httpx
|
|
|
|
client = httpx.AsyncClient(base_url="https://api.example.com")
|
|
provider = OpenAPIProvider(openapi_spec=spec, client=client)
|
|
|
|
mcp = FastMCP("API Server", providers=[provider])
|
|
```
|
|
|
|
Features:
|
|
- Automatic route-to-component mapping (GET → resource, POST/PUT/DELETE → tool)
|
|
- Custom route mappings via `route_maps` or `route_map_fn`
|
|
- Component customization via `mcp_component_fn`
|
|
- Name collision detection and handling
|
|
|
|
### FastMCPProvider
|
|
|
|
`FastMCPProvider` (`src/fastmcp/server/providers/fastmcp_provider.py`) wraps a FastMCP server to enable mounting one server onto another. Components delegate execution through the wrapped server's middleware chain.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.providers import FastMCPProvider
|
|
from fastmcp.server.transforms import Namespace
|
|
|
|
main = FastMCP("Main")
|
|
sub = FastMCP("Sub")
|
|
|
|
@sub.tool
|
|
def greet(name: str) -> str:
|
|
return f"Hello, {name}!"
|
|
|
|
# Mount with namespace
|
|
provider = FastMCPProvider(sub)
|
|
provider.add_transform(Namespace("sub"))
|
|
main.add_provider(provider)
|
|
# Tool accessible as "sub_greet"
|
|
```
|
|
|
|
### Transforms
|
|
|
|
Transforms modify components (tools, resources, prompts) as they flow from providers to clients ([#2836](https://github.com/jlowin/fastmcp/pull/2836)). They use a middleware pattern where each transform receives a `call_next` callable to continue the chain.
|
|
|
|
**Built-in transforms** (`src/fastmcp/server/transforms/`):
|
|
|
|
- `Namespace` - adds prefixes to names (`tool` → `api_tool`) and path segments to URIs (`data://x` → `data://api/x`)
|
|
- `ToolTransform` - modifies tool schemas (rename, description, tags, argument transforms)
|
|
- `Visibility` - filters components by key or tag (backs `enable()`/`disable()` API)
|
|
|
|
```python
|
|
from fastmcp.server.transforms import Namespace, ToolTransform
|
|
from fastmcp.tools.tool_transform import ToolTransformConfig
|
|
|
|
provider = SomeProvider()
|
|
provider.add_transform(Namespace("api"))
|
|
provider.add_transform(ToolTransform({
|
|
"api_verbose_tool_name": ToolTransformConfig(name="short")
|
|
}))
|
|
|
|
# Stacking composes transformations
|
|
# "foo" → "api_foo" (namespace) → "short" (rename)
|
|
```
|
|
|
|
**Custom transforms** subclass `Transform` and override needed methods:
|
|
|
|
```python
|
|
from collections.abc import Sequence
|
|
from fastmcp.server.transforms import Transform, ListToolsNext, GetToolNext
|
|
from fastmcp.tools import Tool
|
|
|
|
class TagFilter(Transform):
|
|
def __init__(self, required_tags: set[str]):
|
|
self.required_tags = required_tags
|
|
|
|
async def list_tools(self, call_next: ListToolsNext) -> Sequence[Tool]:
|
|
tools = await call_next() # Get tools from downstream
|
|
return [t for t in tools if t.tags & self.required_tags]
|
|
|
|
async def get_tool(self, name: str, call_next: GetToolNext) -> Tool | None:
|
|
tool = await call_next(name)
|
|
return tool if tool and tool.tags & self.required_tags else None
|
|
```
|
|
|
|
Transforms apply at two levels:
|
|
- **Provider-level**: `provider.add_transform()` - affects only that provider's components
|
|
- **Server-level**: `server.add_transform()` - affects all components from all providers
|
|
|
|
Documentation: `docs/servers/providers/transforms.mdx`, `docs/servers/visibility.mdx`
|
|
|
|
---
|
|
|
|
## Session-Scoped State
|
|
|
|
v3.0 changes context state from request-scoped to session-scoped. State now persists across multiple tool calls within the same MCP session.
|
|
|
|
```python
|
|
@mcp.tool
|
|
async def increment_counter(ctx: Context) -> int:
|
|
count = await ctx.get_state("counter") or 0
|
|
await ctx.set_state("counter", count + 1)
|
|
return count + 1
|
|
```
|
|
|
|
State is automatically keyed by session ID, ensuring isolation between different clients. The implementation uses [pykeyvalue](https://github.com/strawgate/py-key-value) for pluggable storage backends:
|
|
|
|
```python
|
|
from key_value.aio.stores.redis import RedisStore
|
|
|
|
# Use Redis for distributed deployments
|
|
mcp = FastMCP("server", session_state_store=RedisStore(...))
|
|
```
|
|
|
|
**Key details:**
|
|
- Methods are now async: `await ctx.get_state()`, `await ctx.set_state()`, `await ctx.delete_state()`
|
|
- State expires after 1 day (TTL) to prevent unbounded memory growth
|
|
- Works during `on_initialize` middleware when using the same session object
|
|
- For distributed HTTP, session identity comes from the `mcp-session-id` header
|
|
|
|
Documentation: `docs/servers/context.mdx`
|
|
|
|
---
|
|
|
|
## Visibility System
|
|
|
|
Components can be dynamically enabled/disabled at runtime using the visibility system ([#2708](https://github.com/jlowin/fastmcp/pull/2708)).
|
|
|
|
```python
|
|
mcp = FastMCP("Server")
|
|
|
|
# Disable specific components
|
|
mcp.disable(keys=["tool:dangerous_tool"])
|
|
|
|
# Disable by tag
|
|
mcp.disable(tags={"admin"})
|
|
|
|
# Allowlist mode - only show these
|
|
mcp.enable(keys=["tool:safe_tool"], only=True)
|
|
```
|
|
|
|
Works at both server and provider level. Supports:
|
|
- **Blocklist mode** (default): All components visible except explicitly disabled
|
|
- **Allowlist mode** (`only=True`): Only explicitly enabled components visible
|
|
- **Tag-based filtering**: Enable/disable groups of components by tag
|
|
|
|
---
|
|
|
|
## Type-Safe Canonical Results
|
|
|
|
v3.0 introduces type-safe result classes that provide explicit control over component responses while supporting MCP runtime metadata: `ToolResult` ([#2736](https://github.com/jlowin/fastmcp/pull/2736)), `ResourceResult` ([#2734](https://github.com/jlowin/fastmcp/pull/2734)), and `PromptResult` ([#2738](https://github.com/jlowin/fastmcp/pull/2738)).
|
|
|
|
### ToolResult
|
|
|
|
`ToolResult` (`src/fastmcp/tools/tool.py:79`) provides structured tool responses:
|
|
|
|
```python
|
|
from fastmcp.tools import ToolResult
|
|
|
|
@mcp.tool
|
|
def process(data: str) -> ToolResult:
|
|
return ToolResult(
|
|
content=[TextContent(type="text", text="Done")],
|
|
structured_content={"status": "success", "count": 42},
|
|
meta={"processing_time_ms": 150}
|
|
)
|
|
```
|
|
|
|
Fields:
|
|
- `content`: List of MCP ContentBlocks (text, images, etc.)
|
|
- `structured_content`: Dict matching tool's output schema
|
|
- `meta`: Runtime metadata passed to MCP as `_meta`
|
|
|
|
### ResourceResult
|
|
|
|
`ResourceResult` (`src/fastmcp/resources/resource.py:117`) provides structured resource responses:
|
|
|
|
```python
|
|
from fastmcp.resources import ResourceResult, ResourceContent
|
|
|
|
@mcp.resource("data://items")
|
|
def get_items() -> ResourceResult:
|
|
return ResourceResult(
|
|
contents=[
|
|
ResourceContent({"key": "value"}), # auto-serialized to JSON
|
|
ResourceContent(b"binary data"),
|
|
],
|
|
meta={"count": 2}
|
|
)
|
|
```
|
|
|
|
Accepts strings, bytes, or `list[ResourceContent]` for flexible content handling.
|
|
|
|
### PromptResult
|
|
|
|
`PromptResult` (`src/fastmcp/prompts/prompt.py:109`) provides structured prompt responses:
|
|
|
|
```python
|
|
from fastmcp.prompts import PromptResult, Message
|
|
|
|
@mcp.prompt
|
|
def conversation() -> PromptResult:
|
|
return PromptResult(
|
|
messages=[
|
|
Message("What's the weather?"),
|
|
Message("It's sunny today.", role="assistant"),
|
|
],
|
|
meta={"generated_at": "2024-01-01"}
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Background Tasks (SEP-1686)
|
|
|
|
v3.0 implements MCP SEP-1686 for background task execution via Docket integration.
|
|
|
|
**Configuration** (`src/fastmcp/server/tasks/config.py`):
|
|
|
|
```python
|
|
from fastmcp.server.tasks import TaskConfig
|
|
|
|
@mcp.tool(task=TaskConfig(mode="required"))
|
|
async def long_running_task():
|
|
# Must be executed as background task
|
|
...
|
|
|
|
@mcp.tool(task=TaskConfig(mode="optional"))
|
|
async def flexible_task():
|
|
# Supports both sync and task execution
|
|
...
|
|
|
|
@mcp.tool(task=True) # Shorthand for mode="optional"
|
|
async def simple_task():
|
|
...
|
|
```
|
|
|
|
Task modes:
|
|
- `"forbidden"`: Component does not support task execution (default)
|
|
- `"optional"`: Supports both synchronous and task execution
|
|
- `"required"`: Must be executed as background task
|
|
|
|
Requires Docket server for task scheduling and result polling.
|
|
|
|
---
|
|
|
|
## Decorators Return Functions
|
|
|
|
v3.0 changes what decorators (`@tool`, `@resource`, `@prompt`) return ([#2856](https://github.com/jlowin/fastmcp/pull/2856)). Decorators now return the original function unchanged, rather than transforming it into a component object.
|
|
|
|
**v3 behavior (default):**
|
|
```python
|
|
@mcp.tool
|
|
def greet(name: str) -> str:
|
|
return f"Hello, {name}!"
|
|
|
|
# greet is still your function - call it directly
|
|
greet("World") # "Hello, World!"
|
|
```
|
|
|
|
**Why this matters:**
|
|
- Functions stay callable - useful for testing and reuse
|
|
- Instance methods just work: `mcp.add_tool(obj.method)`
|
|
- Matches how Flask, FastAPI, and Typer decorators behave
|
|
|
|
**For v2 compatibility:**
|
|
|
|
```python
|
|
import fastmcp
|
|
|
|
# v2 behavior: decorators return FunctionTool/FunctionResource/FunctionPrompt objects
|
|
fastmcp.settings.decorator_mode = "object"
|
|
```
|
|
|
|
Environment variable: `FASTMCP_DECORATOR_MODE=object`
|
|
|
|
---
|
|
|
|
## CLI Auto-Reload
|
|
|
|
The `--reload` flag enables file watching with automatic server restarts for development ([#2816](https://github.com/jlowin/fastmcp/pull/2816)).
|
|
|
|
```bash
|
|
# Watch for changes and restart
|
|
fastmcp run server.py --reload
|
|
|
|
# Watch specific directories
|
|
fastmcp run server.py --reload --reload-dir ./src --reload-dir ./lib
|
|
|
|
# Works with any transport
|
|
fastmcp run server.py --reload --transport http --port 8080
|
|
```
|
|
|
|
Implementation (`src/fastmcp/cli/run.py`):
|
|
- Uses `watchfiles` for efficient file monitoring
|
|
- Runs server as subprocess for clean restarts
|
|
- Stateless mode for seamless reconnection after restart
|
|
- stdio: Full MCP features including elicitation
|
|
- HTTP: Limited bidirectional features during reload
|
|
|
|
Also available with `fastmcp dev`:
|
|
```bash
|
|
fastmcp dev server.py # Includes --reload by default
|
|
```
|
|
|
|
---
|
|
|
|
## Component Authorization
|
|
|
|
v3.0 introduces callable-based authorization for tools, resources, and prompts ([#2855](https://github.com/jlowin/fastmcp/pull/2855)).
|
|
|
|
**Component-level auth**:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.auth import require_auth, require_scopes
|
|
|
|
mcp = FastMCP()
|
|
|
|
@mcp.tool(auth=require_auth)
|
|
def protected_tool(): ...
|
|
|
|
@mcp.resource("data://secret", auth=require_scopes("read"))
|
|
def secret_data(): ...
|
|
|
|
@mcp.prompt(auth=require_scopes("admin"))
|
|
def admin_prompt(): ...
|
|
```
|
|
|
|
**Server-wide auth via middleware**:
|
|
|
|
```python
|
|
from fastmcp.server.middleware import AuthMiddleware
|
|
from fastmcp.server.auth import require_auth, restrict_tag
|
|
|
|
# Require auth for all components
|
|
mcp = FastMCP(middleware=[AuthMiddleware(auth=require_auth)])
|
|
|
|
# Tag-based restrictions
|
|
mcp = FastMCP(middleware=[
|
|
AuthMiddleware(auth=restrict_tag("admin", scopes=["admin"]))
|
|
])
|
|
```
|
|
|
|
Built-in checks:
|
|
- `require_auth`: Requires any valid token
|
|
- `require_scopes(*scopes)`: Requires specific OAuth scopes
|
|
- `restrict_tag(tag, scopes)`: Requires scopes only for tagged components
|
|
|
|
Custom checks receive `AuthContext` with `token` and `component`:
|
|
|
|
```python
|
|
def custom_check(ctx: AuthContext) -> bool:
|
|
return ctx.token is not None and "admin" in ctx.token.scopes
|
|
```
|
|
|
|
STDIO transport bypasses all auth checks (no OAuth concept).
|
|
|
|
---
|
|
|
|
## FileSystemProvider
|
|
|
|
v3.0 introduces `FileSystemProvider`, a fundamentally different approach to organizing MCP servers. Instead of importing a server instance and decorating functions with `@server.tool`, you use standalone decorators in separate files and let the provider discover them.
|
|
|
|
**The problem it solves**: Traditional servers require coordination between files—either tool files import the server (creating coupling) or the server imports all tool modules (creating a registry bottleneck). FileSystemProvider removes this coupling entirely.
|
|
|
|
**Usage** ([#2823](https://github.com/jlowin/fastmcp/pull/2823)):
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.providers import FileSystemProvider
|
|
|
|
# Scans mcp/ directory for decorated functions
|
|
mcp = FastMCP("server", providers=[FileSystemProvider("mcp/")])
|
|
```
|
|
|
|
**Tool files are self-contained**:
|
|
|
|
```python
|
|
# mcp/tools/greet.py
|
|
from fastmcp.tools import tool
|
|
|
|
@tool
|
|
def greet(name: str) -> str:
|
|
"""Greet someone by name."""
|
|
return f"Hello, {name}!"
|
|
```
|
|
|
|
Features:
|
|
- **Standalone decorators**: `@tool`, `@resource`, `@prompt` from `fastmcp.tools`, `fastmcp.resources`, `fastmcp.prompts` ([#2832](https://github.com/jlowin/fastmcp/pull/2832))
|
|
- **Reload mode**: `FileSystemProvider("mcp/", reload=True)` re-scans on every request for development
|
|
- **Package support**: Directories with `__init__.py` support relative imports
|
|
- **Warning deduplication**: Broken imports warn once per file modification
|
|
|
|
Documentation: [FileSystemProvider](/servers/providers/filesystem)
|
|
|
|
---
|
|
|
|
## OpenTelemetry Tracing
|
|
|
|
v3.0 adds OpenTelemetry instrumentation for observability into server and client operations ([#2869](https://github.com/jlowin/fastmcp/pull/2869)).
|
|
|
|
**Server spans**: Created for tool calls, resource reads, and prompt renders with attributes including component key, provider type, session ID, and auth context.
|
|
|
|
**Client spans**: Wrap outgoing calls with W3C trace context propagation via request meta.
|
|
|
|
```python
|
|
# Tracing is passive - configure an OTel SDK to export spans
|
|
from opentelemetry import trace
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
|
|
provider = TracerProvider()
|
|
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
|
|
trace.set_tracer_provider(provider)
|
|
|
|
# Use fastmcp normally - spans export to your configured backend
|
|
```
|
|
|
|
Components provide their own span attributes through a `get_span_attributes()` method that subclasses override—this lets LocalProvider, FastMCPProvider, and ProxyProvider each include relevant context (original names, backend URIs, etc.).
|
|
|
|
Documentation: [Telemetry](/servers/telemetry)
|
|
|
|
---
|
|
|
|
## Composable Lifespans
|
|
|
|
Lifespans can be combined with the `|` operator for modular setup/teardown ([#2828](https://github.com/jlowin/fastmcp/pull/2828)):
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.lifespan import lifespan
|
|
|
|
@lifespan
|
|
async def db_lifespan(server):
|
|
db = await connect_db()
|
|
try:
|
|
yield {"db": db}
|
|
finally:
|
|
await db.close()
|
|
|
|
@lifespan
|
|
async def cache_lifespan(server):
|
|
cache = await connect_cache()
|
|
try:
|
|
yield {"cache": cache}
|
|
finally:
|
|
await cache.close()
|
|
|
|
mcp = FastMCP("server", lifespan=db_lifespan | cache_lifespan)
|
|
```
|
|
|
|
Both enter lifespans in order and exit in reverse (LIFO). Context dicts are merged.
|
|
|
|
Also adds `combine_lifespans()` utility for FastAPI integration:
|
|
|
|
```python
|
|
from fastmcp.utilities.lifespan import combine_lifespans
|
|
|
|
app = FastAPI(lifespan=combine_lifespans(app_lifespan, mcp_app.lifespan))
|
|
```
|
|
|
|
Documentation: [Lifespan](/servers/lifespan)
|
|
|
|
---
|
|
|
|
## Tool Timeout
|
|
|
|
Tools can limit foreground execution time with a `timeout` parameter ([#2872](https://github.com/jlowin/fastmcp/pull/2872)):
|
|
|
|
```python
|
|
@mcp.tool(timeout=30.0)
|
|
async def fetch_data(url: str) -> dict:
|
|
"""Fetch with 30-second timeout."""
|
|
...
|
|
```
|
|
|
|
When exceeded, clients receive MCP error code `-32000`. Both sync and async tools are supported—sync functions run in thread pools so the timeout applies regardless of execution model.
|
|
|
|
Note: This timeout applies to foreground execution only. Background tasks (`task=True`) execute in Docket workers where this timeout isn't enforced.
|
|
|
|
---
|
|
|
|
## PingMiddleware
|
|
|
|
Sends periodic server-to-client pings to keep long-lived connections alive ([#2838](https://github.com/jlowin/fastmcp/pull/2838)):
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.middleware import PingMiddleware
|
|
|
|
mcp = FastMCP("server")
|
|
mcp.add_middleware(PingMiddleware(interval_ms=5000))
|
|
```
|
|
|
|
The middleware starts a background ping task on first message from each session, using the session's existing task group for automatic cleanup when the session ends.
|
|
|
|
---
|
|
|
|
## Context.transport Property
|
|
|
|
Tools can detect which transport is active ([#2850](https://github.com/jlowin/fastmcp/pull/2850)):
|
|
|
|
```python
|
|
from fastmcp import FastMCP, Context
|
|
|
|
mcp = FastMCP("example")
|
|
|
|
@mcp.tool
|
|
def my_tool(ctx: Context) -> str:
|
|
if ctx.transport == "stdio":
|
|
return "short response"
|
|
return "detailed response with more context"
|
|
```
|
|
|
|
Returns `Literal["stdio", "sse", "streamable-http"]` when running, or `None` outside a server context.
|
|
|
|
---
|
|
|
|
## Automatic Threadpool for Sync Functions
|
|
|
|
Synchronous tools, resources, and prompts now automatically run in a threadpool, preventing event loop blocking during concurrent requests ([#2865](https://github.com/jlowin/fastmcp/pull/2865)):
|
|
|
|
```python
|
|
import time
|
|
|
|
@mcp.tool
|
|
def slow_tool():
|
|
time.sleep(10) # No longer blocks other requests
|
|
return "done"
|
|
```
|
|
|
|
Three concurrent calls now execute in parallel (~10s) rather than sequentially (30s). Uses `anyio.to_thread.run_sync()` which properly propagates contextvars, so `Context` and `Depends` continue to work.
|
|
|
|
---
|
|
|
|
## CLI Update Notifications
|
|
|
|
The CLI notifies users when a newer FastMCP version is available on PyPI ([#2840](https://github.com/jlowin/fastmcp/pull/2840)).
|
|
|
|
**Setting**: `FASTMCP_CHECK_FOR_UPDATES`
|
|
- `"stable"` - Check for stable releases (default)
|
|
- `"prerelease"` - Include alpha/beta/rc versions
|
|
- `"off"` - Disable
|
|
|
|
12-hour cache, 2-second timeout, fails silently on network errors.
|
|
|
|
---
|
|
|
|
## Deprecated Features
|
|
|
|
These emit deprecation warnings but continue to work.
|
|
|
|
### Mount Prefix Parameter
|
|
|
|
The `prefix` parameter for `mount()` renamed to `namespace`:
|
|
|
|
```python
|
|
# Deprecated
|
|
main.mount(subserver, prefix="api")
|
|
|
|
# New
|
|
main.mount(subserver, namespace="api")
|
|
```
|
|
|
|
### Tag Filtering Init Parameters
|
|
|
|
`FastMCP(include_tags=..., exclude_tags=...)` deprecated. Use `enable()`/`disable()` methods:
|
|
|
|
```python
|
|
# Deprecated
|
|
mcp = FastMCP("server", exclude_tags={"internal"})
|
|
|
|
# New
|
|
mcp = FastMCP("server")
|
|
mcp.disable(tags={"internal"})
|
|
```
|
|
|
|
### Tool Serializer Parameter
|
|
|
|
The `tool_serializer` parameter on `FastMCP` is deprecated. Return `ToolResult` for explicit serialization control.
|
|
|
|
### Tool Transformation Methods
|
|
|
|
`add_tool_transformation()`, `remove_tool_transformation()`, and `tool_transformations` constructor parameter are deprecated. Use `add_transform(ToolTransform({...}))` instead:
|
|
|
|
```python
|
|
# Deprecated
|
|
mcp.add_tool_transformation("name", config)
|
|
|
|
# New
|
|
from fastmcp.server.transforms import ToolTransform
|
|
mcp.add_transform(ToolTransform({"name": config}))
|
|
```
|
|
|
|
---
|
|
|
|
## Breaking Changes
|
|
|
|
### WSTransport Removed
|
|
|
|
The deprecated `WSTransport` client transport has been removed ([#2826](https://github.com/jlowin/fastmcp/pull/2826)). Use `StreamableHttpTransport` instead.
|
|
|
|
### Decorators Return Functions
|
|
|
|
Decorators (`@tool`, `@resource`, `@prompt`) now return the original function instead of component objects. Code that treats the decorated function as a `FunctionTool`, `FunctionResource`, or `FunctionPrompt` will break.
|
|
|
|
```python
|
|
# v2.x
|
|
@mcp.tool
|
|
def greet(name: str) -> str:
|
|
return f"Hello, {name}!"
|
|
|
|
isinstance(greet, FunctionTool) # True
|
|
|
|
# v3.0
|
|
@mcp.tool
|
|
def greet(name: str) -> str:
|
|
return f"Hello, {name}!"
|
|
|
|
isinstance(greet, FunctionTool) # False
|
|
callable(greet) # True - it's still your function
|
|
greet("World") # "Hello, World!"
|
|
```
|
|
|
|
Set `FASTMCP_DECORATOR_MODE=object` or `fastmcp.settings.decorator_mode = "object"` for v2 behavior.
|
|
|
|
### Component Enable/Disable Moved to Server/Provider
|
|
|
|
The `enabled` field and `enable()`/`disable()` methods removed from component objects:
|
|
|
|
```python
|
|
# v2.x
|
|
tool = await server.get_tool("my_tool")
|
|
tool.disable()
|
|
|
|
# v3.0
|
|
server.disable(keys=["tool:my_tool"])
|
|
```
|
|
|
|
### Component Lookup Methods
|
|
|
|
Server lookup and listing methods have updated signatures:
|
|
|
|
- Parameter names: `get_tool(name=...)`, `get_resource(uri=...)`, etc. (was `key`)
|
|
- Return types: `get_tools()`, `get_resources()`, etc. return lists instead of dicts
|
|
|
|
```python
|
|
# v2.x
|
|
tools = await server.get_tools()
|
|
tool = tools["my_tool"]
|
|
|
|
# v3.0
|
|
tools = await server.get_tools()
|
|
tool = next((t for t in tools if t.name == "my_tool"), None)
|
|
```
|
|
|
|
### Prompt Return Types
|
|
|
|
Prompt functions now use `Message` instead of `mcp.types.PromptMessage`:
|
|
|
|
```python
|
|
# v2.x
|
|
from mcp.types import PromptMessage, TextContent
|
|
|
|
@mcp.prompt
|
|
def my_prompt() -> PromptMessage:
|
|
return PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
|
|
|
|
# v3.0
|
|
from fastmcp.prompts import Message
|
|
|
|
@mcp.prompt
|
|
def my_prompt() -> Message:
|
|
return Message("Hello") # role defaults to "user"
|
|
```
|
|
|
|
### Auth Provider Environment Variables Removed
|
|
|
|
Auth providers no longer auto-load from environment variables ([#2752](https://github.com/jlowin/fastmcp/pull/2752)):
|
|
|
|
```python
|
|
# v2.x - auto-loaded from FASTMCP_SERVER_AUTH_GITHUB_*
|
|
auth = GitHubProvider()
|
|
|
|
# v3.0 - explicit configuration
|
|
import os
|
|
auth = GitHubProvider(
|
|
client_id=os.environ["GITHUB_CLIENT_ID"],
|
|
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
|
|
)
|
|
```
|
|
|
|
See `docs/development/v3-notes/auth-provider-env-vars.mdx` for rationale.
|
|
|
|
### Server Banner Environment Variable
|
|
|
|
`FASTMCP_SHOW_CLI_BANNER` → `FASTMCP_SHOW_SERVER_BANNER` ([#2771](https://github.com/jlowin/fastmcp/pull/2771))
|
|
|
|
Now applies to all server startup methods, not just the CLI.
|
|
|
|
### Context State Methods Are Async
|
|
|
|
`ctx.set_state()` and `ctx.get_state()` are now async and session-scoped:
|
|
|
|
```python
|
|
# v2.x
|
|
ctx.set_state("key", "value")
|
|
value = ctx.get_state("key")
|
|
|
|
# v3.0
|
|
await ctx.set_state("key", "value")
|
|
value = await ctx.get_state("key")
|
|
```
|
|
|
|
State now persists across requests within a session. See "Session-Scoped State" above.
|