fastmcp/docs/servers/providers/mounting.mdx
Jeremiah Lowin 07d89c4038
Add transform system for modifying components in provider chains (#2836)
* Consolidate tool transformation logic into TransformingProvider

Tool transformations were previously scattered across LocalProvider,
ProxyProvider, and MCPConfig. This consolidates all transformation
logic into TransformingProvider via with_transforms(tool_transforms={...}).

- Add tool_transforms parameter to TransformingProvider
- Add tool_transforms to Provider.with_transforms()
- Remove transformation storage from LocalProvider and ProxyProvider
- Remove add_tool_transformation() and remove_tool_transformation() from FastMCP
- Add tool_transforms parameter to factory methods (from_openapi, from_fastapi, create_proxy)
- Update tests to use new patterns

* Fix: reject tool lookups by pre-transform name

* Add collision validation for tool_transforms and fix docstring examples

- Validate duplicate target names in tool_transforms raise ValueError
- Fix docstring examples to use arguments/ArgTransformConfig (not args/ArgTransform)
- Add test for collision validation

* Add server-level tool transform APIs and fix task registration

- Add AggregateProvider to present multiple providers as one
- Add _get_root_provider() to apply server-level transforms uniformly
- Fix _docket_lifespan to use root provider (ensures renamed tools
  register with correct keys for background execution)
- Add tool_transforms kwarg to __init__ (non-deprecated)
- Add add_tool_transform(), remove_tool_transform(), tool_transforms property
- Deprecate old API names (tool_transformations, add_tool_transformation, etc.)
- Update tests to use new API

* Add graceful degradation for provider errors in AggregateProvider

* Match original behavior: parallel queries with DEBUG logging

* Refactor transforms to middleware-style call_next pattern

Replaces the ad-hoc transformation system with a unified Transform
abstraction using the same call_next pattern as server middleware.

Key changes:
- New src/fastmcp/server/transforms/ module with Transform base class
- Namespace, ToolTransform, Visibility all implement the same interface
- Transforms compose via functools.partial chain building
- Visibility is now just the first transform in provider._transforms
- Server-level transforms apply after provider aggregation
- Task registration now applies full transform chain

Removes TransformingProvider, _BoundTransform, ComponentSource protocol.
User-facing API unchanged: mount(), add_transform(), enable/disable all
work as before.

* Add comprehensive transforms and visibility documentation

New docs/servers/providers/transforms.mdx covering:
- Mental model for middleware-style transform pattern
- Built-in transforms (Namespace, ToolTransform)
- Server vs provider-level transforms and ordering
- Tool modification (immediate vs deferred)
- Custom transform creation

New docs/servers/visibility.mdx covering:
- Enable/disable API for runtime visibility control
- Keys and tags for targeting components
- Allowlist mode with only=True
- Server vs provider visibility layering

Updates existing docs to reference new pages and simplifies
redundant content. Visibility is documented as a user feature,
not as an implementation detail.

* Restructure transforms docs and delete tool-transformation pattern

* Cleanup: simplify get_tasks and remove unused Provider.get_component

* Update loq

* Update loq limits and add loq note to AGENTS.md

* Deprecate add_tool_transformation and tool_transformations param

* Address PR review feedback: remove redundant imports, fix path reference

* Add missing imports to code examples in v3-features.mdx
2026-01-12 22:11:16 -05:00

309 lines
8 KiB
Text

---
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
```
## Mounting External Servers
Mount remote HTTP servers or subprocess-based MCP servers using `create_proxy()`:
```python
from fastmcp import FastMCP
from fastmcp.server import create_proxy
mcp = FastMCP("Orchestrator")
# Mount a remote HTTP server (URLs work directly)
mcp.mount(create_proxy("http://api.example.com/mcp"), namespace="api")
# Mount local Python scripts (file paths work directly)
mcp.mount(create_proxy("./my_server.py"), namespace="local")
```
### Mounting npm/uvx Packages
For npm packages or Python tools, use the config dict format:
```python
from fastmcp import FastMCP
from fastmcp.server import create_proxy
mcp = FastMCP("Orchestrator")
# Mount npm package via config
github_config = {
"mcpServers": {
"default": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"]
}
}
}
mcp.mount(create_proxy(github_config), namespace="github")
# Mount Python tool via config
sqlite_config = {
"mcpServers": {
"default": {
"command": "uvx",
"args": ["mcp-server-sqlite", "--db", "data.db"]
}
}
}
mcp.mount(create_proxy(sqlite_config), namespace="db")
```
Or use explicit transport classes:
```python
from fastmcp import FastMCP
from fastmcp.server import create_proxy
from fastmcp.client.transports import NpxStdioTransport, UvxStdioTransport
mcp = FastMCP("Orchestrator")
mcp.mount(
create_proxy(NpxStdioTransport(package="@modelcontextprotocol/server-github")),
namespace="github"
)
mcp.mount(
create_proxy(UvxStdioTransport(tool_name="mcp-server-sqlite", tool_args=["--db", "data.db"])),
namespace="db"
)
```
For advanced configuration, see [Proxying](/servers/providers/proxy).
## 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 [transforms](/servers/providers/transforms) 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
<Warning>
The `as_proxy` parameter is deprecated. Mounted servers now always have their lifespan and middleware invoked. To create a proxy server explicitly, use `create_proxy()` from `fastmcp.server`.
</Warning>
Previously, the parent server could treat the mounted server as a separate entity with its own lifecycle. This behavior is now the default for all mounted servers:
- Full client lifecycle events on mounted server
- Mounted server's lifespan is executed
- Communication via in-memory Client transport
## 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)
```