Improve documentation

This commit is contained in:
Jeremiah Lowin 2025-04-16 10:56:30 -04:00
commit eecd4212ab
10 changed files with 149 additions and 192 deletions

View file

@ -5,6 +5,10 @@ description: Learn how to use the FastMCP Client to interact with MCP servers.
icon: user-robot
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
The `fastmcp.Client` provides a high-level, asynchronous interface for interacting with any Model Context Protocol (MCP) server, whether it's built with FastMCP or another implementation. It simplifies communication by handling protocol details and connection management.
## FastMCP Client

View file

@ -50,14 +50,14 @@
{
"group": "Clients",
"pages": [
"clients/overview",
"clients/client",
"clients/transports"
]
},
{
"group": "Patterns",
"pages": [
"patterns/proxying",
"patterns/proxy",
"patterns/composition",
"patterns/decorating-methods",
"patterns/openapi",

View file

@ -4,6 +4,9 @@ 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:
@ -17,13 +20,31 @@ As your MCP applications grow, you might want to organize your tools, resources,
- **Teamwork**: Different teams can work on separate FastMCP servers that are later combined.
- **Organization**: Keep related functionality grouped together logically.
## Importing Subservers (Static Composition)
### Importing vs Mounting
The choice of importing or mounting depends on your use case and requirements. In general, importing is best for simpler cases because it copies the imported server's components into the main server, treating them as native integrations. Mounting is best for more complex cases where you need to delegate requests to the subserver at runtime.
| Feature | Importing | Mounting |
|---------|----------------|---------|
| **Method** | `FastMCP.import_server()` | `FastMCP.mount()` |
| **Composition Type** | One-time copy (static) | Live link (dynamic) |
| **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
| **Lifespan** | Not managed | Automatically managed |
| **Synchronicity** | Async (must be awaited) | Sync |
| **Best For** | Bundling finalized components | Modular runtime composition |
### Proxy Servers
FastMCP supports [MCP proxying](/patterns/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.
## 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*). A `prefix` is added to avoid naming conflicts.
```python
from fastmcp import FastMCP
from typing import dict, list
import asyncio
# --- Define Subservers ---
@ -114,7 +135,7 @@ await main_mcp.import_server(
Be cautious when choosing separators. Some MCP clients (like Claude Desktop) might have restrictions on characters allowed in tool names (e.g., `/` might not be supported). The defaults (`_` for names, `+` for URIs) are generally safe.
</Warning>
## Mounting Subservers (Live Linking)
## 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 `prefix` are **delegated** to the `subserver` at runtime.
@ -186,61 +207,19 @@ main_mcp.mount(
)
```
## Comparing Import and Mount
| Feature | `import_server` | `mount` |
|---------|----------------|---------|
| **Synchronicity** | Async (must be awaited) | Sync |
| **Composition Type** | One-time copy (static) | Live link (dynamic) |
| **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
| **Lifespan** | Not managed | Automatically managed |
| **Best For** | Bundling finalized components | Modular runtime composition |
## Example: Modular Application
Here's how a modular application might use `import_server`:
```python
# modules/text_utils.py
from fastmcp import FastMCP
from typing import list
text_mcp = FastMCP(name="TextUtilities")
@text_mcp.tool()
def count_words(text: str) -> int:
"""Counts words in a text."""
return len(text.split())
@text_mcp.resource("resource://stopwords")
def get_stopwords() -> list[str]:
"""Return a list of common stopwords."""
return ["the", "a", "is", "in"]
# ------------------------------
# modules/data_api.py
from fastmcp import FastMCP
import random
from typing import dict
data_mcp = FastMCP(name="DataAPI")
@data_mcp.tool()
def fetch_record(record_id: int) -> dict:
"""Fetches a dummy data record."""
return {"id": record_id, "value": random.random()}
@data_mcp.resource("data://schema/{table}")
def get_table_schema(table: str) -> dict:
"""Provides a dummy schema for a table."""
return {"table": table, "columns": ["id", "value"]}
# ------------------------------
# main_app.py
<CodeGroup>
```python main.py
from fastmcp import FastMCP
import asyncio
from modules.text_utils import text_mcp # Import server instances
from modules.data_api import data_mcp
# Import the servers (see other files)
from modules.text_server import text_mcp
from modules.data_server import data_mcp
app = FastMCP(name="MainApplication")
@ -273,8 +252,42 @@ if __name__ == "__main__":
# Run the server
app.run()
```
```python modules/text_server.py
from fastmcp import FastMCP
Now, running `main_app.py` starts a server that exposes:
text_mcp = FastMCP(name="TextUtilities")
@text_mcp.tool()
def count_words(text: str) -> int:
"""Counts words in a text."""
return len(text.split())
@text_mcp.resource("resource://stopwords")
def get_stopwords() -> list[str]:
"""Return a list of common stopwords."""
return ["the", "a", "is", "in"]
```
```python modules/data_server.py
from fastmcp import FastMCP
import random
from typing import dict
data_mcp = FastMCP(name="DataAPI")
@data_mcp.tool()
def fetch_record(record_id: int) -> dict:
"""Fetches a dummy data record."""
return {"id": record_id, "value": random.random()}
@data_mcp.resource("data://schema/{table}")
def get_table_schema(table: str) -> dict:
"""Provides a dummy schema for a table."""
return {"table": table, "columns": ["id", "value"]}
```
</CodeGroup>
Now, running `main.py` starts a server that exposes:
- `text_count_words`
- `data_fetch_record`
- `process_and_analyze`

View file

@ -4,6 +4,9 @@ sidebarTitle: FastAPI
description: Generate MCP servers from FastAPI apps
icon: square-bolt
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
FastMCP can automatically convert FastAPI applications into MCP servers.

View file

@ -4,6 +4,9 @@ sidebarTitle: OpenAPI
description: Generate MCP servers from OpenAPI specs
icon: code-branch
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
FastMCP can automatically generate an MCP server from an OpenAPI specification. Users only need to provide an OpenAPI specification (3.0 or 3.1) and an API client.

View file

@ -4,6 +4,9 @@ sidebarTitle: Proxying
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.from_client()` class method.

View file

@ -4,6 +4,7 @@ sidebarTitle: Context
description: Access MCP capabilities like logging, progress, and resources within your tools.
icon: rectangle-code
---
import { VersionBadge } from '/snippets/version-badge.mdx'
When defining FastMCP [tools](/servers/tools), your functions might need to interact with the underlying MCP session or access server capabilities. FastMCP provides the `Context` object for this purpose.
@ -174,6 +175,8 @@ The returned content is typically accessed via `content_list[0].content` and can
### LLM Sampling
<VersionBadge version="2.0.0" />
Request the client's LLM to generate text based on provided messages. This is useful when your tool needs to leverage the LLM's capabilities to process data or generate responses.
```python

View file

@ -225,159 +225,39 @@ The CLI can dynamically find and run FastMCP server objects in your files, but i
## Composing Servers
FastMCP provides two methods for composing multiple servers together:
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.
1. `import_server()`: One-time static import of components (async)
2. `mount()`: Live link delegating to subservers (sync)
This allows you to organize large applications into logical components, reuse existing FastMCP servers, and create domain-specific servers that can be used independently or composed.
### Importing Subservers (Static Composition)
The `import_server()` method performs a one-time copy of all components from a subserver into the main server with prefixed names:
See the [Server Composition](/patterns/composition) guide for full details, best practices, and examples.
```python
# Example: Importing a subserver
from fastmcp import FastMCP
import asyncio
# Create the main server
main_mcp = FastMCP(name="MainServer")
main = FastMCP(name="Main")
sub = FastMCP(name="Sub")
# Create a domain-specific subserver
weather_mcp = FastMCP(name="WeatherService")
@sub.tool()
def hello():
return "hi"
@weather_mcp.tool()
def get_forecast(city: str) -> dict:
"""Get the weather forecast for a city."""
return {"city": city, "forecast": "Sunny", "temperature": 72}
# Create another domain-specific subserver
calculator_mcp = FastMCP(name="CalculatorService")
@calculator_mcp.tool()
def add(a: float, b: float) -> float:
"""Add two numbers."""
return a + b
# Import the subservers with prefixes
async def setup():
await main_mcp.import_server("weather", weather_mcp)
await main_mcp.import_server("calc", calculator_mcp)
# Now main_mcp has access to both subservers' tools:
# - "weather_get_forecast" (from weather_mcp)
# - "calc_add" (from calculator_mcp)
if __name__ == "__main__":
# Run async setup
asyncio.run(setup())
# Then run the server
main_mcp.run()
main.mount("sub", sub)
```
#### How Import Works
## Proxying Servers
When you import a server with `await main_mcp.import_server(prefix, subserver)`:
FastMCP can act as a proxy for any MCP server (local or remote) using `FastMCP.from_client`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa.
1. All tools from the subserver are copied with prefixed names:
- `tool_name` becomes `{prefix}_tool_name`
- Default separator is `_`, but can be customized
2. All resources and resource templates are copied with prefixed URIs:
- `resource://data` becomes `{prefix}+resource://data`
- Default separator is `+`, but can be customized
3. All prompts are copied with prefixed names:
- `prompt_name` becomes `{prefix}_prompt_name`
- Default separator is `_`, but can be customized
4. This is a **one-time copy** - changes to the subserver after importing won't be reflected in the main server
5. The subserver's lifespan is **not** managed by the main server
### Mounting Subservers (Live Linking)
The `mount()` method creates a live link between servers, delegating requests to the appropriate subserver:
See the [Proxying Servers](/patterns/proxy) guide for details and advanced usage.
```python
from fastmcp import FastMCP
from fastmcp import FastMCP, Client
# Create the main server
main_mcp = FastMCP(name="MainServer")
# Create a domain-specific subserver
weather_mcp = FastMCP(name="WeatherService")
@weather_mcp.tool()
def get_forecast(city: str) -> dict:
"""Get the weather forecast for a city."""
return {"city": city, "forecast": "Sunny", "temperature": 72}
# Mount the subserver (sync operation)
main_mcp.mount("weather", weather_mcp)
# Later, add another tool to the subserver
@weather_mcp.tool()
def get_temperature(city: str) -> float:
"""Get the current temperature for a city."""
return 72.5 # Example value
# The new tool is automatically available through the main server
# as "weather_get_temperature"
if __name__ == "__main__":
main_mcp.run()
backend = Client("http://example.com/mcp/sse")
proxy = FastMCP.from_client(backend, name="ProxyServer")
# Now use the proxy like any FastMCP server
```
#### How Mounting Works
When you mount a server with `main_mcp.mount(prefix, subserver)`:
1. A live link is created between the main server and the subserver
2. Requests for components matching the prefix are delegated to the subserver
3. Changes to the subserver are **immediately reflected** when accessing through the main server
4. The subserver's lifespan **is automatically managed** by the main server
### Customizing Separators
For both `import_server()` and `mount()`, you can customize the separators used for naming:
```python
# For import_server (async)
await main_mcp.import_server(
"weather",
weather_mcp,
tool_separator="-", # Use "weather-get_forecast" instead of "weather_get_forecast"
resource_separator=".", # Use "weather.resource://data" instead of "weather+resource://data"
prompt_separator=":" # Use "weather:prompt_name" instead of "weather_prompt_name"
)
# For mount (sync)
main_mcp.mount(
"weather",
weather_mcp,
tool_separator="-",
resource_separator=".",
prompt_separator=":"
)
```
<Warning>
Some MCP clients may reject certain separators as invalid. For example, Claude Desktop does not support `/` in tool names.
</Warning>
### Comparison
| Feature | `import_server()` | `mount()` |
|---------|------------------|-----------|
| **Synchronicity** | Async (must be awaited) | Sync |
| **Composition Type** | One-time copy (static) | Live link (dynamic) |
| **Updates** | Changes to subserver NOT reflected | Changes immediately reflected |
| **Lifespan** | Not managed | Automatically managed |
| **Best For** | Bundling finalized components | Modular runtime composition |
For more detailed examples, see the [Server Composition](/patterns/composition) guide.
## Server Configuration
Server behavior, like transport settings (host, port for SSE) and how duplicate components are handled, can be configured via `ServerSettings`. These settings can be passed during `FastMCP` initialization, set via environment variables (prefixed with `FASTMCP_SERVER_`), or loaded from a `.env` file.

View file

@ -0,0 +1,8 @@
export const VersionBadge = ({ version }) => {
return (
<span className="version-badge">
<span className="badge-emoji" aria-hidden="true" style={{ marginRight: '0.3em', verticalAlign: 'middle' }}>✨</span>
New in version {version}
</span>
);
};

View file

@ -1,4 +1,4 @@
/* Target only inline code elements, not code blocks */
/* Code highlighting -- target only inline code elements, not code blocks */
p code:not(pre code),
table code:not(pre code),
li code:not(pre code),
@ -9,5 +9,45 @@ h4 code:not(pre code),
h5 code:not(pre code),
h6 code:not(pre code) {
color: #f72585 !important;
background-color: #ea54551a !important;
background-color: rgba(247, 37, 133, 0.09);
}
/* Version badge -- display a badge with the current version of the documentation */
.version-badge {
display: inline-flex;
align-items: center;
gap: 0.3em;
padding: 0.32em 1em;
font-size: 0.92em;
font-weight: 600;
letter-spacing: 0.01em;
color: #7417e5;
background: #f3e8ff;
border: 1.5px solid #c084fc;
border-radius: 6px;
box-shadow: none;
vertical-align: middle;
position: relative;
transition: box-shadow 0.2s, transform 0.15s;
}
.version-badge:hover {
box-shadow: 0 2px 8px 0 rgba(160, 132, 252, 0.1);
transform: translateY(-1px) scale(1.03);
}
.dark .version-badge {
color: #fff;
background: #312e81;
border: 1.5px solid #a78bfa;
}
.badge-emoji {
font-size: 1.15em;
line-height: 1;
text-shadow: 0 1px 2px #fff, 0 0px 2px #c084fc;
}
.dark .badge-emoji {
text-shadow: 0 1px 2px #312e81, 0 0px 2px #a78bfa;
}