diff --git a/docs/docs.json b/docs/docs.json
index e833496c6..ea84fa0cf 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -417,6 +417,7 @@
"python-sdk/fastmcp-server-elicitation",
"python-sdk/fastmcp-server-event_store",
"python-sdk/fastmcp-server-http",
+ "python-sdk/fastmcp-server-lifespan",
"python-sdk/fastmcp-server-low_level",
{
"group": "middleware",
@@ -506,8 +507,8 @@
"python-sdk/fastmcp-utilities-inspect",
"python-sdk/fastmcp-utilities-json_schema",
"python-sdk/fastmcp-utilities-json_schema_type",
+ "python-sdk/fastmcp-utilities-lifespan",
"python-sdk/fastmcp-utilities-logging",
- "python-sdk/fastmcp-utilities-mcp_config",
{
"group": "mcp_server_config",
"pages": [
diff --git a/docs/python-sdk/fastmcp-cli-cli.mdx b/docs/python-sdk/fastmcp-cli-cli.mdx
index 6dbb1a74b..cb0c4be1b 100644
--- a/docs/python-sdk/fastmcp-cli-cli.mdx
+++ b/docs/python-sdk/fastmcp-cli-cli.mdx
@@ -10,7 +10,7 @@ FastMCP CLI tools using Cyclopts.
## Functions
-### `with_argv`
+### `with_argv`
```python
with_argv(args: list[str] | None)
@@ -27,7 +27,7 @@ Args are provided without the script name, so we preserve sys.argv[0]
and replace the rest.
-### `version`
+### `version`
```python
version()
@@ -37,7 +37,7 @@ version()
Display version information and platform details.
-### `dev`
+### `dev`
```python
dev(server_spec: str | None = None) -> None
@@ -50,7 +50,7 @@ Run an MCP server with the MCP Inspector for development.
- `server_spec`: Python file to run, optionally with \:object suffix, or None to auto-detect fastmcp.json
-### `run`
+### `run`
```python
run(server_spec: str | None = None, *server_args: str) -> None
@@ -74,7 +74,7 @@ fastmcp run server.py -- --config config.json --debug
- `server_spec`: Python file, object specification (file\:obj), config file, URL, or None to auto-detect
-### `inspect`
+### `inspect`
```python
inspect(server_spec: str | None = None) -> None
@@ -105,7 +105,7 @@ fastmcp inspect # auto-detect fastmcp.json
- `server_spec`: Python file to inspect, optionally with \:object suffix, or fastmcp.json
-### `prepare`
+### `prepare`
```python
prepare(config_path: Annotated[str | None, cyclopts.Parameter(help='Path to fastmcp.json configuration file')] = None, output_dir: Annotated[str | None, cyclopts.Parameter(help='Directory to create the persistent environment in')] = None, skip_source: Annotated[bool, cyclopts.Parameter(help='Skip source preparation (e.g., git clone)')] = False) -> None
diff --git a/docs/python-sdk/fastmcp-cli-run.mdx b/docs/python-sdk/fastmcp-cli-run.mdx
index 2909c19a8..c3192a611 100644
--- a/docs/python-sdk/fastmcp-cli-run.mdx
+++ b/docs/python-sdk/fastmcp-cli-run.mdx
@@ -10,7 +10,7 @@ FastMCP run command implementation with enhanced type hints.
## Functions
-### `is_url`
+### `is_url`
```python
is_url(path: str) -> bool
@@ -20,7 +20,7 @@ is_url(path: str) -> bool
Check if a string is a URL.
-### `create_client_server`
+### `create_client_server`
```python
create_client_server(url: str) -> Any
@@ -36,7 +36,7 @@ Create a FastMCP server from a client URL.
- A FastMCP server instance
-### `create_mcp_config_server`
+### `create_mcp_config_server`
```python
create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]
@@ -46,7 +46,7 @@ create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]
Create a FastMCP server from a MCPConfig.
-### `load_mcp_server_config`
+### `load_mcp_server_config`
```python
load_mcp_server_config(config_path: Path) -> MCPServerConfig
@@ -62,10 +62,10 @@ Load a FastMCP configuration from a fastmcp.json file.
- MCPServerConfig object
-### `run_command`
+### `run_command`
```python
-run_command(server_spec: str, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, server_args: list[str] | None = None, show_banner: bool = True, use_direct_import: bool = False, skip_source: bool = False) -> None
+run_command(server_spec: str, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, server_args: list[str] | None = None, show_banner: bool = True, use_direct_import: bool = False, skip_source: bool = False, stateless: bool = False) -> None
```
@@ -82,9 +82,10 @@ Run a MCP server or connect to a remote one.
- `show_banner`: Whether to show the server banner
- `use_direct_import`: Whether to use direct import instead of subprocess
- `skip_source`: Whether to skip source preparation step
+- `stateless`: Whether to run in stateless mode (no session)
-### `run_v1_server_async`
+### `run_v1_server_async`
```python
run_v1_server_async(server: FastMCP1x, host: str | None = None, port: int | None = None, transport: TransportType | None = None) -> None
@@ -99,3 +100,18 @@ Run a FastMCP 1.x server using async methods.
- `port`: Port to bind to
- `transport`: Transport protocol to use
+
+### `run_with_reload`
+
+```python
+run_with_reload(cmd: list[str], reload_dirs: list[Path] | None = None, is_stdio: bool = False) -> None
+```
+
+
+Run a command with file watching and auto-reload.
+
+**Args:**
+- `cmd`: Command to run as subprocess (should include --no-reload)
+- `reload_dirs`: Directories to watch for changes (default\: cwd)
+- `is_stdio`: Whether this is stdio transport
+
diff --git a/docs/python-sdk/fastmcp-client-auth-oauth.mdx b/docs/python-sdk/fastmcp-client-auth-oauth.mdx
index 0d94c7513..3c83f641e 100644
--- a/docs/python-sdk/fastmcp-client-auth-oauth.mdx
+++ b/docs/python-sdk/fastmcp-client-auth-oauth.mdx
@@ -50,19 +50,19 @@ get_tokens(self) -> OAuthToken | None
set_tokens(self, tokens: OAuthToken) -> None
```
-#### `get_client_info`
+#### `get_client_info`
```python
get_client_info(self) -> OAuthClientInformationFull | None
```
-#### `set_client_info`
+#### `set_client_info`
```python
set_client_info(self, client_info: OAuthClientInformationFull) -> None
```
-### `OAuth`
+### `OAuth`
OAuth client provider for MCP servers with browser-based authentication.
@@ -73,7 +73,7 @@ a browser for user authorization and running a local callback server.
**Methods:**
-#### `redirect_handler`
+#### `redirect_handler`
```python
redirect_handler(self, authorization_url: str) -> None
@@ -82,7 +82,7 @@ redirect_handler(self, authorization_url: str) -> None
Open browser for authorization, with pre-flight check for invalid client.
-#### `callback_handler`
+#### `callback_handler`
```python
callback_handler(self) -> tuple[str, str | None]
@@ -91,7 +91,7 @@ callback_handler(self) -> tuple[str, str | None]
Handle OAuth callback and return (auth_code, state).
-#### `async_auth_flow`
+#### `async_auth_flow`
```python
async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]
diff --git a/docs/python-sdk/fastmcp-client-client.mdx b/docs/python-sdk/fastmcp-client-client.mdx
index eb9f152e7..a638d3940 100644
--- a/docs/python-sdk/fastmcp-client-client.mdx
+++ b/docs/python-sdk/fastmcp-client-client.mdx
@@ -7,7 +7,7 @@ sidebarTitle: client
## Classes
-### `ClientSessionState`
+### `ClientSessionState`
Holds all session-related state for a Client instance.
@@ -16,13 +16,13 @@ This allows clean separation of configuration (which is copied) from
session state (which should be fresh for each new client instance).
-### `CallToolResult`
+### `CallToolResult`
Parsed result from a tool call.
-### `Client`
+### `Client`
MCP client that delegates connection management to a Transport instance.
@@ -85,7 +85,7 @@ async with client:
**Methods:**
-#### `session`
+#### `session`
```python
session(self) -> ClientSession
@@ -94,7 +94,7 @@ session(self) -> ClientSession
Get the current active session. Raises RuntimeError if not connected.
-#### `initialize_result`
+#### `initialize_result`
```python
initialize_result(self) -> mcp.types.InitializeResult | None
@@ -103,7 +103,7 @@ initialize_result(self) -> mcp.types.InitializeResult | None
Get the result of the initialization request.
-#### `set_roots`
+#### `set_roots`
```python
set_roots(self, roots: RootsList | RootsHandler) -> None
@@ -112,7 +112,7 @@ set_roots(self, roots: RootsList | RootsHandler) -> None
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
-#### `set_sampling_callback`
+#### `set_sampling_callback`
```python
set_sampling_callback(self, sampling_callback: SamplingHandler, sampling_capabilities: mcp.types.SamplingCapability | None = None) -> None
@@ -121,7 +121,7 @@ set_sampling_callback(self, sampling_callback: SamplingHandler, sampling_capabil
Set the sampling callback for the client.
-#### `set_elicitation_callback`
+#### `set_elicitation_callback`
```python
set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
@@ -130,7 +130,7 @@ set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
Set the elicitation callback for the client.
-#### `is_connected`
+#### `is_connected`
```python
is_connected(self) -> bool
@@ -139,7 +139,7 @@ is_connected(self) -> bool
Check if the client is currently connected.
-#### `new`
+#### `new`
```python
new(self) -> Client[ClientTransportT]
@@ -155,7 +155,7 @@ share state with the original client.
- A new Client instance with the same configuration but disconnected state.
-#### `initialize`
+#### `initialize`
```python
initialize(self, timeout: datetime.timedelta | float | int | None = None) -> mcp.types.InitializeResult
@@ -183,13 +183,13 @@ capabilities, protocol version, and optional instructions.
- `RuntimeError`: If the client is not connected or initialization times out.
-#### `close`
+#### `close`
```python
close(self)
```
-#### `ping`
+#### `ping`
```python
ping(self) -> bool
@@ -198,7 +198,7 @@ ping(self) -> bool
Send a ping request.
-#### `cancel`
+#### `cancel`
```python
cancel(self, request_id: str | int, reason: str | None = None) -> None
@@ -207,7 +207,7 @@ cancel(self, request_id: str | int, reason: str | None = None) -> None
Send a cancellation notification for an in-progress request.
-#### `progress`
+#### `progress`
```python
progress(self, progress_token: str | int, progress: float, total: float | None = None, message: str | None = None) -> None
@@ -216,7 +216,7 @@ progress(self, progress_token: str | int, progress: float, total: float | None =
Send a progress notification.
-#### `set_logging_level`
+#### `set_logging_level`
```python
set_logging_level(self, level: mcp.types.LoggingLevel) -> None
@@ -225,7 +225,7 @@ set_logging_level(self, level: mcp.types.LoggingLevel) -> None
Send a logging/setLevel request.
-#### `send_roots_list_changed`
+#### `send_roots_list_changed`
```python
send_roots_list_changed(self) -> None
@@ -234,7 +234,7 @@ send_roots_list_changed(self) -> None
Send a roots/list_changed notification.
-#### `list_resources_mcp`
+#### `list_resources_mcp`
```python
list_resources_mcp(self) -> mcp.types.ListResourcesResult
@@ -251,7 +251,7 @@ containing the list of resources and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> list[mcp.types.Resource]
@@ -267,7 +267,7 @@ Retrieve a list of resources available on the server.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_resource_templates_mcp`
+#### `list_resource_templates_mcp`
```python
list_resource_templates_mcp(self) -> mcp.types.ListResourceTemplatesResult
@@ -284,7 +284,7 @@ containing the list of resource templates and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_resource_templates`
+#### `list_resource_templates`
```python
list_resource_templates(self) -> list[mcp.types.ResourceTemplate]
@@ -300,7 +300,7 @@ Retrieve a list of resource templates available on the server.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `read_resource_mcp`
+#### `read_resource_mcp`
```python
read_resource_mcp(self, uri: AnyUrl | str, meta: dict[str, Any] | None = None) -> mcp.types.ReadResourceResult
@@ -321,19 +321,19 @@ containing the resource contents and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: AnyUrl | str) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
```
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: AnyUrl | str) -> ResourceTask
```
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: AnyUrl | str) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents] | ResourceTask
@@ -356,7 +356,7 @@ A list of content objects if task=False, or a ResourceTask object if task=True.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_prompts_mcp`
+#### `list_prompts_mcp`
```python
list_prompts_mcp(self) -> mcp.types.ListPromptsResult
@@ -373,7 +373,7 @@ containing the list of prompts and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> list[mcp.types.Prompt]
@@ -389,7 +389,7 @@ Retrieve a list of prompts available on the server.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `get_prompt_mcp`
+#### `get_prompt_mcp`
```python
get_prompt_mcp(self, name: str, arguments: dict[str, Any] | None = None, meta: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
@@ -411,19 +411,19 @@ containing the prompt messages and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
```
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptTask
```
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult | PromptTask
@@ -447,7 +447,7 @@ or a PromptTask object if task=True.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `complete_mcp`
+#### `complete_mcp`
```python
complete_mcp(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.CompleteResult
@@ -470,7 +470,7 @@ containing the completion and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `complete`
+#### `complete`
```python
complete(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.Completion
@@ -492,7 +492,7 @@ include with the completion request. Defaults to None.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_tools_mcp`
+#### `list_tools_mcp`
```python
list_tools_mcp(self) -> mcp.types.ListToolsResult
@@ -509,7 +509,7 @@ containing the list of tools and any additional metadata.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_tools`
+#### `list_tools`
```python
list_tools(self) -> list[mcp.types.Tool]
@@ -525,7 +525,7 @@ Retrieve a list of tools available on the server.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `call_tool_mcp`
+#### `call_tool_mcp`
```python
call_tool_mcp(self, name: str, arguments: dict[str, Any], progress_handler: ProgressHandler | None = None, timeout: datetime.timedelta | float | int | None = None, meta: dict[str, Any] | None = None) -> mcp.types.CallToolResult
@@ -555,19 +555,19 @@ containing the tool result and any additional metadata.
- `McpError`: If the tool call requests results in a TimeoutError | JSONRPCError
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> CallToolResult
```
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolTask
```
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> CallToolResult | ToolTask
@@ -606,7 +606,7 @@ raw result object.
- `RuntimeError`: If called while the client is not connected.
-#### `get_task_status`
+#### `get_task_status`
```python
get_task_status(self, task_id: str) -> GetTaskResult
@@ -627,7 +627,7 @@ Sends a 'tasks/get' MCP protocol request over the existing transport.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `get_task_result`
+#### `get_task_result`
```python
get_task_result(self, task_id: str) -> Any
@@ -649,7 +649,7 @@ Returns the raw result - callers should parse it appropriately.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `list_tasks`
+#### `list_tasks`
```python
list_tasks(self, cursor: str | None = None, limit: int = 50) -> dict[str, Any]
@@ -675,7 +675,7 @@ querying status for locally tracked task IDs.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `cancel_task`
+#### `cancel_task`
```python
cancel_task(self, task_id: str) -> mcp.types.CancelTaskResult
@@ -697,7 +697,7 @@ and transition to cancelled state.
- `McpError`: If the request results in a TimeoutError | JSONRPCError
-#### `generate_name`
+#### `generate_name`
```python
generate_name(cls, name: str | None = None) -> str
diff --git a/docs/python-sdk/fastmcp-client-transports.mdx b/docs/python-sdk/fastmcp-client-transports.mdx
index 410021c22..c9f748a32 100644
--- a/docs/python-sdk/fastmcp-client-transports.mdx
+++ b/docs/python-sdk/fastmcp-client-transports.mdx
@@ -7,7 +7,7 @@ sidebarTitle: transports
## Functions
-### `infer_transport`
+### `infer_transport`
```python
infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
@@ -57,13 +57,13 @@ transport = infer_transport(config)
## Classes
-### `SessionKwargs`
+### `SessionKwargs`
Keyword arguments for the MCP ClientSession constructor.
-### `ClientTransport`
+### `ClientTransport`
Abstract base class for different MCP client transport mechanisms.
@@ -74,7 +74,7 @@ to an MCP server, and providing a ClientSession within an async context.
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
@@ -93,7 +93,7 @@ within this context.
constructor (e.g., callbacks, timeouts).
-#### `close`
+#### `close`
```python
close(self)
@@ -102,21 +102,7 @@ close(self)
Close the transport.
-### `WSTransport`
-
-
-Transport implementation that connects to an MCP server via WebSockets.
-
-
-**Methods:**
-
-#### `connect_session`
-
-```python
-connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
-```
-
-### `SSETransport`
+### `SSETransport`
Transport implementation that connects to an MCP server via Server-Sent Events.
@@ -124,13 +110,13 @@ Transport implementation that connects to an MCP server via Server-Sent Events.
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-### `StreamableHttpTransport`
+### `StreamableHttpTransport`
Transport implementation that connects to an MCP server via Streamable HTTP Requests.
@@ -138,25 +124,25 @@ Transport implementation that connects to an MCP server via Streamable HTTP Requ
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-#### `get_session_id`
+#### `get_session_id`
```python
get_session_id(self) -> str | None
```
-#### `close`
+#### `close`
```python
close(self)
```
-### `StdioTransport`
+### `StdioTransport`
Base transport for connecting to an MCP server via subprocess with stdio.
@@ -167,67 +153,67 @@ transports like Python, Node, Uvx, etc.
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-#### `connect`
+#### `connect`
```python
connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None
```
-#### `disconnect`
+#### `disconnect`
```python
disconnect(self)
```
-#### `close`
+#### `close`
```python
close(self)
```
-### `PythonStdioTransport`
+### `PythonStdioTransport`
Transport for running Python scripts.
-### `FastMCPStdioTransport`
+### `FastMCPStdioTransport`
Transport for running FastMCP servers using the FastMCP CLI.
-### `NodeStdioTransport`
+### `NodeStdioTransport`
Transport for running Node.js scripts.
-### `UvStdioTransport`
+### `UvStdioTransport`
Transport for running commands via the uv tool.
-### `UvxStdioTransport`
+### `UvxStdioTransport`
Transport for running commands via the uvx tool.
-### `NpxStdioTransport`
+### `NpxStdioTransport`
Transport for running commands via the npx tool.
-### `FastMCPTransport`
+### `FastMCPTransport`
In-memory transport for FastMCP servers.
@@ -240,13 +226,13 @@ tests or scenarios where client and server run in the same runtime.
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-### `MCPConfigTransport`
+### `MCPConfigTransport`
Transport for connecting to one or more MCP servers defined in an MCPConfig.
@@ -268,7 +254,6 @@ MCP servers through a single interface, simplifying client code.
```python
from fastmcp import Client
-from fastmcp.utilities.mcp_config import MCPConfig
# Create a config with multiple servers
config = {
@@ -299,13 +284,13 @@ async with client:
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-#### `close`
+#### `close`
```python
close(self)
diff --git a/docs/python-sdk/fastmcp-fs-__init__.mdx b/docs/python-sdk/fastmcp-fs-__init__.mdx
new file mode 100644
index 000000000..d49a61f5b
--- /dev/null
+++ b/docs/python-sdk/fastmcp-fs-__init__.mdx
@@ -0,0 +1,43 @@
+---
+title: __init__
+sidebarTitle: __init__
+---
+
+# `fastmcp.fs`
+
+
+Filesystem-based component discovery for FastMCP.
+
+This module provides decorators and a provider for discovering MCP components
+from the filesystem. Files are scanned for functions decorated with @tool,
+@resource, or @prompt, and automatically registered with the server.
+
+Example:
+ ```python
+ # server.py
+ from fastmcp import FastMCP
+ from fastmcp.fs import FileSystemProvider
+
+ mcp = FastMCP("MyServer", providers=[FileSystemProvider("mcp/")])
+ ```
+
+ ```python
+ # mcp/tools/greet.py
+ from fastmcp.fs import tool
+
+ @tool
+ def greet(name: str) -> str:
+ '''Greet someone by name.'''
+ return f"Hello, {name}!"
+ ```
+
+ ```python
+ # mcp/resources/config.py
+ from fastmcp.fs import resource
+
+ @resource("config://app")
+ def get_config() -> dict:
+ '''Get application configuration.'''
+ return {"version": "1.0"}
+ ```
+
diff --git a/docs/python-sdk/fastmcp-fs-decorators.mdx b/docs/python-sdk/fastmcp-fs-decorators.mdx
new file mode 100644
index 000000000..45ade0981
--- /dev/null
+++ b/docs/python-sdk/fastmcp-fs-decorators.mdx
@@ -0,0 +1,156 @@
+---
+title: decorators
+sidebarTitle: decorators
+---
+
+# `fastmcp.fs.decorators`
+
+
+Decorators for marking functions in filesystem-based discovery.
+
+These decorators mark functions with metadata so that FileSystemProvider
+can discover and register them. Unlike LocalProvider's decorators, these
+do NOT register components immediately - they just store metadata on the
+function for later discovery.
+
+Example:
+ ```python
+ # mcp/tools/greet.py
+ from fastmcp.fs import tool
+
+ @tool
+ def greet(name: str) -> str:
+ '''Greet someone by name.'''
+ return f"Hello, {name}!"
+
+ @tool(name="custom-greet", tags={"greeting"})
+ def my_greet(name: str) -> str:
+ return f"Hi, {name}!"
+ ```
+
+
+## Functions
+
+### `get_fs_meta`
+
+```python
+get_fs_meta(fn: Any) -> FSMeta | None
+```
+
+
+Get filesystem metadata from a function if it has been decorated.
+
+
+### `has_fs_meta`
+
+```python
+has_fs_meta(fn: Any) -> bool
+```
+
+
+Check if a function has filesystem metadata.
+
+
+### `tool`
+
+```python
+tool(fn: AnyFunction | str | None = None) -> Any
+```
+
+
+Mark a function as a tool for filesystem-based discovery.
+
+This decorator stores metadata on the function but does NOT register it.
+FileSystemProvider discovers marked functions when scanning directories.
+
+Supports multiple calling patterns:
+- @tool (without parentheses)
+- @tool() (with empty parentheses)
+- @tool("custom_name") (with name as first argument)
+- @tool(name="custom_name") (with keyword arguments)
+
+**Args:**
+- `fn`: The function to decorate, or a name string, or None
+- `name`: Optional name for the tool (defaults to function name)
+- `title`: Optional title for display
+- `description`: Optional description (defaults to docstring)
+- `icons`: Optional icons for the tool
+- `tags`: Optional tags for categorization
+- `output_schema`: Optional JSON schema for output
+- `annotations`: Optional tool annotations
+- `meta`: Optional metadata dict
+
+
+### `resource`
+
+```python
+resource(uri: str) -> Any
+```
+
+
+Mark a function as a resource for filesystem-based discovery.
+
+This decorator stores metadata on the function but does NOT register it.
+FileSystemProvider discovers marked functions when scanning directories.
+
+Unlike @tool and @prompt, @resource REQUIRES a URI argument.
+
+**Args:**
+- `uri`: URI for the resource (e.g., "config\://app" or "users\://{user_id}")
+- `name`: Optional name for the resource
+- `title`: Optional title for display
+- `description`: Optional description (defaults to docstring)
+- `icons`: Optional icons for the resource
+- `mime_type`: Optional MIME type
+- `tags`: Optional tags for categorization
+- `annotations`: Optional resource annotations
+- `meta`: Optional metadata dict
+
+
+### `prompt`
+
+```python
+prompt(fn: AnyFunction | str | None = None) -> Any
+```
+
+
+Mark a function as a prompt for filesystem-based discovery.
+
+This decorator stores metadata on the function but does NOT register it.
+FileSystemProvider discovers marked functions when scanning directories.
+
+Supports multiple calling patterns:
+- @prompt (without parentheses)
+- @prompt() (with empty parentheses)
+- @prompt("custom_name") (with name as first argument)
+- @prompt(name="custom_name") (with keyword arguments)
+
+**Args:**
+- `fn`: The function to decorate, or a name string, or None
+- `name`: Optional name for the prompt (defaults to function name)
+- `title`: Optional title for display
+- `description`: Optional description (defaults to docstring)
+- `icons`: Optional icons for the prompt
+- `tags`: Optional tags for categorization
+- `meta`: Optional metadata dict
+
+
+## Classes
+
+### `ToolMeta`
+
+
+Metadata stored on functions decorated with @tool.
+
+
+### `ResourceMeta`
+
+
+Metadata stored on functions decorated with @resource.
+
+
+### `PromptMeta`
+
+
+Metadata stored on functions decorated with @prompt.
+
diff --git a/docs/python-sdk/fastmcp-fs-discovery.mdx b/docs/python-sdk/fastmcp-fs-discovery.mdx
new file mode 100644
index 000000000..4d6720be4
--- /dev/null
+++ b/docs/python-sdk/fastmcp-fs-discovery.mdx
@@ -0,0 +1,103 @@
+---
+title: discovery
+sidebarTitle: discovery
+---
+
+# `fastmcp.fs.discovery`
+
+
+File discovery and module import utilities for filesystem-based routing.
+
+This module provides functions to:
+1. Discover Python files in a directory tree
+2. Import modules (as packages if __init__.py exists, else directly)
+3. Extract decorated functions from imported modules
+
+
+## Functions
+
+### `discover_files`
+
+```python
+discover_files(root: Path) -> list[Path]
+```
+
+
+Recursively discover all Python files under a directory.
+
+Excludes __init__.py files (they're for package structure, not components).
+
+**Args:**
+- `root`: Root directory to scan.
+
+**Returns:**
+- List of .py file paths, sorted for deterministic order.
+
+
+### `import_module_from_file`
+
+```python
+import_module_from_file(file_path: Path) -> ModuleType
+```
+
+
+Import a Python file as a module.
+
+If the file is part of a package (directory has __init__.py), imports
+it as a proper package member (relative imports work). Otherwise,
+imports directly using spec_from_file_location.
+
+**Args:**
+- `file_path`: Path to the Python file.
+
+**Returns:**
+- The imported module.
+
+**Raises:**
+- `ImportError`: If the module cannot be imported.
+
+
+### `extract_components`
+
+```python
+extract_components(module: ModuleType) -> list[tuple[Any, FSMeta]]
+```
+
+
+Extract all decorated functions from a module.
+
+Scans all module attributes for functions that have been decorated
+with @tool, @resource, or @prompt.
+
+**Args:**
+- `module`: The imported module to scan.
+
+**Returns:**
+- List of (function, metadata) tuples for each decorated function.
+
+
+### `discover_and_import`
+
+```python
+discover_and_import(root: Path) -> DiscoveryResult
+```
+
+
+Discover files, import modules, and extract components.
+
+This is the main entry point for filesystem-based discovery.
+
+**Args:**
+- `root`: Root directory to scan.
+
+**Returns:**
+- DiscoveryResult with components and any failed files.
+
+
+## Classes
+
+### `DiscoveryResult`
+
+
+Result of filesystem discovery.
+
diff --git a/docs/python-sdk/fastmcp-fs-provider.mdx b/docs/python-sdk/fastmcp-fs-provider.mdx
new file mode 100644
index 000000000..84bb30a4c
--- /dev/null
+++ b/docs/python-sdk/fastmcp-fs-provider.mdx
@@ -0,0 +1,111 @@
+---
+title: provider
+sidebarTitle: provider
+---
+
+# `fastmcp.fs.provider`
+
+
+FileSystemProvider for filesystem-based component discovery.
+
+FileSystemProvider scans a directory for Python files, imports them, and
+registers any functions decorated with @tool, @resource, or @prompt.
+
+Example:
+ ```python
+ from fastmcp import FastMCP
+ from fastmcp.fs import FileSystemProvider
+
+ mcp = FastMCP("MyServer", providers=[FileSystemProvider("mcp/")])
+ ```
+
+
+## Classes
+
+### `FileSystemProvider`
+
+
+Provider that discovers components from the filesystem.
+
+Scans a directory for Python files and registers functions decorated
+with @tool, @resource, or @prompt from fastmcp.fs.
+
+**Args:**
+- `root`: Root directory to scan. Defaults to current directory.
+- `reload`: If True, re-scan files on every request (dev mode).
+Defaults to False (scan once at init, cache results).
+
+
+**Methods:**
+
+#### `list_tools`
+
+```python
+list_tools(self) -> Sequence[Tool]
+```
+
+Return all tools, reloading if in reload mode.
+
+
+#### `get_tool`
+
+```python
+get_tool(self, name: str) -> Tool | None
+```
+
+Get a tool by name, reloading if in reload mode.
+
+
+#### `list_resources`
+
+```python
+list_resources(self) -> Sequence[Resource]
+```
+
+Return all resources, reloading if in reload mode.
+
+
+#### `get_resource`
+
+```python
+get_resource(self, uri: str) -> Resource | None
+```
+
+Get a resource by URI, reloading if in reload mode.
+
+
+#### `list_resource_templates`
+
+```python
+list_resource_templates(self) -> Sequence[ResourceTemplate]
+```
+
+Return all resource templates, reloading if in reload mode.
+
+
+#### `get_resource_template`
+
+```python
+get_resource_template(self, uri: str) -> ResourceTemplate | None
+```
+
+Get a resource template, reloading if in reload mode.
+
+
+#### `list_prompts`
+
+```python
+list_prompts(self) -> Sequence[Prompt]
+```
+
+Return all prompts, reloading if in reload mode.
+
+
+#### `get_prompt`
+
+```python
+get_prompt(self, name: str) -> Prompt | None
+```
+
+Get a prompt by name, reloading if in reload mode.
+
diff --git a/docs/python-sdk/fastmcp-mcp_config.mdx b/docs/python-sdk/fastmcp-mcp_config.mdx
index cd4356e45..9b30dad85 100644
--- a/docs/python-sdk/fastmcp-mcp_config.mdx
+++ b/docs/python-sdk/fastmcp-mcp_config.mdx
@@ -42,7 +42,7 @@ infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
Infer the appropriate transport type from the given URL.
-### `update_config_file`
+### `update_config_file`
```python
update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
@@ -67,19 +67,19 @@ This is the canonical configuration format for MCP servers using stdio transport
**Methods:**
-#### `to_transport`
+#### `to_transport`
```python
to_transport(self) -> StdioTransport
```
-### `TransformingStdioMCPServer`
+### `TransformingStdioMCPServer`
A Stdio server with tool transforms.
-### `RemoteMCPServer`
+### `RemoteMCPServer`
MCP server configuration for HTTP/SSE transport.
@@ -89,19 +89,19 @@ This is the canonical configuration format for MCP servers using remote transpor
**Methods:**
-#### `to_transport`
+#### `to_transport`
```python
to_transport(self) -> StreamableHttpTransport | SSETransport
```
-### `TransformingRemoteMCPServer`
+### `TransformingRemoteMCPServer`
A Remote server with tool transforms.
-### `MCPConfig`
+### `MCPConfig`
A configuration object for MCP Servers that conforms to the canonical MCP configuration format
@@ -113,7 +113,7 @@ For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.
**Methods:**
-#### `wrap_servers_at_root`
+#### `wrap_servers_at_root`
```python
wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
@@ -122,7 +122,7 @@ wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
If there's no mcpServers key but there are server configs at root, wrap them.
-#### `add_server`
+#### `add_server`
```python
add_server(self, name: str, server: MCPServerTypes) -> None
@@ -131,7 +131,7 @@ add_server(self, name: str, server: MCPServerTypes) -> None
Add or update a server in the configuration.
-#### `from_dict`
+#### `from_dict`
```python
from_dict(cls, config: dict[str, Any]) -> Self
@@ -140,7 +140,7 @@ from_dict(cls, config: dict[str, Any]) -> Self
Parse MCP configuration from dictionary format.
-#### `to_dict`
+#### `to_dict`
```python
to_dict(self) -> dict[str, Any]
@@ -149,7 +149,7 @@ to_dict(self) -> dict[str, Any]
Convert MCPConfig to dictionary format, preserving all fields.
-#### `write_to_file`
+#### `write_to_file`
```python
write_to_file(self, file_path: Path) -> None
@@ -158,7 +158,7 @@ write_to_file(self, file_path: Path) -> None
Write configuration to JSON file.
-#### `from_file`
+#### `from_file`
```python
from_file(cls, file_path: Path) -> Self
@@ -167,7 +167,7 @@ from_file(cls, file_path: Path) -> Self
Load configuration from JSON file.
-### `CanonicalMCPConfig`
+### `CanonicalMCPConfig`
Canonical MCP configuration format.
@@ -178,7 +178,7 @@ The format is designed to be client-agnostic and extensible for future use cases
**Methods:**
-#### `add_server`
+#### `add_server`
```python
add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
diff --git a/docs/python-sdk/fastmcp-server-auth-auth.mdx b/docs/python-sdk/fastmcp-server-auth-auth.mdx
index fab057df0..8f58e8be6 100644
--- a/docs/python-sdk/fastmcp-server-auth-auth.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-auth.mdx
@@ -16,19 +16,19 @@ AccessToken that includes all JWT claims.
### `TokenHandler`
-TokenHandler that returns OAuth 2.1 compliant error responses.
+TokenHandler that returns MCP-compliant error responses.
-The MCP SDK returns `unauthorized_client` for client authentication failures.
-However, per RFC 6749 Section 5.2, authentication failures should return
-`invalid_client` with HTTP 401, not `unauthorized_client`.
+This handler addresses two SDK issues:
-This distinction matters: `unauthorized_client` means "client exists but
-can't do this", while `invalid_client` means "client doesn't exist or
-credentials are wrong". Claude's OAuth client uses this to decide whether
-to re-register.
+1. Error code: The SDK returns `unauthorized_client` for client authentication
+ failures, but RFC 6749 Section 5.2 requires `invalid_client` with HTTP 401.
+ This distinction matters for client re-registration behavior.
-This handler transforms 401 responses with `unauthorized_client` to use
-`invalid_client` instead, making the error semantics correct per OAuth spec.
+2. Status code: The SDK returns HTTP 400 for all token errors including
+ `invalid_grant` (expired/invalid tokens). However, the MCP spec requires:
+ "Invalid or expired tokens MUST receive a HTTP 401 response."
+
+This handler transforms responses to be compliant with both OAuth 2.1 and MCP specs.
**Methods:**
@@ -42,7 +42,7 @@ handle(self, request: Any)
Wrap SDK handle() and transform auth error responses.
-### `AuthProvider`
+### `AuthProvider`
Base class for all FastMCP authentication providers.
@@ -55,7 +55,7 @@ custom authentication routes.
**Methods:**
-#### `verify_token`
+#### `verify_token`
```python
verify_token(self, token: str) -> AccessToken | None
@@ -72,7 +72,24 @@ All auth providers must implement token verification.
- AccessToken object if valid, None if invalid or expired
-#### `get_routes`
+#### `set_mcp_path`
+
+```python
+set_mcp_path(self, mcp_path: str | None) -> None
+```
+
+Set the MCP endpoint path and compute resource URL.
+
+This method is called by get_routes() to configure the expected
+resource URL before route creation. Subclasses can override to
+perform additional initialization that depends on knowing the
+MCP endpoint path.
+
+**Args:**
+- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
+
+
+#### `get_routes`
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
@@ -96,7 +113,7 @@ provider does not create the actual MCP endpoint route.
- List of all routes for this provider (excluding the MCP endpoint itself)
-#### `get_well_known_routes`
+#### `get_well_known_routes`
```python
get_well_known_routes(self, mcp_path: str | None = None) -> list[Route]
@@ -124,7 +141,7 @@ This is used to construct path-scoped well-known URLs.
- List of well-known discovery routes (typically mounted at root level)
-#### `get_middleware`
+#### `get_middleware`
```python
get_middleware(self) -> list
@@ -136,7 +153,7 @@ Get HTTP application-level middleware for this auth provider.
- List of Starlette Middleware instances to apply to the HTTP app
-### `TokenVerifier`
+### `TokenVerifier`
Base class for token verifiers (Resource Servers).
@@ -147,7 +164,7 @@ Token verifiers typically don't provide authentication routes by default.
**Methods:**
-#### `verify_token`
+#### `verify_token`
```python
verify_token(self, token: str) -> AccessToken | None
@@ -156,7 +173,7 @@ verify_token(self, token: str) -> AccessToken | None
Verify a bearer token and return access info if valid.
-### `RemoteAuthProvider`
+### `RemoteAuthProvider`
Authentication provider for resource servers that verify tokens from known authorization servers.
@@ -173,7 +190,7 @@ the authorization servers that issue valid tokens.
**Methods:**
-#### `verify_token`
+#### `verify_token`
```python
verify_token(self, token: str) -> AccessToken | None
@@ -182,7 +199,7 @@ verify_token(self, token: str) -> AccessToken | None
Verify token using the configured token verifier.
-#### `get_routes`
+#### `get_routes`
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
@@ -193,7 +210,7 @@ Get routes for this provider.
Creates protected resource metadata routes (RFC 9728).
-### `OAuthProvider`
+### `OAuthProvider`
OAuth Authorization Server provider.
@@ -204,7 +221,7 @@ authorization flows, token issuance, and token verification.
**Methods:**
-#### `verify_token`
+#### `verify_token`
```python
verify_token(self, token: str) -> AccessToken | None
@@ -222,7 +239,7 @@ to our existing load_access_token method.
- AccessToken object if valid, None if invalid or expired
-#### `get_routes`
+#### `get_routes`
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
@@ -238,7 +255,7 @@ This method creates the full set of OAuth routes including:
- List of OAuth routes
-#### `get_well_known_routes`
+#### `get_well_known_routes`
```python
get_well_known_routes(self, mcp_path: str | None = None) -> list[Route]
diff --git a/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx b/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx
index 058055b82..46f190430 100644
--- a/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx
@@ -26,7 +26,7 @@ production use with enterprise identity providers.
## Functions
-### `create_consent_html`
+### `create_consent_html`
```python
create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id: str, csrf_token: str, client_name: str | None = None, title: str = 'Application Access Request', server_name: str | None = None, server_icon_url: str | None = None, server_website_url: str | None = None, client_website_url: str | None = None, csp_policy: str | None = None) -> str
@@ -42,7 +42,7 @@ If empty string "", disables CSP entirely (no meta tag is rendered).
If a non-empty string, uses that as the CSP policy value.
-### `create_error_html`
+### `create_error_html`
```python
create_error_html(error_title: str, error_message: str, error_details: dict[str, str] | None = None, server_name: str | None = None, server_icon_url: str | None = None) -> str
@@ -64,7 +64,7 @@ Create a styled HTML error page for OAuth errors.
## Classes
-### `OAuthTransaction`
+### `OAuthTransaction`
OAuth transaction state for consent flow.
@@ -73,7 +73,7 @@ Stored server-side to track active authorization flows with client context.
Includes CSRF tokens for consent protection per MCP security best practices.
-### `ClientCode`
+### `ClientCode`
Client authorization code with PKCE and upstream tokens.
@@ -82,7 +82,7 @@ Stored server-side after upstream IdP callback. Contains the upstream
tokens bound to the client's PKCE challenge for secure token exchange.
-### `UpstreamTokenSet`
+### `UpstreamTokenSet`
Stored upstream OAuth tokens from identity provider.
@@ -92,7 +92,7 @@ and stored in plaintext within this model. Encryption is handled transparently
at the storage layer via FernetEncryptionWrapper. Tokens are never exposed to MCP clients.
-### `JTIMapping`
+### `JTIMapping`
Maps FastMCP token JTI to upstream token ID.
@@ -101,7 +101,7 @@ This allows stateless JWT validation while still being able to look up
the corresponding upstream token when tools need to access upstream APIs.
-### `RefreshTokenMetadata`
+### `RefreshTokenMetadata`
Metadata for a refresh token, stored keyed by token hash.
@@ -110,7 +110,7 @@ We store only metadata (not the token itself) for security - if storage
is compromised, attackers get hashes they can't reverse into usable tokens.
-### `ProxyDCRClient`
+### `ProxyDCRClient`
Client for DCR proxy with configurable redirect URI validation.
@@ -140,7 +140,7 @@ arise from accepting arbitrary redirect URIs.
**Methods:**
-#### `validate_redirect_uri`
+#### `validate_redirect_uri`
```python
validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl
@@ -154,7 +154,7 @@ This is essential for cached token scenarios where the client may
reconnect with a different port.
-### `OAuthProxy`
+### `OAuthProxy`
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
@@ -268,7 +268,36 @@ Handles provider-specific requirements:
**Methods:**
-#### `get_client`
+#### `set_mcp_path`
+
+```python
+set_mcp_path(self, mcp_path: str | None) -> None
+```
+
+Set the MCP endpoint path and create JWTIssuer with correct audience.
+
+This method is called by get_routes() to configure the resource URL
+and create the JWTIssuer. The JWT audience is set to the full resource
+URL (e.g., http://localhost:8000/mcp) to ensure tokens are bound to
+this specific MCP endpoint.
+
+**Args:**
+- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
+
+
+#### `jwt_issuer`
+
+```python
+jwt_issuer(self) -> JWTIssuer
+```
+
+Get the JWT issuer, ensuring it has been initialized.
+
+The JWT issuer is created when set_mcp_path() is called (via get_routes()).
+This property ensures a clear error if used before initialization.
+
+
+#### `get_client`
```python
get_client(self, client_id: str) -> OAuthClientInformationFull | None
@@ -280,7 +309,7 @@ provided to the DCR client during registration, not the upstream client ID.
For unregistered clients, returns None (which will raise an error in the SDK).
-#### `register_client`
+#### `register_client`
```python
register_client(self, client_info: OAuthClientInformationFull) -> None
@@ -294,7 +323,7 @@ redirect URI will likely be localhost or unknown to the proxied IDP. The
proxied IDP only knows about this server's fixed redirect URI.
-#### `authorize`
+#### `authorize`
```python
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
@@ -303,15 +332,16 @@ authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams)
Start OAuth transaction and route through consent interstitial.
Flow:
-1. Store transaction with client details and PKCE (if forwarding)
-2. Return local /consent URL; browser visits consent first
-3. Consent handler redirects to upstream IdP if approved/already approved
+1. Validate client's resource matches server's resource URL (security check)
+2. Store transaction with client details and PKCE (if forwarding)
+3. Return local /consent URL; browser visits consent first
+4. Consent handler redirects to upstream IdP if approved/already approved
If consent is disabled (require_authorization_consent=False), skip the consent screen
and redirect directly to the upstream IdP.
-#### `load_authorization_code`
+#### `load_authorization_code`
```python
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
@@ -323,7 +353,7 @@ Look up our client code and return authorization code object
with PKCE challenge for validation.
-#### `exchange_authorization_code`
+#### `exchange_authorization_code`
```python
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
@@ -341,7 +371,7 @@ Implements the token factory pattern:
PKCE validation is handled by the MCP framework before this method is called.
-#### `load_refresh_token`
+#### `load_refresh_token`
```python
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
@@ -353,7 +383,7 @@ Looks up by token hash and reconstructs the RefreshToken object.
Validates that the token belongs to the requesting client.
-#### `exchange_refresh_token`
+#### `exchange_refresh_token`
```python
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
@@ -370,7 +400,7 @@ Implements two-tier refresh:
6. Keep same FastMCP refresh token (unless upstream rotates)
-#### `load_access_token`
+#### `load_access_token`
```python
load_access_token(self, token: str) -> AccessToken | None
@@ -389,7 +419,7 @@ The FastMCP JWT is a reference token - all authorization data comes
from validating the upstream token via the TokenVerifier.
-#### `revoke_token`
+#### `revoke_token`
```python
revoke_token(self, token: AccessToken | RefreshToken) -> None
@@ -402,7 +432,7 @@ For all tokens, attempts upstream revocation if endpoint is configured.
Access token JTI mappings expire via TTL.
-#### `get_routes`
+#### `get_routes`
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx b/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx
index 630f8e0a0..7210566bf 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx
@@ -33,8 +33,9 @@ IMPORTANT SETUP REQUIREMENTS:
- Asymmetric keys (RS256/ES256) are recommended for production
2. JWT Verification:
- - FastMCP verifies JWTs using the JWKS endpoint at {project_url}/auth/v1/.well-known/jwks.json
- - JWTs are issued by {project_url}/auth/v1
+ - FastMCP verifies JWTs using the JWKS endpoint at {project_url}{auth_route}/.well-known/jwks.json
+ - JWTs are issued by {project_url}{auth_route}
+ - Default auth_route is "/auth/v1" (can be customized for self-hosted setups)
- Tokens are cached for up to 10 minutes by Supabase's edge servers
- Algorithm must match your Supabase Auth configuration
@@ -49,7 +50,7 @@ https://supabase.com/docs/guides/auth/jwts
**Methods:**
-#### `get_routes`
+#### `get_routes`
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
diff --git a/docs/python-sdk/fastmcp-server-context.mdx b/docs/python-sdk/fastmcp-server-context.mdx
index fe5572263..91e4c1195 100644
--- a/docs/python-sdk/fastmcp-server-context.mdx
+++ b/docs/python-sdk/fastmcp-server-context.mdx
@@ -110,7 +110,30 @@ async def on_request(self, context, call_next):
```
-#### `report_progress`
+#### `lifespan_context`
+
+```python
+lifespan_context(self) -> dict[str, Any]
+```
+
+Access the server's lifespan context.
+
+Returns the context dict yielded by the server's lifespan function.
+Returns an empty dict if no lifespan was configured or if the MCP
+session is not yet established.
+
+Example:
+```python
+@server.tool
+def my_tool(ctx: Context) -> str:
+ db = ctx.lifespan_context.get("db")
+ if db:
+ return db.query("SELECT 1")
+ return "No database connection"
+```
+
+
+#### `report_progress`
```python
report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None
@@ -123,7 +146,7 @@ Report progress for the current operation.
- `total`: Optional total value e.g. 100
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> list[SDKResource]
@@ -135,7 +158,7 @@ List all available resources from the server.
- List of Resource objects available on the server
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> list[SDKPrompt]
@@ -147,7 +170,7 @@ List all available prompts from the server.
- List of Prompt objects available on the server
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult
@@ -163,7 +186,7 @@ Get a prompt by name with optional arguments.
- The prompt result
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str | AnyUrl) -> ResourceResult
@@ -178,7 +201,7 @@ Read a resource by URI.
- ResourceResult with contents
-#### `log`
+#### `log`
```python
log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -196,7 +219,7 @@ Messages sent to Clients are also logged to the `fastmcp.server.context.to_clien
- `extra`: Optional mapping for additional arguments
-#### `client_id`
+#### `client_id`
```python
client_id(self) -> str | None
@@ -205,7 +228,7 @@ client_id(self) -> str | None
Get the client ID if available.
-#### `request_id`
+#### `request_id`
```python
request_id(self) -> str
@@ -216,7 +239,7 @@ Get the unique ID for this request.
Raises RuntimeError if MCP request context is not available.
-#### `session_id`
+#### `session_id`
```python
session_id(self) -> str
@@ -233,7 +256,7 @@ the same client session.
- for other transports.
-#### `session`
+#### `session`
```python
session(self) -> ServerSession
@@ -244,7 +267,7 @@ Access to the underlying session for advanced usage.
Raises RuntimeError if MCP request context is not available.
-#### `debug`
+#### `debug`
```python
debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -255,7 +278,7 @@ Send a `DEBUG`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `info`
+#### `info`
```python
info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -266,7 +289,7 @@ Send a `INFO`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `warning`
+#### `warning`
```python
warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -277,7 +300,7 @@ Send a `WARNING`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `error`
+#### `error`
```python
error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -288,7 +311,7 @@ Send a `ERROR`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `list_roots`
+#### `list_roots`
```python
list_roots(self) -> list[Root]
@@ -297,7 +320,7 @@ list_roots(self) -> list[Root]
List the roots available to the server, as indicated by the client.
-#### `send_notification`
+#### `send_notification`
```python
send_notification(self, notification: mcp.types.ServerNotificationType) -> None
@@ -313,7 +336,7 @@ for the background flusher.
- `notification`: An MCP notification instance (e.g., ToolListChangedNotification())
-#### `send_notification_sync`
+#### `send_notification_sync`
```python
send_notification_sync(self, notification: mcp.types.ServerNotificationType) -> None
@@ -328,7 +351,7 @@ sent within ~1 second by the background flusher.
- `notification`: An MCP notification instance (e.g., ToolListChangedNotification())
-#### `close_sse_stream`
+#### `close_sse_stream`
```python
close_sse_stream(self) -> None
@@ -346,7 +369,7 @@ Instead of holding a connection open for minutes, you can periodically close
and let the client reconnect.
-#### `sample_step`
+#### `sample_step`
```python
sample_step(self, messages: str | Sequence[str | SamplingMessage]) -> SampleStep
@@ -383,7 +406,7 @@ Tools can raise ToolError to bypass masking.
- - .text: The text content (if any)
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[ResultT]
@@ -392,7 +415,7 @@ sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[
Overload: With result_type, returns SamplingResult[ResultT].
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[str]
@@ -401,7 +424,7 @@ sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[
Overload: Without result_type, returns SamplingResult[str].
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[ResultT] | SamplingResult[str]
@@ -443,43 +466,43 @@ Tools can raise ToolError to bypass masking.
- - .history: All messages exchanged during sampling
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: dict[str, dict[str, str]]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[list[str]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[dict[str, dict[str, str]]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: type[T] | list[str] | dict[str, dict[str, str]] | list[list[str]] | list[dict[str, dict[str, str]]] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
@@ -508,7 +531,7 @@ type or dataclass or BaseModel. If it is a primitive type, an
object schema with a single "value" field will be generated.
-#### `set_state`
+#### `set_state`
```python
set_state(self, key: str, value: Any) -> None
@@ -517,7 +540,7 @@ set_state(self, key: str, value: Any) -> None
Set a value in the context state.
-#### `get_state`
+#### `get_state`
```python
get_state(self, key: str) -> Any
diff --git a/docs/python-sdk/fastmcp-server-elicitation.mdx b/docs/python-sdk/fastmcp-server-elicitation.mdx
index 109181232..c8c3267d0 100644
--- a/docs/python-sdk/fastmcp-server-elicitation.mdx
+++ b/docs/python-sdk/fastmcp-server-elicitation.mdx
@@ -18,12 +18,12 @@ Parse response_type into schema and handling configuration.
Supports multiple syntaxes:
- None: Empty object schema, expect empty response
-- dict: {"low": {"title": "..."}} -> single-select titled enum
+- dict: `{"low": {"title": "..."}}` -> single-select titled enum
- list patterns:
- - [["a", "b"]] -> multi-select untitled
- - [{"low": {...}}] -> multi-select titled
- - ["a", "b"] -> single-select untitled
-- list\[X] type annotation: multi-select with type
+ - `[["a", "b"]]` -> multi-select untitled
+ - `[{"low": {...}}]` -> multi-select titled
+ - `["a", "b"]` -> single-select untitled
+- `list[X]` type annotation: multi-select with type
- Scalar types (bool, int, float, str, Literal, Enum): single value
- Other types (dataclass, BaseModel): use directly
@@ -45,7 +45,7 @@ Handle an accepted elicitation response.
- AcceptedElicitation with the extracted/validated data
-### `get_elicitation_schema`
+### `get_elicitation_schema`
```python
get_elicitation_schema(response_type: type[T]) -> dict[str, Any]
@@ -58,7 +58,7 @@ Get the schema for an elicitation response.
- `response_type`: The type of the response
-### `validate_elicitation_json_schema`
+### `validate_elicitation_json_schema`
```python
validate_elicitation_json_schema(schema: dict[str, Any]) -> None
@@ -121,7 +121,7 @@ enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue
Generate inline enum schema.
-Always generates enum pattern: {"enum": [value, ...]}
+Always generates enum pattern: `{"enum": [value, ...]}`
Titled enums are handled separately via dict-based syntax in ctx.elicit().
diff --git a/docs/python-sdk/fastmcp-server-lifespan.mdx b/docs/python-sdk/fastmcp-server-lifespan.mdx
new file mode 100644
index 000000000..f8582a304
--- /dev/null
+++ b/docs/python-sdk/fastmcp-server-lifespan.mdx
@@ -0,0 +1,101 @@
+---
+title: lifespan
+sidebarTitle: lifespan
+---
+
+# `fastmcp.server.lifespan`
+
+
+Composable lifespans for FastMCP servers.
+
+This module provides a `@lifespan` decorator for creating composable server lifespans
+that can be combined using the `|` operator.
+
+Example:
+ ```python
+ from fastmcp import FastMCP
+ from fastmcp.server.lifespan import lifespan
+
+ @lifespan
+ async def db_lifespan(server):
+ conn = await connect_db()
+ yield {"db": conn}
+ await conn.close()
+
+ @lifespan
+ async def cache_lifespan(server):
+ cache = await connect_cache()
+ yield {"cache": cache}
+ await cache.close()
+
+ mcp = FastMCP("server", lifespan=db_lifespan | cache_lifespan)
+ ```
+
+To compose with existing `@asynccontextmanager` lifespans, wrap them explicitly:
+
+ ```python
+ from contextlib import asynccontextmanager
+ from fastmcp.server.lifespan import lifespan, ContextManagerLifespan
+
+ @asynccontextmanager
+ async def legacy_lifespan(server):
+ yield {"legacy": True}
+
+ @lifespan
+ async def new_lifespan(server):
+ yield {"new": True}
+
+ # Wrap the legacy lifespan explicitly
+ combined = ContextManagerLifespan(legacy_lifespan) | new_lifespan
+ ```
+
+
+## Functions
+
+### `lifespan`
+
+```python
+lifespan(fn: LifespanFn) -> Lifespan
+```
+
+
+Decorator to create a composable lifespan.
+
+Use this decorator on an async generator function to make it composable
+with other lifespans using the `|` operator.
+
+**Args:**
+- `fn`: An async generator function that takes a FastMCP server and yields
+a dict for the lifespan context.
+
+**Returns:**
+- A composable Lifespan wrapper.
+
+
+## Classes
+
+### `Lifespan`
+
+
+Composable lifespan wrapper.
+
+Wraps an async generator function and enables composition via the `|` operator.
+The wrapped function should yield a dict that becomes part of the lifespan context.
+
+
+### `ContextManagerLifespan`
+
+
+Lifespan wrapper for already-wrapped context manager functions.
+
+Use this for functions already decorated with @asynccontextmanager.
+
+
+### `ComposedLifespan`
+
+
+Two lifespans composed together.
+
+Enters the left lifespan first, then the right. Exits in reverse order.
+Results are shallow-merged into a single dict.
+
diff --git a/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx b/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx
index a6a9f5a37..476e54f1f 100644
--- a/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx
+++ b/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx
@@ -27,7 +27,7 @@ run(self, arguments: dict[str, Any]) -> ToolResult
Execute the HTTP request using RequestDirector.
-### `OpenAPIResource`
+### `OpenAPIResource`
Resource implementation for OpenAPI endpoints.
@@ -35,7 +35,7 @@ Resource implementation for OpenAPI endpoints.
**Methods:**
-#### `read`
+#### `read`
```python
read(self) -> ResourceResult
@@ -44,7 +44,7 @@ read(self) -> ResourceResult
Fetch the resource data by making an HTTP request.
-### `OpenAPIResourceTemplate`
+### `OpenAPIResourceTemplate`
Resource template implementation for OpenAPI endpoints.
@@ -52,7 +52,7 @@ Resource template implementation for OpenAPI endpoints.
**Methods:**
-#### `create_resource`
+#### `create_resource`
```python
create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> Resource
diff --git a/docs/python-sdk/fastmcp-server-providers-proxy.mdx b/docs/python-sdk/fastmcp-server-providers-proxy.mdx
index 5076de307..90e779647 100644
--- a/docs/python-sdk/fastmcp-server-providers-proxy.mdx
+++ b/docs/python-sdk/fastmcp-server-providers-proxy.mdx
@@ -15,7 +15,7 @@ classes that forward execution to remote servers.
## Functions
-### `default_proxy_roots_handler`
+### `default_proxy_roots_handler`
```python
default_proxy_roots_handler(context: RequestContext[ClientSession, LifespanContextT]) -> RootsList
@@ -25,7 +25,7 @@ default_proxy_roots_handler(context: RequestContext[ClientSession, LifespanConte
Forward list roots request from remote server to proxy's connected clients.
-### `default_proxy_sampling_handler`
+### `default_proxy_sampling_handler`
```python
default_proxy_sampling_handler(messages: list[mcp.types.SamplingMessage], params: mcp.types.CreateMessageRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> mcp.types.CreateMessageResult
@@ -35,7 +35,7 @@ default_proxy_sampling_handler(messages: list[mcp.types.SamplingMessage], params
Forward sampling request from remote server to proxy's connected clients.
-### `default_proxy_elicitation_handler`
+### `default_proxy_elicitation_handler`
```python
default_proxy_elicitation_handler(message: str, response_type: type, params: mcp.types.ElicitRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> ElicitResult
@@ -45,7 +45,7 @@ default_proxy_elicitation_handler(message: str, response_type: type, params: mcp
Forward elicitation request from remote server to proxy's connected clients.
-### `default_proxy_log_handler`
+### `default_proxy_log_handler`
```python
default_proxy_log_handler(message: LogMessage) -> None
@@ -55,7 +55,7 @@ default_proxy_log_handler(message: LogMessage) -> None
Forward log notification from remote server to proxy's connected clients.
-### `default_proxy_progress_handler`
+### `default_proxy_progress_handler`
```python
default_proxy_progress_handler(progress: float, total: float | None, message: str | None) -> None
@@ -67,7 +67,7 @@ Forward progress notification from remote server to proxy's connected clients.
## Classes
-### `ProxyTool`
+### `ProxyTool`
A Tool that represents and executes a tool on a remote server.
@@ -75,7 +75,7 @@ A Tool that represents and executes a tool on a remote server.
**Methods:**
-#### `model_copy`
+#### `model_copy`
```python
model_copy(self, **kwargs: Any) -> ProxyTool
@@ -84,7 +84,7 @@ model_copy(self, **kwargs: Any) -> ProxyTool
Override to preserve _backend_name when name changes.
-#### `from_mcp_tool`
+#### `from_mcp_tool`
```python
from_mcp_tool(cls, client_factory: ClientFactoryT, mcp_tool: mcp.types.Tool) -> ProxyTool
@@ -93,7 +93,7 @@ from_mcp_tool(cls, client_factory: ClientFactoryT, mcp_tool: mcp.types.Tool) ->
Factory method to create a ProxyTool from a raw MCP tool schema.
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResult
@@ -102,7 +102,7 @@ run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResu
Executes the tool by making a call through the client.
-### `ProxyResource`
+### `ProxyResource`
A Resource that represents and reads a resource from a remote server.
@@ -110,7 +110,7 @@ A Resource that represents and reads a resource from a remote server.
**Methods:**
-#### `model_copy`
+#### `model_copy`
```python
model_copy(self, **kwargs: Any) -> ProxyResource
@@ -119,7 +119,7 @@ model_copy(self, **kwargs: Any) -> ProxyResource
Override to preserve _backend_uri when uri changes.
-#### `from_mcp_resource`
+#### `from_mcp_resource`
```python
from_mcp_resource(cls, client_factory: ClientFactoryT, mcp_resource: mcp.types.Resource) -> ProxyResource
@@ -128,7 +128,7 @@ from_mcp_resource(cls, client_factory: ClientFactoryT, mcp_resource: mcp.types.R
Factory method to create a ProxyResource from a raw MCP resource schema.
-#### `read`
+#### `read`
```python
read(self) -> ResourceResult
@@ -137,7 +137,7 @@ read(self) -> ResourceResult
Read the resource content from the remote server.
-### `ProxyTemplate`
+### `ProxyTemplate`
A ResourceTemplate that represents and creates resources from a remote server template.
@@ -145,7 +145,7 @@ A ResourceTemplate that represents and creates resources from a remote server te
**Methods:**
-#### `model_copy`
+#### `model_copy`
```python
model_copy(self, **kwargs: Any) -> ProxyTemplate
@@ -154,7 +154,7 @@ model_copy(self, **kwargs: Any) -> ProxyTemplate
Override to preserve _backend_uri_template when uri_template changes.
-#### `from_mcp_template`
+#### `from_mcp_template`
```python
from_mcp_template(cls, client_factory: ClientFactoryT, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate
@@ -163,7 +163,7 @@ from_mcp_template(cls, client_factory: ClientFactoryT, mcp_template: mcp.types.R
Factory method to create a ProxyTemplate from a raw MCP template schema.
-#### `create_resource`
+#### `create_resource`
```python
create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> ProxyResource
@@ -172,7 +172,7 @@ create_resource(self, uri: str, params: dict[str, Any], context: Context | None
Create a resource from the template by calling the remote server.
-### `ProxyPrompt`
+### `ProxyPrompt`
A Prompt that represents and renders a prompt from a remote server.
@@ -180,7 +180,7 @@ A Prompt that represents and renders a prompt from a remote server.
**Methods:**
-#### `model_copy`
+#### `model_copy`
```python
model_copy(self, **kwargs: Any) -> ProxyPrompt
@@ -189,7 +189,7 @@ model_copy(self, **kwargs: Any) -> ProxyPrompt
Override to preserve _backend_name when name changes.
-#### `from_mcp_prompt`
+#### `from_mcp_prompt`
```python
from_mcp_prompt(cls, client_factory: ClientFactoryT, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt
@@ -198,7 +198,7 @@ from_mcp_prompt(cls, client_factory: ClientFactoryT, mcp_prompt: mcp.types.Promp
Factory method to create a ProxyPrompt from a raw MCP prompt schema.
-#### `render`
+#### `render`
```python
render(self, arguments: dict[str, Any]) -> PromptResult
@@ -207,7 +207,7 @@ render(self, arguments: dict[str, Any]) -> PromptResult
Render the prompt by making a call through the client.
-### `ProxyProvider`
+### `ProxyProvider`
Provider that proxies to a remote MCP server via a client factory.
@@ -221,7 +221,7 @@ because tasks cannot be executed through a proxy.
**Methods:**
-#### `list_tools`
+#### `list_tools`
```python
list_tools(self) -> Sequence[Tool]
@@ -230,7 +230,7 @@ list_tools(self) -> Sequence[Tool]
List all tools from the remote server.
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> Sequence[Resource]
@@ -239,7 +239,7 @@ list_resources(self) -> Sequence[Resource]
List all resources from the remote server.
-#### `list_resource_templates`
+#### `list_resource_templates`
```python
list_resource_templates(self) -> Sequence[ResourceTemplate]
@@ -248,7 +248,7 @@ list_resource_templates(self) -> Sequence[ResourceTemplate]
List all resource templates from the remote server.
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> Sequence[Prompt]
@@ -257,7 +257,7 @@ list_prompts(self) -> Sequence[Prompt]
List all prompts from the remote server.
-#### `get_tasks`
+#### `get_tasks`
```python
get_tasks(self) -> Sequence[FastMCPComponent]
@@ -270,7 +270,7 @@ server lifespan initialization, which would open the client before any
context is set. All Proxy* components have task_config.mode="forbidden".
-### `FastMCPProxy`
+### `FastMCPProxy`
A FastMCP server that acts as a proxy to a remote MCP-compliant server.
@@ -279,7 +279,7 @@ This is a convenience wrapper that creates a FastMCP server with a
ProxyProvider. For more control, use FastMCP with add_provider(ProxyProvider(...)).
-### `ProxyClient`
+### `ProxyClient`
A proxy client that forwards advanced interactions between a remote MCP server and the proxy's connected clients.
@@ -287,7 +287,7 @@ A proxy client that forwards advanced interactions between a remote MCP server a
Supports forwarding roots, sampling, elicitation, logging, and progress.
-### `StatefulProxyClient`
+### `StatefulProxyClient`
A proxy client that provides a stateful client factory for the proxy server.
@@ -301,7 +301,7 @@ Note that it is essential to ensure that the proxy server itself is also statefu
**Methods:**
-#### `clear`
+#### `clear`
```python
clear(self)
@@ -310,7 +310,7 @@ clear(self)
Clear all cached clients and force disconnect them.
-#### `new_stateful`
+#### `new_stateful`
```python
new_stateful(self) -> Client[ClientTransportT]
diff --git a/docs/python-sdk/fastmcp-server-server.mdx b/docs/python-sdk/fastmcp-server-server.mdx
index 356abcda2..da73702c3 100644
--- a/docs/python-sdk/fastmcp-server-server.mdx
+++ b/docs/python-sdk/fastmcp-server-server.mdx
@@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers.
## Functions
-### `default_lifespan`
+### `default_lifespan`
```python
default_lifespan(server: FastMCP[LifespanResultT]) -> AsyncIterator[Any]
@@ -26,55 +26,81 @@ Default lifespan context manager that does nothing.
- An empty dictionary as the lifespan result.
+### `create_proxy`
+
+```python
+create_proxy(target: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
+```
+
+
+Create a FastMCP proxy server for the given target.
+
+This is the recommended way to create a proxy server. For lower-level control,
+use `FastMCPProxy` or `ProxyProvider` directly from `fastmcp.server.providers.proxy`.
+
+**Args:**
+- `target`: The backend to proxy to. Can be\:
+- A Client instance (connected or disconnected)
+- A ClientTransport
+- A FastMCP server instance
+- A URL string or AnyUrl
+- A Path to a server script
+- An MCPConfig or dict
+- `**settings`: Additional settings passed to FastMCPProxy (name, etc.)
+
+**Returns:**
+- A FastMCPProxy server that proxies to the target.
+
+
## Classes
-### `FastMCP`
+### `FastMCP`
**Methods:**
-#### `settings`
+#### `settings`
```python
settings(self) -> Settings
```
-#### `name`
+#### `name`
```python
name(self) -> str
```
-#### `instructions`
+#### `instructions`
```python
instructions(self) -> str | None
```
-#### `instructions`
+#### `instructions`
```python
instructions(self, value: str | None) -> None
```
-#### `version`
+#### `version`
```python
version(self) -> str | None
```
-#### `website_url`
+#### `website_url`
```python
website_url(self) -> str | None
```
-#### `icons`
+#### `icons`
```python
icons(self) -> list[mcp.types.Icon]
```
-#### `docket`
+#### `docket`
```python
docket(self) -> Docket | None
@@ -85,37 +111,41 @@ Get the Docket instance if Docket support is enabled.
Returns None if Docket is not enabled or server hasn't been started yet.
-#### `run_async`
+#### `run_async`
```python
-run_async(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
+run_async(self, transport: Transport | None = None, show_banner: bool | None = None, **transport_kwargs: Any) -> None
```
Run the FastMCP server asynchronously.
**Args:**
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
+- `show_banner`: Whether to display the server banner. If None, uses the
+FASTMCP_SHOW_SERVER_BANNER setting (default\: True).
-#### `run`
+#### `run`
```python
-run(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
+run(self, transport: Transport | None = None, show_banner: bool | None = None, **transport_kwargs: Any) -> None
```
Run the FastMCP server. Note this is a synchronous function.
**Args:**
- `transport`: Transport protocol to use ("http", "stdio", "sse", or "streamable-http")
+- `show_banner`: Whether to display the server banner. If None, uses the
+FASTMCP_SHOW_SERVER_BANNER setting (default\: True).
-#### `add_middleware`
+#### `add_middleware`
```python
add_middleware(self, middleware: Middleware) -> None
```
-#### `add_provider`
+#### `add_provider`
```python
add_provider(self, provider: Provider) -> None
@@ -131,7 +161,7 @@ always take precedence over providers.
- `provider`: A Provider instance that will provide components dynamically.
-#### `enable`
+#### `enable`
```python
enable(self) -> None
@@ -146,7 +176,7 @@ Enable components by removing from blocklist, or set allowlist with only=True.
This clears existing allowlists and sets default visibility to False.
-#### `disable`
+#### `disable`
```python
disable(self) -> None
@@ -159,7 +189,7 @@ Disable components by adding to the blocklist.
- `tags`: Tags to disable - components with these tags will be disabled.
-#### `get_tools`
+#### `get_tools`
```python
get_tools(self) -> list[Tool]
@@ -175,7 +205,7 @@ First provider wins for duplicate keys. Filters by server blocklist.
returning results. Used by MCP handlers and mounted servers.
-#### `get_tool`
+#### `get_tool`
```python
get_tool(self, name: str) -> Tool
@@ -187,7 +217,7 @@ Queries all providers in parallel to find the tool.
First provider wins. Returns only if enabled.
-#### `get_resources`
+#### `get_resources`
```python
get_resources(self) -> list[Resource]
@@ -203,7 +233,7 @@ First provider wins for duplicate keys. Filters by server blocklist.
returning results. Used by MCP handlers and mounted servers.
-#### `get_resource`
+#### `get_resource`
```python
get_resource(self, uri: str) -> Resource
@@ -215,7 +245,7 @@ Queries all providers in parallel to find the resource.
First provider wins. Returns only if enabled.
-#### `get_resource_templates`
+#### `get_resource_templates`
```python
get_resource_templates(self) -> list[ResourceTemplate]
@@ -231,7 +261,7 @@ First provider wins for duplicate keys. Filters by server blocklist.
returning results. Used by MCP handlers and mounted servers.
-#### `get_resource_template`
+#### `get_resource_template`
```python
get_resource_template(self, uri: str) -> ResourceTemplate
@@ -243,7 +273,7 @@ Queries all providers in parallel to find the template.
First provider wins. Returns only if enabled.
-#### `get_prompts`
+#### `get_prompts`
```python
get_prompts(self) -> list[Prompt]
@@ -259,7 +289,7 @@ First provider wins for duplicate keys. Filters by server blocklist.
returning results. Used by MCP handlers and mounted servers.
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str) -> Prompt
@@ -271,7 +301,7 @@ Queries all providers in parallel to find the prompt.
First provider wins. Returns only if enabled.
-#### `get_component`
+#### `get_component`
```python
get_component(self, key: str) -> Tool | Resource | ResourceTemplate | Prompt
@@ -292,19 +322,19 @@ First provider wins.
- `NotFoundError`: If no component is found with the given key.
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolResult
```
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.CreateTaskResult
```
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolResult | mcp.types.CreateTaskResult
@@ -333,19 +363,19 @@ return ToolResult.
- `ValidationError`: If arguments fail validation
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str) -> ResourceResult
```
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str) -> mcp.types.CreateTaskResult
```
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str) -> ResourceResult | mcp.types.CreateTaskResult
@@ -373,19 +403,19 @@ return ResourceResult.
- `ResourceError`: If resource read fails
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptResult
```
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.CreateTaskResult
```
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptResult | mcp.types.CreateTaskResult
@@ -414,7 +444,7 @@ return PromptResult.
- `PromptError`: If prompt rendering fails
-#### `custom_route`
+#### `custom_route`
```python
custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True) -> Callable[[Callable[[Request], Awaitable[Response]]], Callable[[Request], Awaitable[Response]]]
@@ -435,7 +465,7 @@ Starlette's reverse URL lookup feature)
- `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
-#### `add_tool`
+#### `add_tool`
```python
add_tool(self, tool: Tool) -> Tool
@@ -453,7 +483,7 @@ with the Context type annotation. See the @tool decorator for examples.
- The tool instance that was added to the server.
-#### `remove_tool`
+#### `remove_tool`
```python
remove_tool(self, name: str) -> None
@@ -468,7 +498,7 @@ Remove a tool from the server.
- `NotFoundError`: If the tool is not found
-#### `add_tool_transformation`
+#### `add_tool_transformation`
```python
add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None
@@ -477,7 +507,7 @@ add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfi
Add a tool transformation.
-#### `remove_tool_transformation`
+#### `remove_tool_transformation`
```python
remove_tool_transformation(self, tool_name: str) -> None
@@ -486,19 +516,19 @@ remove_tool_transformation(self, tool_name: str) -> None
Remove a tool transformation.
-#### `tool`
+#### `tool`
```python
tool(self, name_or_fn: AnyFunction) -> FunctionTool
```
-#### `tool`
+#### `tool`
```python
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
```
-#### `tool`
+#### `tool`
```python
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool | partial[Callable[[AnyFunction], FunctionTool] | FunctionTool]
@@ -554,7 +584,7 @@ server.tool(my_function, name="custom_name")
```
-#### `add_resource`
+#### `add_resource`
```python
add_resource(self, resource: Resource) -> Resource
@@ -569,7 +599,7 @@ Add a resource to the server.
- The resource instance that was added to the server.
-#### `add_template`
+#### `add_template`
```python
add_template(self, template: ResourceTemplate) -> ResourceTemplate
@@ -584,7 +614,7 @@ Add a resource template to the server.
- The template instance that was added to the server.
-#### `resource`
+#### `resource`
```python
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
@@ -643,7 +673,7 @@ async def get_weather(city: str) -> str:
```
-#### `add_prompt`
+#### `add_prompt`
```python
add_prompt(self, prompt: Prompt) -> Prompt
@@ -658,19 +688,19 @@ Add a prompt to the server.
- The prompt instance that was added to the server.
-#### `prompt`
+#### `prompt`
```python
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
```
-#### `prompt`
+#### `prompt`
```python
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
```
-#### `prompt`
+#### `prompt`
```python
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt | partial[Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt]
@@ -747,10 +777,10 @@ Decorator to register a prompt.
```
-#### `run_stdio_async`
+#### `run_stdio_async`
```python
-run_stdio_async(self, show_banner: bool = True, log_level: str | None = None) -> None
+run_stdio_async(self, show_banner: bool = True, log_level: str | None = None, stateless: bool = False) -> None
```
Run the server using stdio transport.
@@ -758,12 +788,13 @@ Run the server using stdio transport.
**Args:**
- `show_banner`: Whether to display the server banner
- `log_level`: Log level for the server
+- `stateless`: Whether to run in stateless mode (no session initialization)
-#### `run_http_async`
+#### `run_http_async`
```python
-run_http_async(self, show_banner: bool = True, transport: Literal['http', 'streamable-http', 'sse'] = 'http', host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None) -> None
+run_http_async(self, show_banner: bool = True, transport: Literal['http', 'streamable-http', 'sse'] = 'http', host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, stateless: bool | None = None) -> None
```
Run the server using HTTP transport.
@@ -778,9 +809,10 @@ Run the server using HTTP transport.
- `middleware`: A list of middleware to apply to the app
- `json_response`: Whether to use JSON response format (defaults to settings.json_response)
- `stateless_http`: Whether to use stateless HTTP (defaults to settings.stateless_http)
+- `stateless`: Alias for stateless_http for CLI consistency
-#### `http_app`
+#### `http_app`
```python
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http', event_store: EventStore | None = None, retry_interval: int | None = None) -> StarletteWithLifespan
@@ -806,7 +838,7 @@ streamable-http transport.
- A Starlette application configured with the specified transport
-#### `mount`
+#### `mount`
```python
mount(self, server: FastMCP[LifespanResultT], namespace: str | None = None, as_proxy: bool | None = None, tool_names: dict[str, str] | None = None, prefix: str | None = None) -> None
@@ -845,7 +877,7 @@ middleware chain is invoked for all operations (tool calls, resource reads, prom
- `namespace`: Optional namespace to use for the mounted server's objects. If None,
the server's objects are accessible with their original names.
- `as_proxy`: Deprecated. Mounted servers now always have their lifespan and
-middleware invoked. To create a proxy server, use FastMCP.as_proxy()
+middleware invoked. To create a proxy server, use create_proxy()
explicitly before mounting.
- `tool_names`: Optional mapping of original tool names to custom names. Use this
to override namespaced names. Keys are the original tool names from the
@@ -853,7 +885,7 @@ mounted server.
- `prefix`: Deprecated. Use namespace instead.
-#### `import_server`
+#### `import_server`
```python
import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None) -> None
@@ -894,7 +926,7 @@ templates, and prompts are imported with their original names.
objects are imported with their original names.
-#### `from_openapi`
+#### `from_openapi`
```python
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, name: str = 'OpenAPI Server', route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, timeout: float | None = None, **settings: Any) -> Self
@@ -918,7 +950,7 @@ Create a FastMCP server from an OpenAPI specification.
- A FastMCP server with an OpenAPIProvider attached.
-#### `from_fastapi`
+#### `from_fastapi`
```python
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, timeout: float | None = None, **settings: Any) -> Self
@@ -942,7 +974,7 @@ Create a FastMCP server from a FastAPI application.
- A FastMCP server with an OpenAPIProvider attached.
-#### `as_proxy`
+#### `as_proxy`
```python
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
@@ -950,13 +982,17 @@ as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any]
Create a FastMCP proxy server for the given backend.
+.. deprecated::
+ Use :func:`fastmcp.server.create_proxy` instead.
+ This method will be removed in a future version.
+
The `backend` argument can be either an existing `fastmcp.client.Client`
instance or any value accepted as the `transport` argument of
`fastmcp.client.Client`. This mirrors the convenience of the
`fastmcp.client.Client` constructor.
-#### `generate_name`
+#### `generate_name`
```python
generate_name(cls, name: str | None = None) -> str
diff --git a/docs/python-sdk/fastmcp-server-tasks-keys.mdx b/docs/python-sdk/fastmcp-server-tasks-keys.mdx
index 8094a28a7..c49d033a4 100644
--- a/docs/python-sdk/fastmcp-server-tasks-keys.mdx
+++ b/docs/python-sdk/fastmcp-server-tasks-keys.mdx
@@ -9,7 +9,7 @@ sidebarTitle: keys
Task key management for SEP-1686 background tasks.
Task keys encode security scoping and metadata in the Docket key format:
- {session_id}:{client_task_id}:{task_type}:{component_identifier}
+ `{session_id}:{client_task_id}:{task_type}:{component_identifier}`
This format provides:
- Session-based security scoping (prevents cross-session access)
@@ -28,7 +28,7 @@ build_task_key(session_id: str, client_task_id: str, task_type: str, component_i
Build Docket task key with embedded metadata.
-Format: {session_id}:{client_task_id}:{task_type}:{component_identifier}
+Format: `{session_id}:{client_task_id}:{task_type}:{component_identifier}`
The component_identifier is URI-encoded to handle special characters (colons, slashes, etc.).
@@ -67,14 +67,12 @@ Parse Docket task key to extract metadata.
**Examples:**
>>> parse_task_key("session123:task456:tool:my_tool")
-{'session_id': 'session123', 'client_task_id': 'task456',
- 'task_type': 'tool', 'component_identifier': 'my_tool'}
+`{'session_id': 'session123', 'client_task_id': 'task456', 'task_type': 'tool', 'component_identifier': 'my_tool'}`
>>> parse_task_key("session123:task456:resource:file%3A%2F%2Fdata.txt")
-{'session_id': 'session123', 'client_task_id': 'task456',
- 'task_type': 'resource', 'component_identifier': 'file://data.txt'}
+`{'session_id': 'session123', 'client_task_id': 'task456', 'task_type': 'resource', 'component_identifier': 'file://data.txt'}`
-### `get_client_task_id_from_key`
+### `get_client_task_id_from_key`
```python
get_client_task_id_from_key(task_key: str) -> str
diff --git a/docs/python-sdk/fastmcp-server-tasks-requests.mdx b/docs/python-sdk/fastmcp-server-tasks-requests.mdx
index 25f051d14..75fe1ca67 100644
--- a/docs/python-sdk/fastmcp-server-tasks-requests.mdx
+++ b/docs/python-sdk/fastmcp-server-tasks-requests.mdx
@@ -14,7 +14,7 @@ These handlers query and manage existing tasks (contrast with handlers.py which
## Functions
-### `tasks_get_handler`
+### `tasks_get_handler`
```python
tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskResult
@@ -31,7 +31,7 @@ Handle MCP 'tasks/get' request (SEP-1686).
- Task status response with spec-compliant fields
-### `tasks_result_handler`
+### `tasks_result_handler`
```python
tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any
@@ -50,7 +50,7 @@ Converts raw task return values to MCP types based on task type.
- MCP result (CallToolResult, GetPromptResult, or ReadResourceResult)
-### `tasks_list_handler`
+### `tasks_list_handler`
```python
tasks_list_handler(server: FastMCP, params: dict[str, Any]) -> ListTasksResult
@@ -69,7 +69,7 @@ Note: With client-side tracking, this returns minimal info.
- Response with tasks list and pagination
-### `tasks_cancel_handler`
+### `tasks_cancel_handler`
```python
tasks_cancel_handler(server: FastMCP, params: dict[str, Any]) -> CancelTaskResult
diff --git a/docs/python-sdk/fastmcp-utilities-cli.mdx b/docs/python-sdk/fastmcp-utilities-cli.mdx
index c0bd2aeb8..2eb792220 100644
--- a/docs/python-sdk/fastmcp-utilities-cli.mdx
+++ b/docs/python-sdk/fastmcp-utilities-cli.mdx
@@ -40,16 +40,9 @@ run, inspect, and dev commands.
### `log_server_banner`
```python
-log_server_banner(server: FastMCP[Any], transport: Literal['stdio', 'http', 'sse', 'streamable-http']) -> None
+log_server_banner(server: FastMCP[Any]) -> None
```
Creates and logs a formatted banner with server information and logo.
-**Args:**
-- `transport`: The transport protocol being used
-- `server_name`: Optional server name to display
-- `host`: Host address (for HTTP transports)
-- `port`: Port number (for HTTP transports)
-- `path`: Server path (for HTTP transports)
-
diff --git a/docs/python-sdk/fastmcp-utilities-json_schema.mdx b/docs/python-sdk/fastmcp-utilities-json_schema.mdx
index 3be29930d..259ef10e0 100644
--- a/docs/python-sdk/fastmcp-utilities-json_schema.mdx
+++ b/docs/python-sdk/fastmcp-utilities-json_schema.mdx
@@ -7,7 +7,35 @@ sidebarTitle: json_schema
## Functions
-### `resolve_root_ref`
+### `dereference_refs`
+
+```python
+dereference_refs(schema: dict[str, Any]) -> dict[str, Any]
+```
+
+
+Resolve all $ref references in a JSON schema by inlining definitions.
+
+This function resolves $ref references that point to $defs, replacing them
+with the actual definition content while preserving sibling keywords (like
+description, default, examples) that Pydantic places alongside $ref.
+
+This is necessary because some MCP clients (e.g., VS Code Copilot) don't
+properly handle $ref in tool input schemas.
+
+For self-referencing/circular schemas where full dereferencing is not possible,
+this function falls back to resolving only the root-level $ref while preserving
+$defs for nested references.
+
+**Args:**
+- `schema`: JSON schema dict that may contain $ref references
+
+**Returns:**
+- A new schema dict with $ref resolved where possible and $defs removed
+- when no longer needed
+
+
+### `resolve_root_ref`
```python
resolve_root_ref(schema: dict[str, Any]) -> dict[str, Any]
@@ -29,19 +57,23 @@ the referenced definition while preserving $defs for nested references.
- if no resolution is needed
-### `compress_schema`
+### `compress_schema`
```python
-compress_schema(schema: dict, prune_params: list[str] | None = None, prune_defs: bool = True, prune_additional_properties: bool = True, prune_titles: bool = False) -> dict
+compress_schema(schema: dict[str, Any], prune_params: list[str] | None = None, prune_additional_properties: bool = True, prune_titles: bool = False) -> dict[str, Any]
```
-Remove the given parameters from the schema.
+Compress and optimize a JSON schema for MCP compatibility.
+
+This function dereferences all $ref entries (inlining definitions) to ensure
+compatibility with MCP clients that don't properly handle $ref in schemas
+(e.g., VS Code Copilot). It also applies various optimizations to reduce
+schema size.
**Args:**
- `schema`: The schema to compress
- `prune_params`: List of parameter names to remove from properties
-- `prune_defs`: Whether to remove unused definitions
- `prune_additional_properties`: Whether to remove additionalProperties\: false
- `prune_titles`: Whether to remove title fields from the schema
diff --git a/docs/python-sdk/fastmcp-utilities-lifespan.mdx b/docs/python-sdk/fastmcp-utilities-lifespan.mdx
new file mode 100644
index 000000000..319dbc29c
--- /dev/null
+++ b/docs/python-sdk/fastmcp-utilities-lifespan.mdx
@@ -0,0 +1,36 @@
+---
+title: lifespan
+sidebarTitle: lifespan
+---
+
+# `fastmcp.utilities.lifespan`
+
+
+Lifespan utilities for combining async context manager lifespans.
+
+## Functions
+
+### `combine_lifespans`
+
+```python
+combine_lifespans(*lifespans: Callable[[AppT], AbstractAsyncContextManager[dict[str, Any] | None]]) -> Callable[[AppT], AbstractAsyncContextManager[dict[str, Any]]]
+```
+
+
+Combine multiple lifespans into a single lifespan.
+
+Useful when mounting FastMCP into FastAPI and you need to run
+both your app's lifespan and the MCP server's lifespan.
+
+Works with both FastAPI-style lifespans (yield None) and FastMCP-style
+lifespans (yield dict). Results are merged; later lifespans override
+earlier ones on key conflicts.
+
+Lifespans are entered in order and exited in reverse order (LIFO).
+
+**Args:**
+- `*lifespans`: Lifespan context manager factories to combine.
+
+**Returns:**
+- A combined lifespan context manager factory.
+
diff --git a/docs/python-sdk/fastmcp-utilities-mcp_config.mdx b/docs/python-sdk/fastmcp-utilities-mcp_config.mdx
deleted file mode 100644
index 403acd2be..000000000
--- a/docs/python-sdk/fastmcp-utilities-mcp_config.mdx
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: mcp_config
-sidebarTitle: mcp_config
----
-
-# `fastmcp.utilities.mcp_config`
-
-## Functions
-
-### `mcp_config_to_servers_and_transports`
-
-```python
-mcp_config_to_servers_and_transports(config: MCPConfig) -> list[tuple[str, FastMCP[Any], ClientTransport]]
-```
-
-
-A utility function to convert each entry of an MCP Config into a transport and server.
-
-
-### `mcp_server_type_to_servers_and_transports`
-
-```python
-mcp_server_type_to_servers_and_transports(name: str, mcp_server: MCPServerTypes) -> tuple[str, FastMCP[Any], ClientTransport]
-```
-
-
-A utility function to convert each entry of an MCP Config into a transport and server.
-