Reorganize docs around provider architecture (#2723)

This commit is contained in:
Jeremiah Lowin 2025-12-25 09:00:20 -05:00 committed by GitHub
commit 1d17334803
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1379 additions and 776 deletions

View file

@ -10,6 +10,31 @@ This guide provides migration instructions for breaking changes and major update
## v3.0.0
### Provider Architecture
FastMCP v3 introduces a unified provider architecture for sourcing components. All tools, resources, and prompts now flow through providers:
- **LocalProvider** stores decorator-registered components (`@mcp.tool`, etc.)
- **FastMCPProvider** wraps another FastMCP server for composition
- **ProxyProvider** connects to remote MCP servers
- **TransformingProvider** adds namespacing and renaming
See [Providers](/servers/providers/overview) for the complete documentation.
### Mount Namespace Parameter
The `prefix` parameter for `mount()` has been renamed to `namespace`:
<CodeGroup>
```python Before
main.mount(subserver, prefix="api")
```
```python After
main.mount(subserver, namespace="api")
```
</CodeGroup>
### Component Enable/Disable
The `enabled` field and `enable()`/`disable()` methods have been removed from component objects. Use server or provider methods instead:

View file

@ -20,10 +20,7 @@
"primary": "#2d00f7"
},
"contextual": {
"options": [
"copy",
"view"
]
"options": ["copy", "view"]
},
"description": "The fast, Pythonic way to build MCP servers and clients.",
"errors": {
@ -95,7 +92,7 @@
"pages": [
"servers/server",
{
"group": "Core Components",
"group": "Components",
"icon": "toolbox",
"pages": [
"servers/tools",
@ -104,20 +101,30 @@
]
},
{
"group": "Advanced Features",
"group": "Providers",
"icon": "layer-group",
"pages": [
"servers/providers/overview",
"servers/providers/local",
"servers/providers/mounting",
"servers/providers/namespacing",
"servers/providers/proxy",
"servers/providers/custom"
]
},
{
"group": "Features",
"icon": "stars",
"pages": [
"servers/composition",
"servers/tasks",
"servers/context",
"servers/elicitation",
"servers/icons",
"servers/logging",
"servers/middleware",
"servers/progress",
"servers/proxy",
"servers/sampling",
"servers/storage-backends",
"servers/tasks"
"servers/storage-backends"
]
},
{
@ -150,10 +157,7 @@
{
"group": "Essentials",
"icon": "cube",
"pages": [
"clients/client",
"clients/transports"
]
"pages": ["clients/client", "clients/transports"]
},
{
"group": "Core Operations",
@ -180,16 +184,18 @@
{
"group": "Authentication",
"icon": "user-shield",
"pages": [
"clients/auth/oauth",
"clients/auth/bearer"
]
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
}
]
},
{
"group": "Integrations",
"pages": [
{
"group": "Providers",
"icon": "globe",
"pages": ["integrations/fastapi", "integrations/openapi"]
},
{
"group": "Authentication",
"icon": "key",
@ -236,14 +242,6 @@
"integrations/gemini",
"integrations/openai"
]
},
{
"group": "API Integration",
"icon": "globe",
"pages": [
"integrations/fastapi",
"integrations/openapi"
]
}
]
},
@ -476,6 +474,7 @@
"group": "fastmcp.utilities",
"pages": [
"python-sdk/fastmcp-utilities-__init__",
"python-sdk/fastmcp-utilities-async_utils",
"python-sdk/fastmcp-utilities-auth",
"python-sdk/fastmcp-utilities-cli",
"python-sdk/fastmcp-utilities-components",
@ -543,12 +542,20 @@
},
"redirects": [
{
"destination": "/servers/proxy",
"destination": "/servers/providers/proxy",
"source": "/patterns/proxy"
},
{
"destination": "/servers/composition",
"destination": "/servers/providers/mounting",
"source": "/patterns/composition"
},
{
"destination": "/servers/providers/proxy",
"source": "/servers/proxy"
},
{
"destination": "/servers/providers/mounting",
"source": "/servers/composition"
}
],
"search": {

View file

@ -12,6 +12,10 @@ FastMCP provides two powerful ways to integrate with FastAPI applications:
1. **[Generate an MCP server FROM your FastAPI app](#generating-an-mcp-server)** - Convert existing API endpoints into MCP tools
2. **[Mount an MCP server INTO your FastAPI app](#mounting-an-mcp-server)** - Add MCP functionality to your web application
<Note>
When generating an MCP server from FastAPI, FastMCP uses OpenAPIProvider (v3.0.0+) under the hood to source tools from your FastAPI app's OpenAPI spec. See [Providers](/servers/providers/overview) to understand how FastMCP sources components.
</Note>
<Tip>
Generating MCP servers from OpenAPI is a great way to get started with FastMCP, but in practice LLMs achieve **significantly better performance** with well-designed and curated MCP servers than with auto-converted OpenAPI servers. This is especially true for complex APIs with many endpoints and parameters.

View file

@ -11,6 +11,10 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
FastMCP can automatically generate an MCP server from any OpenAPI specification, allowing AI models to interact with existing APIs through the MCP protocol. Instead of manually creating tools and resources, you provide an OpenAPI spec and FastMCP intelligently converts API endpoints into the appropriate MCP components.
<Note>
Under the hood, OpenAPI integration uses OpenAPIProvider (v3.0.0+) to source tools from the specification. See [Providers](/servers/providers/overview) to understand how FastMCP sources components.
</Note>
<Tip>
Generating MCP servers from OpenAPI is a great way to get started with FastMCP, but in practice LLMs achieve **significantly better performance** with well-designed and curated MCP servers than with auto-converted OpenAPI servers. This is especially true for complex APIs with many endpoints and parameters.

View file

@ -698,7 +698,7 @@ This pattern provides several benefits:
- **Scalable**: Easily add new tools by wrapping additional client methods
### Adapting Remote or Generated Tools
This is one of the most common reasons to use tool transformation. Tools from remote MCP servers (via a [proxy](/servers/proxy)) or generated from an [OpenAPI spec](/integrations/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.
This is one of the most common reasons to use tool transformation. Tools from remote MCP servers (via a [proxy](/servers/providers/proxy)) or generated from an [OpenAPI spec](/integrations/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.
### Chaining Transformations
You can chain transformations by using an already transformed tool as the parent for a new transformation. This lets you build up complex behaviors in layers, for example, first renaming arguments, and then adding validation logic to the renamed tool.

View file

@ -1,326 +0,0 @@
---
title: Server Composition
sidebarTitle: Composition
description: Combine multiple FastMCP servers into a single, larger application using mounting and importing.
icon: puzzle-piece
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.2.0" />
As your MCP applications grow, you might want to organize your tools, resources, and prompts into logical modules or reuse existing server components. FastMCP supports composition through two methods:
- **`import_server`**: For a one-time copy of components with prefixing (static composition).
- **`mount`**: For creating a live link where the main server delegates requests to the subserver (dynamic composition).
## Why Compose Servers?
- **Modularity**: Break down large applications into smaller, focused servers (e.g., a `WeatherServer`, a `DatabaseServer`, a `CalendarServer`).
- **Reusability**: Create common utility servers (e.g., a `TextProcessingServer`) and mount them wherever needed.
- **Teamwork**: Different teams can work on separate FastMCP servers that are later combined.
- **Organization**: Keep related functionality grouped together logically.
### Importing vs Mounting
The choice of importing or mounting depends on your use case and requirements.
| Feature | Importing | Mounting |
|---------|----------------|---------|
| **Method** | `FastMCP.import_server(server, prefix=None)` | `FastMCP.mount(server, prefix=None)` |
| **Composition Type** | One-time copy (static) | Live link (dynamic) |
| **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
| **Performance** | Fast - no runtime delegation | Slower - affected by slowest mounted server |
| **Prefix** | Optional - omit for original names | Optional - omit for original names |
| **Best For** | Bundling finalized components, performance-critical setups | Modular runtime composition |
### Proxy Servers
FastMCP supports [MCP proxying](/servers/proxy), which allows you to mirror a local or remote server in a local FastMCP instance. Proxies are fully compatible with both importing and mounting.
<VersionBadge version="2.4.0" />
You can also create proxies from configuration dictionaries that follow the MCPConfig schema, which is useful for quickly connecting to one or more remote servers. See the [Proxy Servers documentation](/servers/proxy#configuration-based-proxies) for details on configuration-based proxying. Note that MCPConfig follows an emerging standard and its format may evolve over time.
Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies. When prefixes are used, resource URIs are prefixed using path format (since 2.4.0): `resource://prefix/path/to/resource`.
## Importing (Static Composition)
The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). An optional `prefix` can be provided to avoid naming conflicts. If no prefix is provided, components are imported without modification. When multiple servers are imported with the same prefix (or no prefix), the most recently imported server's components take precedence.
```python
from fastmcp import FastMCP
import asyncio
# Define subservers
weather_mcp = FastMCP(name="WeatherService")
@weather_mcp.tool
def get_forecast(city: str) -> dict:
"""Get weather forecast."""
return {"city": city, "forecast": "Sunny"}
@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
"""List cities with weather support."""
return ["London", "Paris", "Tokyo"]
# Define main server
main_mcp = FastMCP(name="MainApp")
# Import subserver
async def setup():
await main_mcp.import_server(weather_mcp, prefix="weather")
# Result: main_mcp now contains prefixed components:
# - Tool: "weather_get_forecast"
# - Resource: "data://weather/cities/supported"
if __name__ == "__main__":
asyncio.run(setup())
main_mcp.run()
```
### How Importing Works
When you call `await main_mcp.import_server(subserver, prefix={whatever})`:
1. **Tools**: All tools from `subserver` are added to `main_mcp` with names prefixed using `{prefix}_`.
- `subserver.tool(name="my_tool")` becomes `main_mcp.tool(name="{prefix}_my_tool")`.
2. **Resources**: All resources are added with both URIs and names prefixed.
- URI: `subserver.resource(uri="data://info")` becomes `main_mcp.resource(uri="data://{prefix}/info")`.
- Name: `resource.name` becomes `"{prefix}_{resource.name}"`.
3. **Resource Templates**: Templates are prefixed similarly to resources.
- URI: `subserver.resource(uri="data://{id}")` becomes `main_mcp.resource(uri="data://{prefix}/{id}")`.
- Name: `template.name` becomes `"{prefix}_{template.name}"`.
4. **Prompts**: All prompts are added with names prefixed using `{prefix}_`.
- `subserver.prompt(name="my_prompt")` becomes `main_mcp.prompt(name="{prefix}_my_prompt")`.
Note that `import_server` performs a **one-time copy** of components. Changes made to the `subserver` *after* importing **will not** be reflected in `main_mcp`. The `subserver`'s `lifespan` context is also **not** executed by the main server.
<Tip>
The `prefix` parameter is optional. If omitted, components are imported without modification.
</Tip>
#### Importing Without Prefixes
<VersionBadge version="2.9.0" />
You can also import servers without specifying a prefix, which copies components using their original names:
```python
from fastmcp import FastMCP
import asyncio
# Define subservers
weather_mcp = FastMCP(name="WeatherService")
@weather_mcp.tool
def get_forecast(city: str) -> dict:
"""Get weather forecast."""
return {"city": city, "forecast": "Sunny"}
@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
"""List cities with weather support."""
return ["London", "Paris", "Tokyo"]
# Define main server
main_mcp = FastMCP(name="MainApp")
# Import subserver
async def setup():
# Import without prefix - components keep original names
await main_mcp.import_server(weather_mcp)
# Result: main_mcp now contains:
# - Tool: "get_forecast" (original name preserved)
# - Resource: "data://cities/supported" (original URI preserved)
if __name__ == "__main__":
asyncio.run(setup())
main_mcp.run()
```
#### Conflict Resolution
<VersionBadge version="2.9.0" />
When importing multiple servers with the same prefix, or no prefix, components from the **most recently imported** server take precedence.
## Mounting (Live Linking)
The `mount()` method creates a **live link** between the `main_mcp` server and the `subserver`. Instead of copying components, requests for components matching the optional `prefix` are **delegated** to the `subserver` at runtime. If no prefix is provided, the subserver's components are accessible without prefixing. When multiple servers are mounted with the same prefix (or no prefix), the most recently mounted server takes precedence for conflicting component names.
```python
import asyncio
from fastmcp import FastMCP, Client
# Define subserver
dynamic_mcp = FastMCP(name="DynamicService")
@dynamic_mcp.tool
def initial_tool():
"""Initial tool demonstration."""
return "Initial Tool Exists"
# Mount subserver (synchronous operation)
main_mcp = FastMCP(name="MainAppLive")
main_mcp.mount(dynamic_mcp, prefix="dynamic")
# Add a tool AFTER mounting - it will be accessible through main_mcp
@dynamic_mcp.tool
def added_later():
"""Tool added after mounting."""
return "Tool Added Dynamically!"
# Testing access to mounted tools
async def test_dynamic_mount():
tools = await main_mcp.get_tools()
print("Available tools:", list(tools.keys()))
# Shows: ['dynamic_initial_tool', 'dynamic_added_later']
async with Client(main_mcp) as client:
result = await client.call_tool("dynamic_added_later")
print("Result:", result.data)
# Shows: "Tool Added Dynamically!"
if __name__ == "__main__":
asyncio.run(test_dynamic_mount())
```
### How Mounting Works
When mounting is configured:
1. **Live Link**: The parent server establishes a connection to the mounted server.
2. **Dynamic Updates**: Changes to the mounted server are immediately reflected when accessed through the parent.
3. **Prefixed Access**: The parent server uses prefixes to route requests to the mounted server.
4. **Delegation**: Requests for components matching the prefix are delegated to the mounted server at runtime.
The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts. This includes prefixing both the URIs/keys and the names of resources and templates for better identification in multi-server configurations.
<Tip>
The `prefix` parameter is optional. If omitted, components are mounted without modification.
</Tip>
<Note>
When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application.
</Note>
#### Performance Considerations
Due to the "live link", operations like `list_tools()` on the parent server will be impacted by the speed of the slowest mounted server. In particular, HTTP-based mounted servers can introduce significant latency (300-400ms vs 1-2ms for local tools), and this slowdown affects the whole server, not just interactions with the HTTP-proxied tools. If performance is important, importing tools via [`import_server()`](#importing-static-composition) may be a more appropriate solution as it copies components once at startup rather than delegating requests at runtime.
#### Mounting Without Prefixes
<VersionBadge version="2.9.0" />
You can also mount servers without specifying a prefix, which makes components accessible without prefixing. This works identically to [importing without prefixes](#importing-without-prefixes), including [conflict resolution](#conflict-resolution).
### Direct vs. Proxy Mounting
<VersionBadge version="2.2.7" />
FastMCP supports two mounting modes:
1. **Direct Mounting** (default): The parent server directly accesses the mounted server's objects in memory.
- No client lifecycle events occur on the mounted server
- The mounted server's lifespan context is not executed
- Communication is handled through direct method calls
2. **Proxy Mounting**: The parent server treats the mounted server as a separate entity and communicates with it through a client interface.
- Full client lifecycle events occur on the mounted server
- The mounted server's lifespan is executed when a client connects
- Communication happens via an in-memory Client transport
```python
# Direct mounting (default when no custom lifespan)
main_mcp.mount(api_server, prefix="api")
# Proxy mounting (preserves full client lifecycle)
main_mcp.mount(api_server, prefix="api", as_proxy=True)
# Mounting without a prefix (components accessible without prefixing)
main_mcp.mount(api_server)
```
FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior with the `as_proxy` parameter.
#### Interaction with Proxy Servers
When using `FastMCP.as_proxy()` to create a proxy server, mounting that server will always use proxy mounting:
```python
# Create a proxy for a remote server
remote_proxy = FastMCP.as_proxy(Client("http://example.com/mcp"))
# Mount the proxy (always uses proxy mounting)
main_server.mount(remote_proxy, prefix="remote")
```
## Tag Filtering with Composition
<VersionBadge version="2.9.0" />
When using `include_tags` or `exclude_tags` on a parent server, these filters apply **recursively** to all components, including those from mounted or imported servers. This allows you to control which components are exposed at the parent level, regardless of how your application is composed.
```python
import asyncio
from fastmcp import FastMCP, Client
# Create a subserver with tools tagged for different environments
api_server = FastMCP(name="APIServer")
@api_server.tool(tags={"production"})
def prod_endpoint() -> str:
"""Production-ready endpoint."""
return "Production data"
@api_server.tool(tags={"development"})
def dev_endpoint() -> str:
"""Development-only endpoint."""
return "Debug data"
# Mount the subserver with production tag filtering at parent level
prod_app = FastMCP(name="ProductionApp", include_tags={"production"})
prod_app.mount(api_server, prefix="api")
# Test the filtering
async def test_filtering():
async with Client(prod_app) as client:
tools = await client.list_tools()
print("Available tools:", [t.name for t in tools])
# Shows: ['api_prod_endpoint']
# The 'api_dev_endpoint' is filtered out
# Calling the filtered tool raises an error
try:
await client.call_tool("api_dev_endpoint")
except Exception as e:
print(f"Filtered tool not accessible: {e}")
if __name__ == "__main__":
asyncio.run(test_filtering())
```
### How Recursive Filtering Works
Tag filters apply in the following order:
1. **Child Server Filters**: Each mounted/imported server first applies its own `include_tags`/`exclude_tags` to its components.
2. **Parent Server Filters**: The parent server then applies its own `include_tags`/`exclude_tags` to all components, including those from child servers.
This ensures that parent server tag policies act as a global policy for everything the parent server exposes, no matter how your application is composed.
<Note>
This filtering applies to both **listing** (e.g., `list_tools()`) and **execution** (e.g., `call_tool()`). Filtered components are neither visible nor executable through the parent server.
</Note>

View file

@ -436,7 +436,7 @@ This creates the following execution flow:
## Server Composition and Middleware
When using [Server Composition](/servers/composition) with `mount` or `import_server`, middleware behavior follows these rules:
When using [Mounting Servers](/servers/providers/mounting) with `mount` or `import_server`, middleware behavior follows these rules:
1. **Parent server middleware** runs for all requests, including those routed to mounted servers
2. **Mounted server middleware** only runs for requests handled by that specific server
@ -458,7 +458,7 @@ def child_tool() -> str:
return "from child"
# Mount the child server
parent.mount(child, prefix="child")
parent.mount(child, namespace="child")
```
When a client calls "child_tool", the request will flow through the parent's authentication middleware first, then route to the child server where it will go through the child's logging middleware.

View file

@ -94,7 +94,8 @@ def data_analysis_prompt(
</ParamField>
<ParamField body="enabled" type="bool" default="True">
A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
<Warning>Deprecated in v3.0.0. Use `mcp.enable()` / `mcp.disable()` at the server level instead.</Warning>
A boolean to enable or disable the prompt. See [Visibility Control](#visibility-control) for the recommended approach.
</ParamField>
<ParamField body="icons" type="list[Icon] | None">
@ -268,31 +269,36 @@ def data_analysis_prompt(
In this example, the client *must* provide `data_uri`. If `analysis_type` or `include_charts` are omitted, their default values will be used.
### Disabling Prompts
### Visibility Control
<VersionBadge version="2.8.0" />
<VersionBadge version="3.0.0" />
You can control the visibility and availability of prompts by enabling or disabling them. Disabled prompts will not appear in the list of available prompts, and attempting to call a disabled prompt will result in an "Unknown prompt" error.
By default, all prompts are enabled. You can disable a prompt upon creation using the `enabled` parameter in the decorator:
You can control which prompts are visible to clients using server-level visibility control. Disabled prompts don't appear in `list_prompts` and can't be called.
```python
@mcp.prompt(enabled=False)
def experimental_prompt():
"""This prompt is not ready for use."""
return "This is an experimental prompt."
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.prompt(tags={"public"})
def public_prompt(topic: str) -> str:
return f"Discuss: {topic}"
@mcp.prompt(tags={"internal"})
def internal_prompt() -> str:
return "Internal system prompt"
# Disable specific prompts by key
mcp.disable(keys=["prompt:internal_prompt"])
# Disable prompts by tag
mcp.disable(tags={"internal"})
# Or use allowlist mode - only show prompts with specific tags
mcp.enable(tags={"public"}, only=True)
```
You can also toggle a prompt's state programmatically after it has been created:
```python
@mcp.prompt
def seasonal_prompt(): return "Happy Holidays!"
# Disable and re-enable the prompt
seasonal_prompt.disable()
seasonal_prompt.enable()
```
See [Local Provider](/servers/providers/local#visibility-control) for the complete visibility control API including key formats and tag-based filtering.
### Async Prompts
@ -349,9 +355,9 @@ def example_prompt() -> str:
return "Hello!"
# These operations trigger notifications:
mcp.add_prompt(example_prompt) # Sends prompts/list_changed notification
example_prompt.disable() # Sends prompts/list_changed notification
example_prompt.enable() # Sends prompts/list_changed notification
mcp.add_prompt(example_prompt) # Sends prompts/list_changed notification
mcp.disable(keys=["prompt:example_prompt"]) # Sends prompts/list_changed notification
mcp.enable(keys=["prompt:example_prompt"]) # Sends prompts/list_changed notification
```
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.

View file

@ -0,0 +1,241 @@
---
title: Custom Providers
sidebarTitle: Custom
description: Build providers that source components from any data source
icon: code
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
Custom providers let you source components from anywhere - databases, APIs, configuration systems, or dynamic runtime logic. If you can write Python code to fetch or generate a component, you can wrap it in a provider.
## When to Build Custom
The built-in providers handle common cases: decorators (`LocalProvider`), composition (`FastMCPProvider`), and proxying (`ProxyProvider`). Build a custom provider when your components come from somewhere else:
- **Database-backed tools**: Admin users define tools in a database, and your server exposes them dynamically
- **API-backed resources**: Resources that fetch content from external services on demand
- **Configuration-driven components**: Components loaded from YAML/JSON config files at startup
- **Multi-tenant systems**: Different users see different tools based on their permissions
- **Plugin systems**: Third-party code registers components at runtime
## Providers vs Middleware
Both providers and [middleware](/servers/middleware) can influence what components a client sees, but they work at different levels.
**Providers** are objects that source components. They make it easy to reason about where tools, resources, and prompts come from - a database, another server, an API.
**Middleware** intercepts individual requests. It's well-suited for request-specific decisions like logging, rate limiting, or authentication.
You *could* use middleware to dynamically add tools based on request context. But it's often cleaner to have a provider source all possible tools, then use middleware or [visibility controls](/servers/providers/local#visibility-control) to filter what each request can see. This separation makes it easier to reason about how components are sourced and how they interact with other server machinery.
## The Provider Interface
A provider must implement `list_*` methods that return available components:
```python
from fastmcp.server.providers import Provider
from fastmcp.tools import Tool
from fastmcp.resources import Resource
from fastmcp.prompts import Prompt
class MyProvider(Provider):
async def list_tools(self) -> list[Tool]:
"""Return all tools this provider offers."""
return []
async def list_resources(self) -> list[Resource]:
"""Return all resources this provider offers."""
return []
async def list_prompts(self) -> list[Prompt]:
"""Return all prompts this provider offers."""
return []
```
You only need to implement the methods for component types you provide. The base class returns empty lists by default.
The `get_*` methods (`get_tool`, `get_resource`, `get_prompt`) have default implementations that search through the list results. Override them only if you can fetch individual components more efficiently than iterating the full list.
## What Providers Return
Providers return component objects that are ready to use. When a client calls a tool, FastMCP invokes the tool's function - your provider isn't involved in execution. This means the `Tool`, `Resource`, or `Prompt` you return must actually work.
The easiest way to create components is from functions:
```python
from fastmcp.tools import Tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
tool = Tool.from_function(add)
```
The function's type hints become the input schema, and the docstring becomes the description. You can override these:
```python
tool = Tool.from_function(
add,
name="calculator_add",
description="Add two integers together"
)
```
Similar `from_function` methods exist for `Resource` and `Prompt`.
## Registering Providers
Add providers when creating the server:
```python
mcp = FastMCP(
"MyServer",
providers=[
DatabaseProvider(db_url),
ConfigProvider(config_path),
]
)
```
Or add them after creation:
```python
mcp = FastMCP("MyServer")
mcp.add_provider(DatabaseProvider(db_url))
```
## A Simple Provider
Here's a minimal provider that serves tools from a dictionary:
```python
from fastmcp import FastMCP
from fastmcp.server.providers import Provider
from fastmcp.tools import Tool
class DictProvider(Provider):
def __init__(self, tools: dict[str, Callable]):
super().__init__()
self._tools = [
Tool.from_function(fn, name=name)
for name, fn in tools.items()
]
async def list_tools(self) -> list[Tool]:
return self._tools
```
Use it like this:
```python
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
mcp = FastMCP("Calculator", providers=[
DictProvider({"add": add, "multiply": multiply})
])
```
## Lifecycle Management
Providers often need to set up connections when the server starts and clean them up when it stops. Override the `lifespan` method:
```python
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
class DatabaseProvider(Provider):
def __init__(self, db_url: str):
super().__init__()
self.db_url = db_url
self.db = None
@asynccontextmanager
async def lifespan(self) -> AsyncIterator[None]:
self.db = await connect_database(self.db_url)
try:
yield
finally:
await self.db.close()
async def list_tools(self) -> list[Tool]:
rows = await self.db.fetch("SELECT * FROM tools")
return [self._make_tool(row) for row in rows]
```
FastMCP calls your provider's `lifespan` during server startup and shutdown. The connection is available to your methods while the server runs.
## Full Example: API-Backed Resources
Here's a complete provider that fetches resources from an external REST API:
```python
from contextlib import asynccontextmanager
from fastmcp.server.providers import Provider
from fastmcp.resources import Resource
import httpx
class ApiResourceProvider(Provider):
"""Provides resources backed by an external API."""
def __init__(self, base_url: str, api_key: str):
super().__init__()
self.base_url = base_url
self.api_key = api_key
self.client = None
@asynccontextmanager
async def lifespan(self) -> AsyncIterator[None]:
self.client = httpx.AsyncClient(
base_url=self.base_url,
headers={"Authorization": f"Bearer {self.api_key}"}
)
try:
yield
finally:
await self.client.aclose()
async def list_resources(self) -> list[Resource]:
response = await self.client.get("/resources")
response.raise_for_status()
return [
self._make_resource(item)
for item in response.json()["items"]
]
def _make_resource(self, data: dict) -> Resource:
resource_id = data["id"]
async def read_content() -> str:
response = await self.client.get(
f"/resources/{resource_id}/content"
)
return response.text
return Resource.from_function(
read_content,
uri=f"api://resources/{resource_id}",
name=data["name"],
description=data.get("description", ""),
mime_type=data.get("mime_type", "text/plain")
)
```
Register it like any other provider:
```python
from fastmcp import FastMCP
mcp = FastMCP("API Resources", providers=[
ApiResourceProvider("https://api.example.com", "my-api-key")
])
```

View file

@ -0,0 +1,238 @@
---
title: Local Provider
sidebarTitle: Local
description: The default provider for decorator-registered components
icon: house
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
`LocalProvider` stores components that you define directly on your server. When you use `@mcp.tool`, `@mcp.resource`, or `@mcp.prompt`, you're adding components to your server's `LocalProvider`.
## How It Works
Every FastMCP server has a `LocalProvider` as its first provider. Components registered via decorators or direct methods are stored here:
```python
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
# These are stored in the server's `LocalProvider`
@mcp.tool
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@mcp.resource("data://config")
def get_config() -> str:
"""Return configuration data."""
return '{"version": "1.0"}'
@mcp.prompt
def analyze(topic: str) -> str:
"""Create an analysis prompt."""
return f"Please analyze: {topic}"
```
The `LocalProvider` is always queried first when clients request components, ensuring that your directly-defined components take precedence over those from mounted or proxied servers.
## Component Registration
### Using Decorators
The most common way to register components:
```python
@mcp.tool
def my_tool(x: int) -> str:
return str(x)
@mcp.resource("data://info")
def my_resource() -> str:
return "info"
@mcp.prompt
def my_prompt(topic: str) -> str:
return f"Discuss: {topic}"
```
### Using Direct Methods
You can also add pre-built component objects:
```python
from fastmcp.tools import Tool
# Create a tool object
my_tool = Tool.from_function(some_function, name="custom_tool")
# Add it to the server
mcp.add_tool(my_tool)
mcp.add_resource(my_resource)
mcp.add_prompt(my_prompt)
```
### Removing Components
Remove components by name or URI:
```python
mcp.remove_tool("my_tool")
mcp.remove_resource("data://info")
mcp.remove_prompt("my_prompt")
```
## Duplicate Handling
When you try to add a component that already exists, the behavior depends on the `on_duplicate` setting:
| Mode | Behavior |
|------|----------|
| `"error"` (default) | Raise `ValueError` |
| `"warn"` | Log warning and replace |
| `"replace"` | Silently replace |
| `"ignore"` | Keep existing component |
Configure this when creating the server:
```python
mcp = FastMCP("MyServer", on_duplicate="warn")
```
## Visibility Control
<VersionBadge version="3.0.0" />
You can enable or disable components at runtime. Disabled components don't appear in listings and can't be called.
### Disabling Components
Use `disable()` to hide components:
```python
# Disable by key
mcp.disable(keys=["tool:admin_action"])
# Disable by tag
mcp.disable(tags={"internal"})
# Disable multiple
mcp.disable(keys=["tool:debug"], tags={"deprecated"})
```
### Enabling Components
Use `enable()` to re-enable disabled components:
```python
# Re-enable by key
mcp.enable(keys=["tool:admin_action"])
# Re-enable by tag
mcp.enable(tags={"internal"})
```
### Allowlist Mode
Use `only=True` to switch to allowlist mode, where **only** the specified components are visible:
```python
# Only show public tools
mcp.enable(tags={"public"}, only=True)
# Only show specific tools
mcp.enable(keys=["tool:safe_tool", "tool:read_only"], only=True)
```
### Component Keys
Keys follow the format `{type}:{identifier}`:
| Component Type | Key Format | Example |
|----------------|------------|---------|
| Tool | `tool:{name}` | `tool:greet` |
| Resource | `resource:{uri}` | `resource:data://config` |
| Template | `template:{uri}` | `template:data://{id}` |
| Prompt | `prompt:{name}` | `prompt:analyze` |
### Using Tags
Add tags when defining components to enable group-based visibility control:
```python
@mcp.tool(tags={"admin", "dangerous"})
def delete_all() -> str:
"""Delete everything."""
return "Deleted"
@mcp.tool(tags={"public", "safe"})
def get_status() -> str:
"""Get system status."""
return "OK"
# Hide all admin tools
mcp.disable(tags={"admin"})
# Or only show safe tools
mcp.enable(tags={"safe"}, only=True)
```
### Automatic Notifications
When you enable or disable components, FastMCP automatically sends `list_changed` notifications to connected clients. Clients that support notifications will refresh their component lists automatically.
## Standalone LocalProvider
You can create a LocalProvider independently and attach it to multiple servers:
```python
from fastmcp import FastMCP
from fastmcp.server.providers import LocalProvider
# Create a reusable provider
shared_tools = LocalProvider()
@shared_tools.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
@shared_tools.resource("data://version")
def get_version() -> str:
return "1.0.0"
# Attach to multiple servers
server1 = FastMCP("Server1", providers=[shared_tools])
server2 = FastMCP("Server2", providers=[shared_tools])
```
This is useful for:
- Sharing components across servers
- Testing components in isolation
- Building reusable component libraries
## Provider-Level Visibility
Visibility can be controlled at the provider level as well:
```python
from fastmcp.server.providers import LocalProvider
provider = LocalProvider()
@provider.tool(tags={"internal"})
def internal_tool() -> str:
return "Internal"
# Disable at provider level
provider.disable(tags={"internal"})
# This affects all servers using this provider
mcp = FastMCP("Server", providers=[provider])
```
<Note>
When a component is disabled at the provider level, it's hidden from all servers using that provider. Server-level visibility adds another layer of filtering on top of provider-level visibility.
</Note>

View file

@ -0,0 +1,240 @@
---
title: Mounting Servers
sidebarTitle: Mounting
description: Compose servers by mounting one inside another
icon: puzzle-piece
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.2.0" />
Mounting lets you combine multiple FastMCP servers into one. When you mount a server, all its components become available through the parent. Under the hood, FastMCP uses `FastMCPProvider` (v3.0.0+) to source components from the mounted server.
## Why Mount Servers
Large applications benefit from modular organization. Rather than defining all components in one massive file, create focused servers for specific domains and combine them:
- **Modularity**: Break down applications into smaller, focused servers
- **Reusability**: Create utility servers and mount them wherever needed
- **Teamwork**: Different teams can work on separate servers
- **Organization**: Keep related functionality grouped together
## Basic Mounting
Use `mount()` to add another server's components to your server:
```python
from fastmcp import FastMCP
# Create focused subservers
weather_server = FastMCP("Weather")
@weather_server.tool
def get_forecast(city: str) -> str:
"""Get weather forecast for a city."""
return f"Sunny in {city}"
@weather_server.resource("data://cities")
def list_cities() -> list[str]:
"""List supported cities."""
return ["London", "Paris", "Tokyo"]
# Create main server and mount the subserver
main = FastMCP("MainApp")
main.mount(weather_server)
# Now main has access to get_forecast and data://cities
```
## Namespacing
<VersionBadge version="3.0.0" />
When mounting multiple servers, use namespaces to avoid naming conflicts:
```python
weather = FastMCP("Weather")
calendar = FastMCP("Calendar")
@weather.tool
def get_data() -> str:
return "Weather data"
@calendar.tool
def get_data() -> str:
return "Calendar data"
main = FastMCP("Main")
main.mount(weather, namespace="weather")
main.mount(calendar, namespace="calendar")
# Tools are now:
# - weather_get_data
# - calendar_get_data
```
### How Namespacing Works
| Component Type | Without Namespace | With `namespace="api"` |
|----------------|-------------------|------------------------|
| Tool | `my_tool` | `api_my_tool` |
| Prompt | `my_prompt` | `api_my_prompt` |
| Resource | `data://info` | `data://api/info` |
| Template | `data://{id}` | `data://api/{id}` |
Namespacing uses [`TransformingProvider`](/servers/providers/namespacing) under the hood.
## Mounting vs Importing
FastMCP offers two ways to combine servers:
| Feature | `mount()` | `import_server()` |
|---------|-----------|-------------------|
| **Link Type** | Live (dynamic) | One-time copy (static) |
| **Updates** | Changes reflected immediately | Changes not reflected |
| **Performance** | Runtime delegation | Faster - no delegation |
| **Use Case** | Modular runtime composition | Bundling finalized components |
### Live Mounting
With `mount()`, changes to the subserver are immediately reflected:
```python
main = FastMCP("Main")
main.mount(dynamic_server, namespace="dynamic")
# Add a tool AFTER mounting - it's accessible through main
@dynamic_server.tool
def added_later() -> str:
return "Added after mounting!"
# This works because mount() creates a live link
```
### Static Importing
With `import_server()`, components are copied once at import time:
```python
main = FastMCP("Main")
async def setup():
await main.import_server(static_server, namespace="static")
# Changes to static_server after this point are NOT reflected in main
```
## Direct vs Proxy Mounting
<VersionBadge version="2.2.7" />
FastMCP supports two mounting modes:
### Direct Mounting (Default)
The parent server directly accesses the mounted server's objects in memory:
```python
main.mount(subserver, namespace="api")
```
- No client lifecycle events on mounted server
- Mounted server's lifespan is not executed
- Communication via direct method calls
### Proxy Mounting
The parent server treats the mounted server as a separate entity:
```python
main.mount(subserver, namespace="api", as_proxy=True)
```
- Full client lifecycle events on mounted server
- Mounted server's lifespan is executed
- Communication via in-memory Client transport
FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan. Override with `as_proxy=True` or `as_proxy=False`.
## Tag Filtering
<VersionBadge version="3.0.0" />
Parent server tag filters apply recursively to mounted servers:
```python
api_server = FastMCP("API")
@api_server.tool(tags={"production"})
def prod_endpoint() -> str:
return "Production data"
@api_server.tool(tags={"development"})
def dev_endpoint() -> str:
return "Debug data"
# Mount with production filter
prod_app = FastMCP("Production")
prod_app.mount(api_server, namespace="api")
prod_app.enable(tags={"production"}, only=True)
# Only prod_endpoint (namespaced as api_prod_endpoint) is visible
```
## Performance Considerations
When using live mounting, operations like `list_tools()` on the parent server are affected by the performance of all mounted servers. This is particularly noticeable with:
- HTTP-based mounted servers (300-400ms vs 1-2ms for local tools)
- Mounted servers with slow initialization
- Deep mounting hierarchies
If low latency is critical, consider:
- Using `import_server()` for static composition
- Implementing caching strategies
- Limiting mounting depth
## Custom Routes
<VersionBadge version="2.4.0" />
Custom HTTP routes defined with `@server.custom_route()` are also forwarded when mounting:
```python
subserver = FastMCP("Sub")
@subserver.custom_route("/health", methods=["GET"])
async def health_check():
return {"status": "ok"}
main = FastMCP("Main")
main.mount(subserver, namespace="sub")
# /health is now accessible through main's HTTP app
```
## Conflict Resolution
<VersionBadge version="3.0.0" />
When mounting multiple servers with the same namespace (or no namespace), the **most recently mounted** server takes precedence for conflicting component names:
```python
server_a = FastMCP("A")
server_b = FastMCP("B")
@server_a.tool
def shared_tool() -> str:
return "From A"
@server_b.tool
def shared_tool() -> str:
return "From B"
main = FastMCP("Main")
main.mount(server_a)
main.mount(server_b)
# shared_tool returns "From B" (most recently mounted)
```

View file

@ -0,0 +1,159 @@
---
title: Namespacing
sidebarTitle: Namespacing
description: Namespace and rename components with TransformingProvider
icon: wand-magic-sparkles
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
`TransformingProvider` wraps another provider and modifies its components, enabling namespacing and renaming. When you use `mount(namespace="...")`, `TransformingProvider` is used under the hood.
## Why Transform
When composing servers, you may encounter naming conflicts:
```python
weather = FastMCP("Weather")
calendar = FastMCP("Calendar")
@weather.tool
def get_data() -> str:
return "Weather"
@calendar.tool
def get_data() -> str: # Same name!
return "Calendar"
main = FastMCP("Main")
main.mount(weather)
main.mount(calendar) # Conflict: which get_data?
```
Transformations solve this by prefixing or renaming components.
## Namespacing
The most common transformation is namespacing, which prefixes all component names:
```python
main = FastMCP("Main")
main.mount(weather, namespace="weather")
main.mount(calendar, namespace="calendar")
# Tools are now:
# - weather_get_data
# - calendar_get_data
```
### Namespace Rules
| Component Type | Original | With `namespace="api"` |
|----------------|----------|------------------------|
| Tool | `my_tool` | `api_my_tool` |
| Prompt | `my_prompt` | `api_my_prompt` |
| Resource | `data://info` | `data://api/info` |
| Template | `data://{id}` | `data://api/{id}` |
Tools and prompts get an underscore prefix. Resources get a path-style prefix.
## Tool Renaming
For more control, rename specific tools explicitly:
```python
from fastmcp.server.providers import FastMCPProvider
provider = FastMCPProvider(weather_server).with_transforms(
namespace="weather",
tool_renames={"verbose_tool_name": "short"}
)
# "verbose_tool_name" → "short" (explicit rename, bypasses namespace)
# "other_tool" → "weather_other_tool" (namespace applied)
```
Explicit renames take precedence over namespacing.
## Direct Provider Usage
You can use `TransformingProvider` directly via `with_transforms()`:
```python
from fastmcp.server.providers import LocalProvider
provider = LocalProvider()
@provider.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
# Apply transformations
namespaced = provider.with_transforms(namespace="api")
# Or use shorthand
namespaced = provider.with_namespace("api")
# Register with server
mcp = FastMCP("Server", providers=[namespaced])
# Tool is now: api_greet
```
## Stacking Transformations
Transformations can be stacked - each `with_transforms()` creates a new wrapper:
```python
provider = (
LocalProvider()
.with_transforms(namespace="inner")
.with_transforms(namespace="outer")
)
# Tool "foo" becomes:
# 1. inner_foo (first transform)
# 2. outer_inner_foo (second transform)
```
You can also combine namespacing with renaming at different levels:
```python
provider = (
LocalProvider()
.with_transforms(namespace="api")
.with_transforms(tool_renames={"api_verbose": "short"})
)
# "verbose" → "api_verbose" (namespace) → "short" (rename)
```
## Use Cases
### Avoiding Conflicts
```python
# Two servers with same tool names
main.mount(server_a, namespace="a")
main.mount(server_b, namespace="b")
```
### API Versioning
```python
# Mount different versions
main.mount(v1_server, namespace="v1")
main.mount(v2_server, namespace="v2")
```
### Aliasing Long Names
```python
provider = FastMCPProvider(server).with_transforms(
tool_renames={
"get_user_authentication_status": "auth_status",
"retrieve_configuration_settings": "get_config"
}
)
```

View file

@ -0,0 +1,67 @@
---
title: Providers
sidebarTitle: Overview
description: How FastMCP sources tools, resources, and prompts
icon: layer-group
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
Every FastMCP server has one or more component providers. A provider is a source of tools, resources, and prompts - it's what makes components available to clients.
## What Is a Provider?
When a client connects to your server and asks "what tools do you have?", FastMCP asks each provider that question and combines the results. When a client calls a specific tool, FastMCP finds which provider has it and delegates the call.
You're already using providers. When you write `@mcp.tool`, you're adding a tool to your server's `LocalProvider` - the default provider that stores components you define directly in code. You just don't have to think about it for simple servers.
Providers become important when your components come from multiple sources: another FastMCP server to include, a remote MCP server to proxy, or a database where tools are defined dynamically. Each source gets its own provider, and FastMCP queries them all seamlessly.
## Why Providers?
The provider abstraction solves a common problem: as servers grow, you need to organize components across multiple sources without tangling everything together.
**Composition**: Break a large server into focused modules. A "weather" server and a "calendar" server can each be developed independently, then mounted into a main server. Each mounted server becomes a `FastMCPProvider`.
**Proxying**: Expose a remote MCP server through your local server. Maybe you're bridging transports (remote HTTP to local stdio) or aggregating multiple backends. Remote connections become `ProxyProvider` instances.
**Dynamic sources**: Load tools from a database, generate them from an OpenAPI spec, or create them based on user permissions. Custom providers let components come from anywhere.
## Built-in Providers
FastMCP includes providers for common patterns:
| Provider | What it does | How you use it |
|----------|--------------|----------------|
| `LocalProvider` | Stores components you define in code | `@mcp.tool`, `mcp.add_tool()` |
| `FastMCPProvider` | Wraps another FastMCP server | `mcp.mount(server)` |
| `ProxyProvider` | Connects to remote MCP servers | `FastMCP.as_proxy(client)` |
| `TransformingProvider` | Adds prefixes to avoid name collisions | `mcp.mount(server, namespace="api")` |
Most users only interact with `LocalProvider` (through decorators) and occasionally mount or proxy other servers. The provider abstraction stays invisible until you need it.
## Provider Order
When a client requests a tool, FastMCP queries providers in registration order. The first provider that has the tool handles the request.
`LocalProvider` is always first, so your decorator-defined tools take precedence. Additional providers are queried in the order you added them. This means if two providers have a tool with the same name, the first one wins.
## When to Care About Providers
**You can ignore providers entirely** if you're building a simple server with decorators. Just use `@mcp.tool`, `@mcp.resource`, and `@mcp.prompt` - FastMCP handles the rest.
**Learn about providers when** you want to:
- [Mount another server](/servers/providers/mounting) into yours
- [Proxy a remote server](/servers/providers/proxy) through yours
- [Control visibility](/servers/providers/local#visibility-control) of components
- [Build dynamic sources](/servers/providers/custom) like database-backed tools
## Next Steps
- [Local](/servers/providers/local) - How decorators and visibility control work
- [Mounting](/servers/providers/mounting) - Compose servers together
- [Proxying](/servers/providers/proxy) - Connect to remote servers
- [Namespacing](/servers/providers/namespacing) - Avoid name collisions
- [Custom](/servers/providers/custom) - Build your own providers

View file

@ -0,0 +1,275 @@
---
title: Remote Proxies
sidebarTitle: Proxying
description: Expose remote MCP servers through your local server
icon: arrows-retweet
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
Proxying lets you expose a remote MCP server's tools, resources, and prompts through your local server. Under the hood, FastMCP uses `ProxyProvider` (v3.0.0+) to source components from a client connection.
## Why Proxy Servers
Proxying lets you:
- **Bridge transports**: Expose a remote SSE server via local stdio
- **Aggregate servers**: Combine multiple remote servers into one
- **Add security**: Act as a controlled gateway to backend servers
- **Simplify access**: Single endpoint even if backends change
```mermaid
sequenceDiagram
participant Client as Your Client
participant Proxy as FastMCP Proxy
participant Backend as Remote Server
Client->>Proxy: MCP Request (stdio)
Proxy->>Backend: MCP Request (HTTP)
Backend-->>Proxy: MCP Response
Proxy-->>Client: MCP Response
```
## Quick Start
<VersionBadge version="2.10.3" />
Create a proxy using `FastMCP.as_proxy()`:
```python
from fastmcp import FastMCP
from fastmcp.server.proxy import ProxyClient
# Create a proxy to a remote server
proxy = FastMCP.as_proxy(
ProxyClient("http://example.com/mcp"),
name="MyProxy"
)
if __name__ == "__main__":
proxy.run()
```
This gives you:
- Safe concurrent request handling
- Automatic forwarding of MCP features (sampling, elicitation, etc.)
- Session isolation to prevent context mixing
## Transport Bridging
A common use case is bridging transports - making a remote server available locally:
```python
from fastmcp import FastMCP
from fastmcp.server.proxy import ProxyClient
# Bridge remote HTTP to local stdio
remote_proxy = FastMCP.as_proxy(
ProxyClient("http://example.com/mcp/sse"),
name="Remote-to-Local"
)
# Run locally via stdio for Claude Desktop
if __name__ == "__main__":
remote_proxy.run() # Defaults to stdio
```
Or expose a local server via HTTP:
```python
# Bridge local server to HTTP
local_proxy = FastMCP.as_proxy(
ProxyClient("local_server.py"),
name="Local-to-HTTP"
)
if __name__ == "__main__":
local_proxy.run(transport="http", host="0.0.0.0", port=8080)
```
## Session Isolation
<VersionBadge version="2.10.3" />
ProxyClient provides session isolation - each request gets its own isolated backend session:
```python
from fastmcp.server.proxy import ProxyClient
# Each request creates a fresh backend session (recommended)
proxy = FastMCP.as_proxy(ProxyClient("backend_server.py"))
# Multiple clients can use this proxy simultaneously:
# - Client A calls a tool → gets isolated session
# - Client B calls a tool → gets different session
# - No context mixing
```
### Shared Sessions
If you pass an already-connected client, the proxy reuses that session:
```python
from fastmcp import Client
async with Client("backend_server.py") as connected_client:
# This proxy reuses the connected session
proxy = FastMCP.as_proxy(connected_client)
# ⚠️ Warning: All requests share the same session
```
<Warning>
Shared sessions may cause context mixing in concurrent scenarios. Use only in single-threaded situations or with explicit synchronization.
</Warning>
## MCP Feature Forwarding
<VersionBadge version="2.10.3" />
ProxyClient automatically forwards MCP protocol features:
| Feature | Description |
|---------|-------------|
| Roots | Filesystem root access requests |
| Sampling | LLM completion requests |
| Elicitation | User input requests |
| Logging | Log messages from backend |
| Progress | Progress notifications |
```python
from fastmcp.server.proxy import ProxyClient
# All features forwarded automatically
backend = ProxyClient("advanced_backend.py")
proxy = FastMCP.as_proxy(backend)
# When the backend:
# - Requests LLM sampling → forwarded to your client
# - Logs messages → appear in your client
# - Reports progress → shown in your client
```
### Disabling Features
Selectively disable forwarding:
```python
backend = ProxyClient(
"backend_server.py",
sampling_handler=None, # Disable LLM sampling
log_handler=None # Disable log forwarding
)
```
## Configuration-Based Proxies
<VersionBadge version="2.4.0" />
Create proxies from configuration dictionaries:
```python
from fastmcp import FastMCP
config = {
"mcpServers": {
"default": {
"url": "https://example.com/mcp",
"transport": "http"
}
}
}
proxy = FastMCP.as_proxy(config, name="Config-Based Proxy")
```
### Multi-Server Proxies
Combine multiple servers with automatic namespacing:
```python
config = {
"mcpServers": {
"weather": {
"url": "https://weather-api.example.com/mcp",
"transport": "http"
},
"calendar": {
"url": "https://calendar-api.example.com/mcp",
"transport": "http"
}
}
}
# Creates unified proxy with prefixed components:
# - weather_get_forecast
# - calendar_add_event
composite = FastMCP.as_proxy(config, name="Composite")
```
## Component Prefixing
Proxied components follow standard prefixing rules:
| Component Type | Pattern |
|----------------|---------|
| Tools | `{prefix}_{tool_name}` |
| Prompts | `{prefix}_{prompt_name}` |
| Resources | `protocol://{prefix}/path` |
| Templates | `protocol://{prefix}/...` |
## Mirrored Components
<VersionBadge version="2.10.5" />
Components from a proxy server are "mirrored" - they reflect the remote server's state and cannot be modified directly.
To modify a proxied component (like disabling it), create a local copy:
```python
proxy = FastMCP.as_proxy("backend_server.py")
# Get mirrored tool
mirrored_tool = await proxy.get_tool("useful_tool")
# Create modifiable local copy
local_tool = mirrored_tool.copy()
# Add to your own server
my_server = FastMCP("MyServer")
my_server.add_tool(local_tool)
# Now you can control visibility
my_server.disable(keys=[local_tool.key])
```
## Performance Considerations
Proxying introduces network latency:
| Operation | Local | Proxied (HTTP) |
|-----------|-------|----------------|
| `list_tools()` | 1-2ms | 300-400ms |
| `call_tool()` | 1-2ms | 200-500ms |
When mounting proxy servers, this latency affects all operations on the parent server.
For low-latency requirements, consider using [`import_server()`](/servers/providers/mounting#static-importing) to copy tools at startup.
## Advanced: FastMCPProxy Class
For explicit session control, use `FastMCPProxy` directly:
```python
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
# Custom session factory
def create_client():
return ProxyClient("backend_server.py")
proxy = FastMCPProxy(client_factory=create_client)
```
This gives you full control over session creation and reuse strategies.

View file

@ -1,349 +0,0 @@
---
title: Proxy Servers
sidebarTitle: Proxy Servers
description: Use FastMCP to act as an intermediary or change transport for other MCP servers.
icon: arrows-retweet
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
FastMCP provides a powerful proxying capability that allows one FastMCP server instance to act as a frontend for another MCP server (which could be remote, running on a different transport, or even another FastMCP instance). This is achieved using the `FastMCP.as_proxy()` class method.
## What is Proxying?
Proxying means setting up a FastMCP server that doesn't implement its own tools or resources directly. Instead, when it receives a request (like `tools/call` or `resources/read`), it forwards that request to a *backend* MCP server, receives the response, and then relays that response back to the original client.
```mermaid
sequenceDiagram
participant ClientApp as Your Client (e.g., Claude Desktop)
participant FastMCPProxy as FastMCP Proxy Server
participant BackendServer as Backend MCP Server (e.g., remote SSE)
ClientApp->>FastMCPProxy: MCP Request (e.g. stdio)
Note over FastMCPProxy, BackendServer: Proxy forwards the request
FastMCPProxy->>BackendServer: MCP Request (e.g. sse)
BackendServer-->>FastMCPProxy: MCP Response (e.g. sse)
Note over ClientApp, FastMCPProxy: Proxy relays the response
FastMCPProxy-->>ClientApp: MCP Response (e.g. stdio)
```
### Key Benefits
<VersionBadge version="2.10.3" />
- **Session Isolation**: Each request gets its own isolated session, ensuring safe concurrent operations
- **Transport Bridging**: Expose servers running on one transport via a different transport
- **Advanced MCP Features**: Automatic forwarding of sampling, elicitation, logging, and progress
- **Security**: Acts as a controlled gateway to backend servers
- **Simplicity**: Single endpoint even if backend location or transport changes
### Performance Considerations
When using proxy servers, especially those connecting to HTTP-based backend servers, be aware that latency can be significant. Operations like `list_tools()` may take hundreds of milliseconds compared to 1-2ms for local tools. When mounting proxy servers, this latency affects all operations on the parent server, not just interactions with the proxied tools.
If low latency is a requirement for your use-case, consider using [`import_server()`](/servers/composition#importing-static-composition) to copy tools at startup rather than proxying them at runtime.
## Quick Start
<VersionBadge version="2.10.3" />
The recommended way to create a proxy is using `ProxyClient`, which provides full MCP feature support with automatic session isolation:
```python
from fastmcp import FastMCP
from fastmcp.server.proxy import ProxyClient
# Create a proxy with full MCP feature support
proxy = FastMCP.as_proxy(
ProxyClient("backend_server.py"),
name="MyProxy"
)
# Run the proxy (e.g., via stdio for Claude Desktop)
if __name__ == "__main__":
proxy.run()
```
This single setup gives you:
- Safe concurrent request handling
- Automatic forwarding of advanced MCP features (sampling, elicitation, etc.)
- Session isolation to prevent context mixing
- Full compatibility with all MCP clients
You can also pass a FastMCP [client transport](/clients/transports) (or parameter that can be inferred to a transport) to `as_proxy()`. This will automatically create a `ProxyClient` instance for you.
Finally, you can pass a regular FastMCP `Client` instance to `as_proxy()`. This will work for many use cases, but may break if advanced MCP features like sampling or elicitation are invoked by the server.
## Session Isolation & Concurrency
<VersionBadge version="2.10.3" />
FastMCP proxies provide session isolation to ensure safe concurrent operations. The session strategy depends on how the proxy is configured:
### Fresh Sessions
When you pass a disconnected client (which is the normal case), each request gets its own isolated backend session:
```python
from fastmcp.server.proxy import ProxyClient
# Each request creates a fresh backend session (recommended)
proxy = FastMCP.as_proxy(ProxyClient("backend_server.py"))
# Multiple clients can use this proxy simultaneously without interference:
# - Client A calls a tool -> gets isolated backend session
# - Client B calls a tool -> gets different isolated backend session
# - No context mixing between requests
```
### Session Reuse with Connected Clients
When you pass an already-connected client, the proxy will reuse that session for all requests:
```python
from fastmcp import Client
# Create and connect a client
async with Client("backend_server.py") as connected_client:
# This proxy will reuse the connected session for all requests
proxy = FastMCP.as_proxy(connected_client)
# ⚠️ Warning: All requests share the same backend session
# This may cause context mixing in concurrent scenarios
```
**Important**: Using shared sessions with concurrent requests from multiple clients may lead to context mixing and race conditions. This approach should only be used in single-threaded scenarios or when you have explicit synchronization.
## Transport Bridging
A common use case is bridging transports - exposing a server running on one transport via a different transport. For example, making a remote SSE server available locally via stdio:
```python
from fastmcp import FastMCP
from fastmcp.server.proxy import ProxyClient
# Bridge remote SSE server to local stdio
remote_proxy = FastMCP.as_proxy(
ProxyClient("http://example.com/mcp/sse"),
name="Remote-to-Local Bridge"
)
# Run locally via stdio for Claude Desktop
if __name__ == "__main__":
remote_proxy.run() # Defaults to stdio transport
```
Or expose a local server via HTTP for remote access:
```python
# Bridge local server to HTTP
local_proxy = FastMCP.as_proxy(
ProxyClient("local_server.py"),
name="Local-to-HTTP Bridge"
)
# Run via HTTP for remote clients
if __name__ == "__main__":
local_proxy.run(transport="http", host="0.0.0.0", port=8080)
```
## Advanced MCP Features
<VersionBadge version="2.10.3" />
`ProxyClient` automatically forwards advanced MCP protocol features between the backend server and clients connected to the proxy, ensuring full MCP compatibility.
### Supported Features
- **Roots**: Forwards filesystem root access requests to the client
- **Sampling**: Forwards LLM completion requests from backend to client
- **Elicitation**: Forwards user input requests to the client
- **Logging**: Forwards log messages from backend through to client
- **Progress**: Forwards progress notifications during long operations
```python
from fastmcp.server.proxy import ProxyClient
# ProxyClient automatically handles all these features
backend = ProxyClient("advanced_backend.py")
proxy = FastMCP.as_proxy(backend)
# When the backend server:
# - Requests LLM sampling -> forwarded to your client
# - Logs messages -> appear in your client
# - Reports progress -> shown in your client
# - Needs user input -> prompts your client
```
### Customizing Feature Support
You can selectively disable forwarding by passing `None` for specific handlers:
```python
# Disable sampling but keep other features
backend = ProxyClient(
"backend_server.py",
sampling_handler=None, # Disable LLM sampling forwarding
log_handler=None # Disable log forwarding
)
```
When you use a transport string directly with `FastMCP.as_proxy()`, it automatically creates a `ProxyClient` internally to ensure full feature support.
## Configuration-Based Proxies
<VersionBadge version="2.4.0" />
You can create a proxy directly from a configuration dictionary that follows the MCPConfig schema. This is useful for quickly setting up proxies to remote servers without manually configuring each connection detail.
```python
from fastmcp import FastMCP
# Create a proxy directly from a config dictionary
config = {
"mcpServers": {
"default": { # For single server configs, 'default' is commonly used
"url": "https://example.com/mcp",
"transport": "http"
}
}
}
# Create a proxy to the configured server (auto-creates ProxyClient)
proxy = FastMCP.as_proxy(config, name="Config-Based Proxy")
# Run the proxy with stdio transport for local access
if __name__ == "__main__":
proxy.run()
```
<Note>
The MCPConfig format follows an emerging standard for MCP server configuration and may evolve as the specification matures. While FastMCP aims to maintain compatibility with future versions, be aware that field names or structure might change.
</Note>
### Multi-Server Configurations
You can create a proxy to multiple servers by specifying multiple entries in the config. They are automatically mounted with their config names as prefixes:
```python
# Multi-server configuration
config = {
"mcpServers": {
"weather": {
"url": "https://weather-api.example.com/mcp",
"transport": "http"
},
"calendar": {
"url": "https://calendar-api.example.com/mcp",
"transport": "http"
}
}
}
# Create a unified proxy to multiple servers
composite_proxy = FastMCP.as_proxy(config, name="Composite Proxy")
# Tools, resources, prompts, and templates are accessible with prefixes:
# - Tools: weather_get_forecast, calendar_add_event
# - Prompts: weather_daily_summary, calendar_quick_add
# - Resources: weather://weather/icons/sunny, calendar://calendar/events/today
# - Templates: weather://weather/locations/{id}, calendar://calendar/events/{date}
```
## Component Prefixing
When proxying one or more servers, component names are prefixed the same way as with mounting and importing:
- Tools: `{prefix}_{tool_name}`
- Prompts: `{prefix}_{prompt_name}`
- Resources: `protocol://{prefix}/path/to/resource` (default path format)
- Resource templates: `protocol://{prefix}/...` and template names are also prefixed
These rules apply uniformly whether you:
- Mount a proxy on another server
- Create a multi-server proxy from an `MCPConfig`
- Use `FastMCP.as_proxy()` directly
## Mirrored Components
<VersionBadge version="2.10.5" />
When you access tools, resources, or prompts from a proxy server, they are "mirrored" from the remote server. Mirrored components cannot be modified directly since they reflect the state of the remote server. For example, you can not simply "disable" a mirrored component.
However, you can create a copy of a mirrored component and store it as a new locally-defined component. Local components always take precedence over mirrored ones because the proxy server will check its own registry before it attempts to engage the remote server.
Therefore, to enable or disable a proxy tool, resource, or prompt, you should first create a local copy and add it to your own server. Here's an example of how to do that for a tool:
```python
# Create your own server
my_server = FastMCP("MyServer")
# Get a proxy server
proxy = FastMCP.as_proxy("backend_server.py")
# Get mirrored components from proxy
mirrored_tool = await proxy.get_tool("useful_tool")
# Create a local copy that you can modify
local_tool = mirrored_tool.copy()
# Add the local copy to your server
my_server.add_tool(local_tool)
# Now you can disable YOUR copy
local_tool.disable()
```
## `FastMCPProxy` Class
Internally, `FastMCP.as_proxy()` uses the `FastMCPProxy` class. You generally don't need to interact with this class directly, but it's available if needed for advanced scenarios.
### Direct Usage
```python
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
# Provide a client factory for explicit session control
def create_client():
return ProxyClient("backend_server.py")
proxy = FastMCPProxy(client_factory=create_client)
```
### Parameters
- **`client_factory`**: A callable that returns a `Client` instance when called. This gives you full control over session creation and reuse strategies.
### Explicit Session Management
`FastMCPProxy` requires explicit session management - no automatic detection is performed. You must choose your session strategy:
```python
# Share session across all requests (be careful with concurrency)
shared_client = ProxyClient("backend_server.py")
def shared_session_factory():
return shared_client
proxy = FastMCPProxy(client_factory=shared_session_factory)
# Create fresh sessions per request (recommended)
def fresh_session_factory():
return ProxyClient("backend_server.py")
proxy = FastMCPProxy(client_factory=fresh_session_factory)
```
For automatic session strategy selection, use the convenience method `FastMCP.as_proxy()` instead.
```python
# Custom factory with specific configuration
def custom_client_factory():
client = ProxyClient("backend_server.py")
# Add any custom configuration here
return client
proxy = FastMCPProxy(client_factory=custom_client_factory)
```

View file

@ -103,7 +103,8 @@ def get_application_status() -> dict:
</ParamField>
<ParamField body="enabled" type="bool" default="True">
A boolean to enable or disable the resource. See [Disabling Resources](#disabling-resources) for more information
<Warning>Deprecated in v3.0.0. Use `mcp.enable()` / `mcp.disable()` at the server level instead.</Warning>
A boolean to enable or disable the resource. See [Visibility Control](#visibility-control) for the recommended approach.
</ParamField>
<ParamField body="icons" type="list[Icon] | None">
@ -191,31 +192,34 @@ The `meta` field in `ResourceContent` is for runtime metadata specific to this r
You can still return plain `str` or `bytes` from your resource functions—`ResourceContent` is opt-in for when you need to include metadata.
### Disabling Resources
### Visibility Control
<VersionBadge version="2.8.0" />
<VersionBadge version="3.0.0" />
You can control the visibility and availability of resources and templates by enabling or disabling them. Disabled resources will not appear in the list of available resources or templates, and attempting to read a disabled resource will result in an "Unknown resource" error.
By default, all resources are enabled. You can disable a resource upon creation using the `enabled` parameter in the decorator:
You can control which resources are visible to clients using server-level visibility control. Disabled resources don't appear in `list_resources` and can't be read.
```python
@mcp.resource("data://secret", enabled=False)
def get_secret_data():
"""This resource is currently disabled."""
return "Secret data"
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.resource("data://public", tags={"public"})
def get_public(): return "public"
@mcp.resource("data://secret", tags={"internal"})
def get_secret(): return "secret"
# Disable specific resources by key
mcp.disable(keys=["resource:data://secret"])
# Disable resources by tag
mcp.disable(tags={"internal"})
# Or use allowlist mode - only show resources with specific tags
mcp.enable(tags={"public"}, only=True)
```
You can also toggle a resource's state programmatically after it has been created:
```python
@mcp.resource("data://config")
def get_config(): return {"version": 1}
# Disable and re-enable the resource
get_config.disable()
get_config.enable()
```
See [Local Provider](/servers/providers/local#visibility-control) for the complete visibility control API including key formats and tag-based filtering.
### Accessing MCP Context
@ -367,9 +371,9 @@ def example_resource() -> str:
return "Hello!"
# These operations trigger notifications:
mcp.add_resource(example_resource) # Sends resources/list_changed notification
example_resource.disable() # Sends resources/list_changed notification
example_resource.enable() # Sends resources/list_changed notification
mcp.add_resource(example_resource) # Sends resources/list_changed notification
mcp.disable(keys=["resource:data://example"]) # Sends resources/list_changed notification
mcp.enable(keys=["resource:data://example"]) # Sends resources/list_changed notification
```
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.

View file

@ -266,7 +266,7 @@ For more complex web applications, consider [mounting your MCP server into a Fas
FastMCP supports composing multiple servers together using `import_server` (static copy) and `mount` (live link). This allows you to organize large applications into modular components or reuse existing servers.
See the [Server Composition](/servers/composition) guide for full details, best practices, and examples.
See the [Mounting Servers](/servers/providers/mounting) guide for full details, best practices, and examples.
```python
# Example: Importing a subserver
@ -277,11 +277,11 @@ main = FastMCP(name="Main")
sub = FastMCP(name="Sub")
@sub.tool
def hello():
def hello():
return "hi"
# Mount directly
main.mount(sub, prefix="sub")
main.mount(sub, namespace="sub")
```
## Proxying Servers
@ -292,7 +292,7 @@ FastMCP can act as a proxy for any MCP server (local or remote) using `FastMCP.a
Proxies automatically handle concurrent operations safely by creating fresh sessions for each request when using disconnected clients.
See the [Proxying Servers](/servers/proxy) guide for details and advanced usage.
See the [Remote Proxies](/servers/providers/proxy) guide for details and advanced usage.
```python
from fastmcp import FastMCP, Client

View file

@ -78,7 +78,8 @@ def search_products_implementation(query: str, category: str | None = None) -> l
</ParamField>
<ParamField body="enabled" type="bool" default="True">
A boolean to enable or disable the tool. See [Disabling Tools](#disabling-tools) for more information
<Warning>Deprecated in v3.0.0. Use `mcp.enable()` / `mcp.disable()` at the server level instead.</Warning>
A boolean to enable or disable the tool. See [Visibility Control](#visibility-control) for the recommended approach.
</ParamField>
<ParamField body="icons" type="list[Icon] | None">
@ -750,32 +751,39 @@ def divide(a: float, b: float) -> float:
When `mask_error_details=True`, only error messages from `ToolError` will include details, other exceptions will be converted to a generic message.
## Disabling Tools
## Visibility Control
<VersionBadge version="2.8.0" />
<VersionBadge version="3.0.0" />
You can control the visibility and availability of tools by enabling or disabling them. This is useful for feature flagging, maintenance, or dynamically changing the toolset available to a client. Disabled tools will not appear in the list of available tools returned by `list_tools`, and attempting to call a disabled tool will result in an "Unknown tool" error, just as if the tool did not exist.
By default, all tools are enabled. You can disable a tool upon creation using the `enabled` parameter in the decorator:
You can control which tools are visible to clients using server-level visibility control. Disabled tools don't appear in `list_tools` and can't be called.
```python
@mcp.tool(enabled=False)
def maintenance_tool():
"""This tool is currently under maintenance."""
return "This tool is disabled."
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool(tags={"admin"})
def admin_action() -> str:
"""Admin-only action."""
return "Done"
@mcp.tool(tags={"public"})
def public_action() -> str:
"""Public action."""
return "Done"
# Disable specific tools by key
mcp.disable(keys=["tool:admin_action"])
# Disable tools by tag
mcp.disable(tags={"admin"})
# Or use allowlist mode - only show tools with specific tags
mcp.enable(tags={"public"}, only=True)
```
You can also toggle a tool's state programmatically after it has been created:
See [Local Provider](/servers/providers/local#visibility-control) for the complete visibility control API including key formats and tag-based filtering.
```python
@mcp.tool
def dynamic_tool():
return "I am a dynamic tool."
# Disable and re-enable the tool
dynamic_tool.disable()
dynamic_tool.enable()
```
## MCP Annotations
<VersionBadge version="2.2.7" />
@ -878,10 +886,10 @@ def example_tool() -> str:
return "Hello!"
# These operations trigger notifications:
mcp.add_tool(example_tool) # Sends tools/list_changed notification
example_tool.disable() # Sends tools/list_changed notification
example_tool.enable() # Sends tools/list_changed notification
mcp.remove_tool("example_tool") # Sends tools/list_changed notification
mcp.add_tool(example_tool) # Sends tools/list_changed notification
mcp.disable(keys=["tool:example_tool"]) # Sends tools/list_changed notification
mcp.enable(keys=["tool:example_tool"]) # Sends tools/list_changed notification
mcp.remove_tool("example_tool") # Sends tools/list_changed notification
```
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.

View file

@ -1,6 +1,6 @@
export const VersionBadge = ({ version }) => {
return (
<Badge stroke size='lg' icon='gift' iconType='regular' className="version-badge">
<Badge stroke size="lg" icon='gift' iconType='regular' className="version-badge">
New in version <code>{version}</code>
</Badge>
);