mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Restructure documentation for FastMCP 3.0 (#2951)
This commit is contained in:
parent
8596c09fdf
commit
3af9de197a
145 changed files with 27876 additions and 5545 deletions
|
|
@ -3,7 +3,6 @@ title: OAuth Authentication
|
|||
sidebarTitle: OAuth
|
||||
description: Authenticate your FastMCP client via OAuth 2.1.
|
||||
icon: window
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -9,24 +9,17 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
The central piece of MCP client applications is the `fastmcp.Client` class. This class provides a **programmatic interface** for interacting with any Model Context Protocol (MCP) server, handling protocol details and connection management automatically.
|
||||
|
||||
The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for:
|
||||
|
||||
- **Testing MCP servers** during development
|
||||
- **Building deterministic applications** that need reliable MCP interactions
|
||||
- **Creating the foundation for agentic or LLM-based clients** with structured, type-safe operations
|
||||
|
||||
All client operations require using the `async with` context manager for proper connection lifecycle management.
|
||||
The `fastmcp.Client` class provides a programmatic interface for interacting with any MCP server. It handles protocol details and connection management automatically, letting you focus on the operations you want to perform.
|
||||
|
||||
The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for testing MCP servers during development, building deterministic applications that need reliable MCP interactions, and creating the foundation for agentic or LLM-based clients with structured, type-safe operations.
|
||||
|
||||
<Note>
|
||||
This is not an agentic client - it requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems.
|
||||
This is a programmatic client that requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems.
|
||||
</Note>
|
||||
|
||||
## Creating a Client
|
||||
|
||||
Creating a client is straightforward. You provide a server source and the client automatically infers the appropriate transport mechanism.
|
||||
You provide a server source and the client automatically infers the appropriate transport mechanism.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
|
@ -46,12 +39,12 @@ async def main():
|
|||
async with client:
|
||||
# Basic server interaction
|
||||
await client.ping()
|
||||
|
||||
|
||||
# List available operations
|
||||
tools = await client.list_tools()
|
||||
resources = await client.list_resources()
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
|
||||
# Execute operations
|
||||
result = await client.call_tool("example_tool", {"param": "value"})
|
||||
print(result)
|
||||
|
|
@ -59,35 +52,42 @@ async def main():
|
|||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Client-Transport Architecture
|
||||
All client operations require using the `async with` context manager for proper connection lifecycle management.
|
||||
|
||||
The FastMCP Client separates concerns between protocol and connection:
|
||||
## Choosing a Transport
|
||||
|
||||
- **`Client`**: Handles MCP protocol operations (tools, resources, prompts) and manages callbacks
|
||||
- **`Transport`**: Establishes and maintains the connection (WebSockets, HTTP, Stdio, in-memory)
|
||||
The client automatically selects a transport based on what you pass to it, but different transports have different characteristics that matter for your use case.
|
||||
|
||||
### Transport Inference
|
||||
|
||||
The client automatically infers the appropriate transport based on the input:
|
||||
|
||||
1. **`FastMCP` instance** → In-memory transport (perfect for testing)
|
||||
2. **File path ending in `.py`** → Python Stdio transport
|
||||
3. **File path ending in `.js`** → Node.js Stdio transport
|
||||
4. **URL starting with `http://` or `https://`** → HTTP transport
|
||||
5. **`MCPConfig` dictionary** → Multi-server client
|
||||
**In-memory transport** connects directly to a FastMCP server instance within the same Python process. Use this for testing and development where you want to eliminate subprocess and network complexity. The server shares your process's environment and memory space.
|
||||
|
||||
```python
|
||||
from fastmcp import Client, FastMCP
|
||||
|
||||
# Examples of transport inference
|
||||
client_memory = Client(FastMCP("TestServer"))
|
||||
client_script = Client("./server.py")
|
||||
client_http = Client("https://api.example.com/mcp")
|
||||
server = FastMCP("TestServer")
|
||||
client = Client(server) # In-memory, no network or subprocess
|
||||
```
|
||||
|
||||
<Tip>
|
||||
For testing and development, always prefer the in-memory transport by passing a `FastMCP` server directly to the client. This eliminates network complexity and separate processes.
|
||||
</Tip>
|
||||
**STDIO transport** launches a server as a subprocess and communicates through stdin/stdout pipes. This is the standard mechanism used by desktop clients like Claude Desktop. The subprocess runs in an isolated environment, so you must explicitly pass any environment variables the server needs.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# Simple inference from file path
|
||||
client = Client("my_server.py")
|
||||
|
||||
# With explicit environment configuration
|
||||
client = Client("my_server.py", env={"API_KEY": "secret"})
|
||||
```
|
||||
|
||||
**HTTP transport** connects to servers running as web services. Use this for production deployments where the server runs independently and manages its own lifecycle.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
client = Client("https://api.example.com/mcp")
|
||||
```
|
||||
|
||||
See [Transports](/clients/transports) for detailed configuration options including authentication headers, session persistence, and multi-server configurations.
|
||||
|
||||
## Configuration-Based Clients
|
||||
|
||||
|
|
@ -95,39 +95,18 @@ For testing and development, always prefer the in-memory transport by passing a
|
|||
|
||||
Create clients from MCP configuration dictionaries, which can include multiple servers. While there is no official standard for MCP configuration format, FastMCP follows established conventions used by tools like Claude Desktop.
|
||||
|
||||
### Configuration Format
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"server_name": {
|
||||
# Remote HTTP/SSE server
|
||||
"transport": "http", # or "sse"
|
||||
"url": "https://api.example.com/mcp",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
"auth": "oauth" # or bearer token string
|
||||
"weather": {
|
||||
"url": "https://weather-api.example.com/mcp"
|
||||
},
|
||||
"local_server": {
|
||||
# Local stdio server
|
||||
"transport": "stdio",
|
||||
"assistant": {
|
||||
"command": "python",
|
||||
"args": ["./server.py", "--verbose"],
|
||||
"env": {"DEBUG": "true"},
|
||||
"cwd": "/path/to/server",
|
||||
"args": ["./assistant_server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Server Example
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {"url": "https://weather-api.example.com/mcp"},
|
||||
"assistant": {"command": "python", "args": ["./assistant_server.py"]}
|
||||
}
|
||||
}
|
||||
|
||||
client = Client(config)
|
||||
|
||||
|
|
@ -135,97 +114,14 @@ async with client:
|
|||
# Tools are prefixed with server names
|
||||
weather_data = await client.call_tool("weather_get_forecast", {"city": "London"})
|
||||
response = await client.call_tool("assistant_answer_question", {"question": "What's the capital of France?"})
|
||||
|
||||
|
||||
# Resources use prefixed URIs
|
||||
icons = await client.read_resource("weather://weather/icons/sunny")
|
||||
templates = await client.read_resource("resource://assistant/templates/list")
|
||||
```
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
The client operates asynchronously and uses context managers for connection management:
|
||||
|
||||
```python
|
||||
async def example():
|
||||
client = Client("my_mcp_server.py")
|
||||
|
||||
# Connection established here
|
||||
async with client:
|
||||
print(f"Connected: {client.is_connected()}")
|
||||
|
||||
# Make multiple calls within the same session
|
||||
tools = await client.list_tools()
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
|
||||
# Connection closed automatically here
|
||||
print(f"Connected: {client.is_connected()}")
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
FastMCP clients can interact with several types of server components:
|
||||
|
||||
### Tools
|
||||
|
||||
Tools are server-side functions that the client can execute with arguments.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# List available tools
|
||||
tools = await client.list_tools()
|
||||
|
||||
# Execute a tool
|
||||
result = await client.call_tool("multiply", {"a": 5, "b": 3})
|
||||
print(result.data) # 15
|
||||
```
|
||||
|
||||
See [Tools](/clients/tools) for detailed documentation.
|
||||
|
||||
### Resources
|
||||
|
||||
Resources are data sources that the client can read, either static or templated.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# List available resources
|
||||
resources = await client.list_resources()
|
||||
|
||||
# Read a resource
|
||||
content = await client.read_resource("file:///config/settings.json")
|
||||
print(content[0].text)
|
||||
```
|
||||
|
||||
See [Resources](/clients/resources) for detailed documentation.
|
||||
|
||||
### Prompts
|
||||
|
||||
Prompts are reusable message templates that can accept arguments.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# List available prompts
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
# Get a rendered prompt
|
||||
messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]})
|
||||
print(messages.messages)
|
||||
```
|
||||
|
||||
See [Prompts](/clients/prompts) for detailed documentation.
|
||||
|
||||
### Server Connectivity
|
||||
|
||||
Use `ping()` to verify the server is reachable:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
await client.ping()
|
||||
print("Server is reachable")
|
||||
```
|
||||
|
||||
### Initialization and Server Information
|
||||
|
||||
When you enter the client context manager, the client automatically performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions. The result is available through the `initialize_result` property.
|
||||
The client uses context managers for connection management. When you enter the context, the client establishes a connection and performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions.
|
||||
|
||||
```python
|
||||
from fastmcp import Client, FastMCP
|
||||
|
|
@ -240,21 +136,15 @@ def greet(name: str) -> str:
|
|||
async with Client(mcp) as client:
|
||||
# Initialization already happened automatically
|
||||
print(f"Server: {client.initialize_result.serverInfo.name}")
|
||||
print(f"Version: {client.initialize_result.serverInfo.version}")
|
||||
print(f"Instructions: {client.initialize_result.instructions}")
|
||||
print(f"Capabilities: {client.initialize_result.capabilities.tools}")
|
||||
```
|
||||
|
||||
#### Manual Initialization Control
|
||||
|
||||
In advanced scenarios, you might want precise control over when initialization happens. For example, you may need custom error handling, want to defer initialization until after other setup, or need to measure initialization timing separately.
|
||||
|
||||
Disable automatic initialization and call `initialize()` manually:
|
||||
For advanced scenarios where you need precise control over when initialization happens, disable automatic initialization and call `initialize()` manually:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# Disable automatic initialization
|
||||
client = Client("my_mcp_server.py", auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
|
|
@ -270,15 +160,46 @@ async with client:
|
|||
tools = await client.list_tools()
|
||||
```
|
||||
|
||||
The `initialize()` method is idempotent - calling it multiple times returns the cached result from the first successful call.
|
||||
## Operations
|
||||
|
||||
## Client Configuration
|
||||
FastMCP clients interact with three types of server components.
|
||||
|
||||
Clients can be configured with additional handlers and settings for specialized use cases.
|
||||
**Tools** are server-side functions that the client can execute with arguments. Call them with `call_tool()` and receive structured results.
|
||||
|
||||
### Callback Handlers
|
||||
```python
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
result = await client.call_tool("multiply", {"a": 5, "b": 3})
|
||||
print(result.data) # 15
|
||||
```
|
||||
|
||||
The client supports several callback handlers for advanced server interactions:
|
||||
See [Tools](/clients/tools) for detailed documentation including version selection, error handling, and structured output.
|
||||
|
||||
**Resources** are data sources that the client can read, either static or templated. Access them with `read_resource()` using URIs.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
content = await client.read_resource("file:///config/settings.json")
|
||||
print(content[0].text)
|
||||
```
|
||||
|
||||
See [Resources](/clients/resources) for detailed documentation including templates and binary content.
|
||||
|
||||
**Prompts** are reusable message templates that can accept arguments. Retrieve rendered prompts with `get_prompt()`.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]})
|
||||
print(messages.messages)
|
||||
```
|
||||
|
||||
See [Prompts](/clients/prompts) for detailed documentation including argument serialization.
|
||||
|
||||
## Callback Handlers
|
||||
|
||||
The client supports callback handlers for advanced server interactions. These let you respond to server-initiated requests and receive notifications.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
|
@ -303,38 +224,14 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
The `Client` constructor accepts several configuration options:
|
||||
Each handler type has its own documentation:
|
||||
|
||||
- `transport`: Transport instance or source for automatic inference
|
||||
- `log_handler`: Handle server log messages
|
||||
- `progress_handler`: Monitor long-running operations
|
||||
- `sampling_handler`: Respond to server LLM requests
|
||||
- `roots`: Provide local context to servers
|
||||
- `timeout`: Default timeout for requests (in seconds)
|
||||
|
||||
### Transport Configuration
|
||||
|
||||
For detailed transport configuration (headers, authentication, environment variables), see the [Transports](/clients/transports) documentation.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Explore the detailed documentation for each operation type:
|
||||
|
||||
### Core Operations
|
||||
- **[Tools](/clients/tools)** - Execute server-side functions and handle results
|
||||
- **[Resources](/clients/resources)** - Access static and templated resources
|
||||
- **[Prompts](/clients/prompts)** - Work with message templates and argument serialization
|
||||
|
||||
### Advanced Features
|
||||
- **[Logging](/clients/logging)** - Handle server log messages
|
||||
- **[Progress](/clients/progress)** - Monitor long-running operations
|
||||
- **[Sampling](/clients/sampling)** - Respond to server LLM requests
|
||||
- **[Elicitation](/clients/elicitation)** - Handle server requests for user input
|
||||
- **[Progress](/clients/progress)** - Monitor long-running operations
|
||||
- **[Logging](/clients/logging)** - Handle server log messages
|
||||
- **[Roots](/clients/roots)** - Provide local context to servers
|
||||
|
||||
### Connection Details
|
||||
- **[Transports](/clients/transports)** - Configure connection methods and parameters
|
||||
- **[Authentication](/clients/auth/oauth)** - Set up OAuth and bearer token authentication
|
||||
|
||||
<Tip>
|
||||
The FastMCP Client is designed as a foundational tool. Use it directly for deterministic operations, or build higher-level agentic systems on top of its reliable, type-safe interface.
|
||||
</Tip>
|
||||
</Tip>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: User Elicitation
|
||||
sidebarTitle: Elicitation
|
||||
description: Handle server-initiated user input requests with structured schemas.
|
||||
description: Handle server requests for structured user input.
|
||||
icon: message-question
|
||||
---
|
||||
|
||||
|
|
@ -9,44 +9,43 @@ import { VersionBadge } from "/snippets/version-badge.mdx";
|
|||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
## What is Elicitation?
|
||||
Use this when you need to respond to server requests for user input during tool execution.
|
||||
|
||||
Elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, servers can interactively ask users for information as needed - like prompting for missing parameters, requesting clarification, or gathering additional context.
|
||||
Elicitation allows MCP servers to request structured input from users during operations. Instead of requiring all inputs upfront, servers can interactively ask for missing parameters, request clarification, or gather additional context.
|
||||
|
||||
For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?"
|
||||
|
||||
## How FastMCP Makes Elicitation Easy
|
||||
|
||||
FastMCP's client provides a helpful abstraction layer that:
|
||||
|
||||
- **Converts JSON schemas to Python types**: The raw MCP protocol uses JSON schemas, but FastMCP automatically converts these to Python dataclasses
|
||||
- **Provides structured constructors**: Instead of manually building dictionaries that match the schema, you get dataclass constructors that ensure correct structure
|
||||
- **Handles type conversion**: FastMCP takes care of converting between JSON representations and Python objects
|
||||
- **Runtime introspection**: You can inspect the generated dataclass fields to understand the expected structure
|
||||
|
||||
When you implement an elicitation handler, FastMCP gives you a dataclass type that matches the server's schema, making it easy to create properly structured responses without having to manually parse JSON schemas.
|
||||
|
||||
## Elicitation Handler
|
||||
|
||||
Provide an `elicitation_handler` function when creating the client. FastMCP automatically converts the server's JSON schema into a Python dataclass type, making it easy to construct the response:
|
||||
## Handler Template
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.elicitation import ElicitResult
|
||||
from fastmcp.client.elicitation import ElicitResult, ElicitRequestParams, RequestContext
|
||||
|
||||
async def elicitation_handler(message: str, response_type: type, params, context):
|
||||
# Present the message to the user and collect input
|
||||
async def elicitation_handler(
|
||||
message: str,
|
||||
response_type: type | None,
|
||||
params: ElicitRequestParams,
|
||||
context: RequestContext
|
||||
) -> ElicitResult | object:
|
||||
"""
|
||||
Handle server requests for user input.
|
||||
|
||||
Args:
|
||||
message: The prompt to display to the user
|
||||
response_type: Python dataclass type for the response (None if no data expected)
|
||||
params: Original MCP elicitation parameters including raw JSON schema
|
||||
context: Request context with metadata
|
||||
|
||||
Returns:
|
||||
- Data directly (implicitly accepts the elicitation)
|
||||
- ElicitResult for explicit control over the action
|
||||
"""
|
||||
# Present the message and collect input
|
||||
user_input = input(f"{message}: ")
|
||||
|
||||
|
||||
if not user_input:
|
||||
return ElicitResult(action="decline")
|
||||
|
||||
# Create response using the provided dataclass type
|
||||
# FastMCP converted the JSON schema to this Python type for you
|
||||
response_data = response_type(value=user_input)
|
||||
|
||||
# You can return data directly - FastMCP will implicitly accept the elicitation
|
||||
return response_data
|
||||
|
||||
# Or explicitly return an ElicitResult for more control
|
||||
# return ElicitResult(action="accept", content=response_data)
|
||||
return response_type(value=user_input)
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
|
|
@ -54,21 +53,23 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
### Handler Parameters
|
||||
## How It Works
|
||||
|
||||
The elicitation handler receives four parameters:
|
||||
When a server needs user input, it sends an elicitation request with a message prompt and a JSON schema describing the expected response structure. FastMCP automatically converts this schema into a Python dataclass type, making it easy to construct properly typed responses without manually parsing JSON schemas.
|
||||
|
||||
<Card icon="code" title="Elicitation Handler Parameters">
|
||||
The handler receives four parameters:
|
||||
|
||||
<Card icon="code" title="Handler Parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The prompt message to display to the user
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="response_type" type="type">
|
||||
A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing and IDE support. If the server requests an empty object (indicating no response), this will be `None`.
|
||||
<ResponseField name="response_type" type="type | None">
|
||||
A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing. If the server requests an empty object, this will be `None`.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="params" type="ElicitRequestParams">
|
||||
The original MCP elicitation request parameters, including the raw JSON schema in `params.requestedSchema` if you need it
|
||||
The original MCP elicitation parameters, including the raw JSON schema in `params.requestedSchema`
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="context" type="RequestContext">
|
||||
|
|
@ -76,50 +77,62 @@ The elicitation handler receives four parameters:
|
|||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
### Response Actions
|
||||
## Response Actions
|
||||
|
||||
The handler can return data directly (which implicitly accepts the elicitation) or an `ElicitResult` object for more control over the response action:
|
||||
You can return data directly, which implicitly accepts the elicitation:
|
||||
|
||||
<Card icon="code" title="ElicitResult Structure">
|
||||
<ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
|
||||
How the user responded to the elicitation request
|
||||
</ResponseField>
|
||||
```python
|
||||
async def elicitation_handler(message, response_type, params, context):
|
||||
user_input = input(f"{message}: ")
|
||||
return response_type(value=user_input) # Implicit accept
|
||||
```
|
||||
|
||||
<ResponseField name="content" type="dataclass instance | dict | None">
|
||||
The user's input data (required for "accept", omitted for "decline"/"cancel")
|
||||
</ResponseField>
|
||||
</Card>
|
||||
Or return an `ElicitResult` for explicit control over the action:
|
||||
|
||||
**Action Types:**
|
||||
- **`accept`**: User provided valid input - include their data in the `content` field
|
||||
- **`decline`**: User chose not to provide the requested information - omit `content`
|
||||
- **`cancel`**: User cancelled the entire operation - omit `content`
|
||||
```python
|
||||
from fastmcp.client.elicitation import ElicitResult
|
||||
|
||||
## Basic Example
|
||||
async def elicitation_handler(message, response_type, params, context):
|
||||
user_input = input(f"{message}: ")
|
||||
|
||||
if not user_input:
|
||||
return ElicitResult(action="decline") # User declined
|
||||
|
||||
if user_input == "cancel":
|
||||
return ElicitResult(action="cancel") # Cancel entire operation
|
||||
|
||||
return ElicitResult(
|
||||
action="accept",
|
||||
content=response_type(value=user_input)
|
||||
)
|
||||
```
|
||||
|
||||
**Action types:**
|
||||
- **`accept`**: User provided valid input. Include the data in the `content` field.
|
||||
- **`decline`**: User chose not to provide the requested information. Omit `content`.
|
||||
- **`cancel`**: User cancelled the entire operation. Omit `content`.
|
||||
|
||||
## Example
|
||||
|
||||
A file management tool might ask which directory to create:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.elicitation import ElicitResult
|
||||
|
||||
async def basic_elicitation_handler(message: str, response_type: type, params, context):
|
||||
async def elicitation_handler(message, response_type, params, context):
|
||||
print(f"Server asks: {message}")
|
||||
|
||||
# Simple text input for demonstration
|
||||
|
||||
user_response = input("Your response: ")
|
||||
|
||||
|
||||
if not user_response:
|
||||
# For non-acceptance, use ElicitResult explicitly
|
||||
return ElicitResult(action="decline")
|
||||
|
||||
|
||||
# Use the response_type dataclass to create a properly structured response
|
||||
# FastMCP handles the conversion from JSON schema to Python type
|
||||
# Return data directly - FastMCP will implicitly accept the elicitation
|
||||
return response_type(value=user_response)
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
elicitation_handler=basic_elicitation_handler
|
||||
"my_mcp_server.py",
|
||||
elicitation_handler=elicitation_handler
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,82 +9,61 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
MCP servers can emit log messages to clients. The client can handle these logs through a log handler callback.
|
||||
Use this when you need to capture or process log messages sent by the server.
|
||||
|
||||
MCP servers can emit log messages to clients. The client handles these through a log handler callback.
|
||||
|
||||
## Log Handler
|
||||
|
||||
Provide a `log_handler` function when creating the client. For robust logging, the log messages can be integrated with Python's standard `logging` module.
|
||||
Provide a `log_handler` function when creating the client:
|
||||
|
||||
```python
|
||||
import logging
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.logging import LogMessage
|
||||
|
||||
# In a real app, you might configure this in your main entry point
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Get a logger for the module where the client is used
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# This mapping is useful for converting MCP level strings to Python's levels
|
||||
LOGGING_LEVEL_MAP = logging.getLevelNamesMapping()
|
||||
|
||||
async def log_handler(message: LogMessage):
|
||||
"""
|
||||
Handles incoming logs from the MCP server and forwards them
|
||||
to the standard Python logging system.
|
||||
"""
|
||||
"""Forward MCP server logs to Python's logging system."""
|
||||
msg = message.data.get('msg')
|
||||
extra = message.data.get('extra')
|
||||
|
||||
# Convert the MCP log level to a Python log level
|
||||
level = LOGGING_LEVEL_MAP.get(message.level.upper(), logging.INFO)
|
||||
|
||||
# Log the message using the standard logging library
|
||||
logger.log(level, msg, extra=extra)
|
||||
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
log_handler=log_handler,
|
||||
)
|
||||
```
|
||||
|
||||
## Handling Structured Logs
|
||||
The handler receives a `LogMessage` object:
|
||||
|
||||
The `message.data` attribute is a dictionary that contains the log payload from the server. This enables structured logging, allowing you to receive rich, contextual information.
|
||||
<Card icon="code" title="LogMessage">
|
||||
<ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
|
||||
The log level
|
||||
</ResponseField>
|
||||
|
||||
The dictionary contains two keys:
|
||||
- `msg`: The string log message.
|
||||
- `extra`: A dictionary containing any extra data sent from the server.
|
||||
<ResponseField name="logger" type="str | None">
|
||||
The logger name (may be None)
|
||||
</ResponseField>
|
||||
|
||||
This structure is preserved even when logs are forwarded through a FastMCP proxy, making it a powerful tool for debugging complex, multi-server applications.
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The `log_handler` is called every time a log message is received. It receives a `LogMessage` object:
|
||||
|
||||
<Card icon="code" title="Log Handler Parameters">
|
||||
<ResponseField name="LogMessage" type="Log Message Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
|
||||
The log level
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="logger" type="str | None">
|
||||
The logger name (optional, may be None)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="data" type="dict">
|
||||
The log payload, containing `msg` and `extra` keys.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
<ResponseField name="data" type="dict">
|
||||
The log payload, containing `msg` and `extra` keys
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Structured Logs
|
||||
|
||||
The `message.data` attribute is a dictionary containing the log payload. This enables structured logging with rich contextual information.
|
||||
|
||||
```python
|
||||
async def detailed_log_handler(message: LogMessage):
|
||||
msg = message.data.get('msg')
|
||||
|
|
@ -98,14 +77,16 @@ async def detailed_log_handler(message: LogMessage):
|
|||
print(f"{message.level.upper()}: {msg}")
|
||||
```
|
||||
|
||||
## Default Log Handling
|
||||
This structure is preserved even when logs are forwarded through a FastMCP proxy, making it useful for debugging multi-server applications.
|
||||
|
||||
If you don't provide a custom `log_handler`, FastMCP's default handler routes server logs to the appropriate Python logging levels. The MCP levels are mapped as follows: `notice` → INFO; `alert` and `emergency` → CRITICAL. If the server includes a logger name, it is prefixed in the message, and any `extra` data is forwarded via the logging `extra` parameter.
|
||||
## Default Behavior
|
||||
|
||||
If you do not provide a custom `log_handler`, FastMCP's default handler routes server logs to Python's logging system at the appropriate severity level. The MCP levels map as follows: `notice` becomes INFO; `alert` and `emergency` become CRITICAL.
|
||||
|
||||
```python
|
||||
client = Client("my_mcp_server.py")
|
||||
|
||||
async with client:
|
||||
# Server logs are forwarded at their proper severity (DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
||||
# Server logs are forwarded at proper severity automatically
|
||||
await client.call_tool("some_tool")
|
||||
```
|
||||
|
|
|
|||
155
docs/clients/notifications.mdx
Normal file
155
docs/clients/notifications.mdx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
title: Notifications
|
||||
sidebarTitle: Notifications
|
||||
description: Handle server-sent notifications for list changes and other events.
|
||||
icon: envelope
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
Use this when you need to react to server-side changes like tool list updates or resource modifications.
|
||||
|
||||
MCP servers can send notifications to inform clients about state changes. The message handler provides a unified way to process these notifications.
|
||||
|
||||
## Handling Notifications
|
||||
|
||||
The simplest approach is a function that receives all messages and filters for the notifications you care about:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async def message_handler(message):
|
||||
"""Handle MCP notifications from the server."""
|
||||
if hasattr(message, 'root'):
|
||||
method = message.root.method
|
||||
|
||||
if method == "notifications/tools/list_changed":
|
||||
print("Tools have changed - refresh tool cache")
|
||||
elif method == "notifications/resources/list_changed":
|
||||
print("Resources have changed")
|
||||
elif method == "notifications/prompts/list_changed":
|
||||
print("Prompts have changed")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
message_handler=message_handler,
|
||||
)
|
||||
```
|
||||
|
||||
## MessageHandler Class
|
||||
|
||||
For fine-grained targeting, subclass `MessageHandler` to use specific hooks:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class MyMessageHandler(MessageHandler):
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Handle tool list changes."""
|
||||
print("Tool list changed - refreshing available tools")
|
||||
|
||||
async def on_resource_list_changed(
|
||||
self, notification: mcp.types.ResourceListChangedNotification
|
||||
) -> None:
|
||||
"""Handle resource list changes."""
|
||||
print("Resource list changed")
|
||||
|
||||
async def on_prompt_list_changed(
|
||||
self, notification: mcp.types.PromptListChangedNotification
|
||||
) -> None:
|
||||
"""Handle prompt list changes."""
|
||||
print("Prompt list changed")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
message_handler=MyMessageHandler(),
|
||||
)
|
||||
```
|
||||
|
||||
### Handler Template
|
||||
|
||||
```python
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class MyMessageHandler(MessageHandler):
|
||||
async def on_message(self, message) -> None:
|
||||
"""Called for ALL messages (requests and notifications)."""
|
||||
pass
|
||||
|
||||
async def on_notification(
|
||||
self, notification: mcp.types.ServerNotification
|
||||
) -> None:
|
||||
"""Called for notifications (fire-and-forget)."""
|
||||
pass
|
||||
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Called when the server's tool list changes."""
|
||||
pass
|
||||
|
||||
async def on_resource_list_changed(
|
||||
self, notification: mcp.types.ResourceListChangedNotification
|
||||
) -> None:
|
||||
"""Called when the server's resource list changes."""
|
||||
pass
|
||||
|
||||
async def on_prompt_list_changed(
|
||||
self, notification: mcp.types.PromptListChangedNotification
|
||||
) -> None:
|
||||
"""Called when the server's prompt list changes."""
|
||||
pass
|
||||
|
||||
async def on_progress(
|
||||
self, notification: mcp.types.ProgressNotification
|
||||
) -> None:
|
||||
"""Called for progress updates during long-running operations."""
|
||||
pass
|
||||
|
||||
async def on_logging_message(
|
||||
self, notification: mcp.types.LoggingMessageNotification
|
||||
) -> None:
|
||||
"""Called for log messages from the server."""
|
||||
pass
|
||||
```
|
||||
|
||||
## List Change Notifications
|
||||
|
||||
A practical example of maintaining a tool cache that refreshes when tools change:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class ToolCacheHandler(MessageHandler):
|
||||
def __init__(self):
|
||||
self.cached_tools = []
|
||||
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Clear tool cache when tools change."""
|
||||
print("Tools changed - clearing cache")
|
||||
self.cached_tools = [] # Force refresh on next access
|
||||
|
||||
client = Client("server.py", message_handler=ToolCacheHandler())
|
||||
```
|
||||
|
||||
## Server Requests
|
||||
|
||||
While the message handler receives server-initiated requests, you should use dedicated callback parameters for most interactive scenarios:
|
||||
|
||||
- **Sampling requests**: Use [`sampling_handler`](/clients/sampling)
|
||||
- **Elicitation requests**: Use [`elicitation_handler`](/clients/elicitation)
|
||||
- **Progress updates**: Use [`progress_handler`](/clients/progress)
|
||||
- **Log messages**: Use [`log_handler`](/clients/logging)
|
||||
|
||||
The message handler is primarily for monitoring and handling notifications rather than responding to requests.
|
||||
|
|
@ -9,18 +9,20 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.3.5" />
|
||||
|
||||
MCP servers can report progress during long-running operations. The client can receive these updates through a progress handler.
|
||||
Use this when you need to track progress of long-running operations.
|
||||
|
||||
MCP servers can report progress during operations. The client receives these updates through a progress handler.
|
||||
|
||||
## Progress Handler
|
||||
|
||||
Set a progress handler when creating the client:
|
||||
Set a handler when creating the client:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async def my_progress_handler(
|
||||
progress: float,
|
||||
total: float | None,
|
||||
async def progress_handler(
|
||||
progress: float,
|
||||
total: float | None,
|
||||
message: str | None
|
||||
) -> None:
|
||||
if total is not None:
|
||||
|
|
@ -31,40 +33,35 @@ async def my_progress_handler(
|
|||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
progress_handler=my_progress_handler
|
||||
progress_handler=progress_handler
|
||||
)
|
||||
```
|
||||
|
||||
### Handler Parameters
|
||||
The handler receives three parameters:
|
||||
|
||||
The progress handler receives three parameters:
|
||||
|
||||
|
||||
<Card icon="code" title="Progress Handler Parameters">
|
||||
<Card icon="code" title="Handler Parameters">
|
||||
<ResponseField name="progress" type="float">
|
||||
Current progress value
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="total" type="float | None">
|
||||
Expected total value (may be None)
|
||||
Expected total value (may be None if unknown)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="message" type="str | None">
|
||||
Optional status message (may be None)
|
||||
Optional status message
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Per-Call Handler
|
||||
|
||||
## Per-Call Progress Handler
|
||||
|
||||
Override the progress handler for specific tool calls:
|
||||
Override the client-level handler for specific tool calls:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Override with specific progress handler for this call
|
||||
result = await client.call_tool(
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
progress_handler=my_progress_handler
|
||||
)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Prompts
|
||||
title: Getting Prompts
|
||||
sidebarTitle: Prompts
|
||||
description: Use server-side prompt templates with automatic argument serialization.
|
||||
description: Retrieve rendered message templates with automatic argument serialization.
|
||||
icon: message-lines
|
||||
---
|
||||
|
||||
|
|
@ -9,112 +9,44 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Use this when you need to retrieve server-defined message templates for LLM interactions.
|
||||
|
||||
Prompts are reusable message templates exposed by MCP servers. They can accept arguments to generate personalized message sequences for LLM interactions.
|
||||
|
||||
## Listing Prompts
|
||||
## Basic Usage
|
||||
|
||||
Use `list_prompts()` to retrieve all available prompt templates. When the server paginates results, the client automatically fetches all pages and returns the complete list.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
# prompts -> list[mcp.types.Prompt]
|
||||
|
||||
for prompt in prompts:
|
||||
print(f"Prompt: {prompt.name}")
|
||||
print(f"Description: {prompt.description}")
|
||||
if prompt.arguments:
|
||||
print(f"Arguments: {[arg.name for arg in prompt.arguments]}")
|
||||
# Access tags and other metadata
|
||||
if prompt.meta:
|
||||
fastmcp_meta = prompt.meta.get('fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
For manual pagination control, use `list_prompts_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details.
|
||||
|
||||
### Filtering by Tags
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
You can use the `meta` field to filter prompts based on their tags:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
# Filter prompts by tag
|
||||
analysis_prompts = [
|
||||
prompt for prompt in prompts
|
||||
if prompt.meta and
|
||||
prompt.meta.get('fastmcp', {}) and
|
||||
'analysis' in prompt.meta.get('fastmcp', {}).get('tags', [])
|
||||
]
|
||||
|
||||
print(f"Found {len(analysis_prompts)} analysis prompts")
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. Component versions are also included in the metadata when available (e.g., `meta.fastmcp.version`). Other MCP server implementations may not provide this metadata structure.
|
||||
</Note>
|
||||
|
||||
## Using Prompts
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Request a rendered prompt using `get_prompt()` with the prompt name and arguments:
|
||||
Request a rendered prompt with `get_prompt()`:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Simple prompt without arguments
|
||||
result = await client.get_prompt("welcome_message")
|
||||
# result -> mcp.types.GetPromptResult
|
||||
|
||||
|
||||
# Access the generated messages
|
||||
for message in result.messages:
|
||||
print(f"Role: {message.role}")
|
||||
print(f"Content: {message.content}")
|
||||
```
|
||||
|
||||
### Prompts with Arguments
|
||||
|
||||
Pass arguments as a dictionary to customize the prompt:
|
||||
Pass arguments to customize the prompt:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Prompt with simple arguments
|
||||
result = await client.get_prompt("user_greeting", {
|
||||
"name": "Alice",
|
||||
"role": "administrator"
|
||||
})
|
||||
|
||||
# Access the personalized messages
|
||||
for message in result.messages:
|
||||
print(f"Generated message: {message.content}")
|
||||
```
|
||||
|
||||
### Requesting Specific Versions
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
When a server has multiple versions of a prompt, you can request a specific version instead of the default (highest) version.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Get the highest version (default)
|
||||
result = await client.get_prompt("summarize", {"text": "..."})
|
||||
|
||||
# Get a specific version
|
||||
result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0")
|
||||
```
|
||||
|
||||
To discover available versions, check the `meta.fastmcp.versions` field when listing prompts. See [Version Selection](#version-selection) below for more details.
|
||||
|
||||
## Automatic Argument Serialization
|
||||
## Argument Serialization
|
||||
|
||||
<VersionBadge version="2.9.0" />
|
||||
|
||||
FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. This allows you to pass typed objects directly:
|
||||
FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. You can pass typed objects directly:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
|
@ -125,82 +57,31 @@ class UserData:
|
|||
age: int
|
||||
|
||||
async with client:
|
||||
# Complex arguments are automatically serialized
|
||||
result = await client.get_prompt("analyze_user", {
|
||||
"user": UserData(name="Alice", age=30), # Automatically serialized to JSON
|
||||
"preferences": {"theme": "dark"}, # Dict serialized to JSON string
|
||||
"scores": [85, 92, 78], # List serialized to JSON string
|
||||
"simple_name": "Bob" # Strings passed through unchanged
|
||||
"user": UserData(name="Alice", age=30), # Automatically serialized
|
||||
"preferences": {"theme": "dark"}, # Dict serialized
|
||||
"scores": [85, 92, 78], # List serialized
|
||||
"simple_name": "Bob" # Strings unchanged
|
||||
})
|
||||
```
|
||||
|
||||
The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers can automatically deserialize these JSON strings back to the expected types.
|
||||
The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers automatically deserialize these JSON strings back to the expected types.
|
||||
|
||||
### Serialization Examples
|
||||
## Working with Results
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt("data_analysis", {
|
||||
# These will be automatically serialized to JSON strings:
|
||||
"config": {
|
||||
"format": "csv",
|
||||
"include_headers": True,
|
||||
"delimiter": ","
|
||||
},
|
||||
"filters": [
|
||||
{"field": "age", "operator": ">", "value": 18},
|
||||
{"field": "status", "operator": "==", "value": "active"}
|
||||
],
|
||||
# This remains a string:
|
||||
"report_title": "Monthly Analytics Report"
|
||||
})
|
||||
```
|
||||
|
||||
## Working with Prompt Results
|
||||
|
||||
The `get_prompt()` method returns a `GetPromptResult` object containing a list of messages:
|
||||
The `get_prompt()` method returns a `GetPromptResult` containing a list of messages:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt("conversation_starter", {"topic": "climate"})
|
||||
|
||||
# Access individual messages
|
||||
|
||||
for i, message in enumerate(result.messages):
|
||||
print(f"Message {i + 1}:")
|
||||
print(f" Role: {message.role}")
|
||||
print(f" Content: {message.content.text if hasattr(message.content, 'text') else message.content}")
|
||||
```
|
||||
|
||||
## Raw MCP Protocol Access
|
||||
|
||||
For access to the complete MCP protocol objects, use the `*_mcp` methods:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Raw MCP method returns full protocol object
|
||||
prompts_result = await client.list_prompts_mcp()
|
||||
# prompts_result -> mcp.types.ListPromptsResult
|
||||
|
||||
prompt_result = await client.get_prompt_mcp("example_prompt", {"arg": "value"})
|
||||
# prompt_result -> mcp.types.GetPromptResult
|
||||
```
|
||||
|
||||
## Multi-Server Clients
|
||||
|
||||
When using multi-server clients, prompts are accessible without prefixing (unlike tools):
|
||||
|
||||
```python
|
||||
async with client: # Multi-server client
|
||||
# Prompts from any server are directly accessible
|
||||
result1 = await client.get_prompt("weather_prompt", {"city": "London"})
|
||||
result2 = await client.get_prompt("assistant_prompt", {"query": "help"})
|
||||
```
|
||||
|
||||
## Common Prompt Patterns
|
||||
|
||||
### System Messages
|
||||
|
||||
Many prompts generate system messages for LLM configuration:
|
||||
Prompts can generate different message types. System messages configure LLM behavior:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
|
|
@ -208,15 +89,13 @@ async with client:
|
|||
"role": "helpful assistant",
|
||||
"expertise": "python programming"
|
||||
})
|
||||
|
||||
|
||||
# Typically returns messages with role="system"
|
||||
system_message = result.messages[0]
|
||||
print(f"System prompt: {system_message.content}")
|
||||
```
|
||||
|
||||
### Conversation Templates
|
||||
|
||||
Prompts can generate multi-turn conversation templates:
|
||||
Conversation templates generate multi-turn flows:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
|
|
@ -224,56 +103,45 @@ async with client:
|
|||
"candidate_name": "Alice",
|
||||
"position": "Senior Developer"
|
||||
})
|
||||
|
||||
|
||||
# Multiple messages for a conversation flow
|
||||
for message in result.messages:
|
||||
print(f"{message.role}: {message.content}")
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Prompt arguments and their expected types depend on the specific prompt implementation. Check the server's documentation or use `list_prompts()` to see available arguments for each prompt.
|
||||
</Tip>
|
||||
|
||||
## Version Selection
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
FastMCP servers can expose multiple versions of the same prompt. By default, clients receive and request the highest version, but you can request a specific version when needed.
|
||||
|
||||
### Discovering Versions
|
||||
|
||||
When a server registers multiple versions of a prompt, the `list_prompts()` response includes version information in the metadata. The `meta.fastmcp.version` field shows which version is being returned, while `meta.fastmcp.versions` lists all available versions sorted from highest to lowest.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
for prompt in prompts:
|
||||
if prompt.meta:
|
||||
fastmcp_meta = prompt.meta.get("fastmcp", {})
|
||||
version = fastmcp_meta.get("version")
|
||||
all_versions = fastmcp_meta.get("versions")
|
||||
if all_versions:
|
||||
print(f"{prompt.name}: v{version} (available: {all_versions})")
|
||||
```
|
||||
|
||||
Unversioned prompts omit these metadata fields entirely.
|
||||
|
||||
### Getting Specific Versions
|
||||
|
||||
Pass the `version` parameter to `get_prompt()` to render a specific version instead of the highest.
|
||||
When a server exposes multiple versions of a prompt, you can request a specific version:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Get the highest version (default)
|
||||
result = await client.get_prompt("summarize", {"text": "..."})
|
||||
|
||||
# Get version 1.0 specifically
|
||||
# Get a specific version
|
||||
result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0")
|
||||
```
|
||||
|
||||
If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version.
|
||||
See [Metadata](/servers/versioning#version-discovery) for how to discover available versions.
|
||||
|
||||
<Note>
|
||||
Version selection is a FastMCP extension to the MCP protocol. See [Versioning](/servers/versioning#requesting-specific-versions) for details on how this works at the protocol level for non-FastMCP clients.
|
||||
</Note>
|
||||
## Multi-Server Clients
|
||||
|
||||
When using multi-server clients, prompts are accessible directly without prefixing:
|
||||
|
||||
```python
|
||||
async with client: # Multi-server client
|
||||
result1 = await client.get_prompt("weather_prompt", {"city": "London"})
|
||||
result2 = await client.get_prompt("assistant_prompt", {"query": "help"})
|
||||
```
|
||||
|
||||
## Raw Protocol Access
|
||||
|
||||
For complete control, use `get_prompt_mcp()` which returns the full MCP protocol object:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt_mcp("example_prompt", {"arg": "value"})
|
||||
# result -> mcp.types.GetPromptResult
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Resource Operations
|
||||
title: Reading Resources
|
||||
sidebarTitle: Resources
|
||||
description: Access static and templated resources from MCP servers.
|
||||
description: Access static and templated data sources from MCP servers.
|
||||
icon: folder-open
|
||||
---
|
||||
|
||||
|
|
@ -9,226 +9,102 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Resources are data sources exposed by MCP servers. They can be static files or dynamic templates that generate content based on parameters.
|
||||
Use this when you need to read data from server-exposed resources like configuration files, generated content, or external data sources.
|
||||
|
||||
## Types of Resources
|
||||
|
||||
MCP servers expose two types of resources:
|
||||
|
||||
- **Static Resources**: Fixed content accessible via URI (e.g., configuration files, documentation)
|
||||
- **Resource Templates**: Dynamic resources that accept parameters to generate content (e.g., API endpoints, database queries)
|
||||
|
||||
## Listing Resources
|
||||
|
||||
### Static Resources
|
||||
|
||||
Use `list_resources()` to retrieve all static resources available on the server. When the server paginates results, the client automatically fetches all pages and returns the complete list.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
# resources -> list[mcp.types.Resource]
|
||||
|
||||
for resource in resources:
|
||||
print(f"Resource URI: {resource.uri}")
|
||||
print(f"Name: {resource.name}")
|
||||
print(f"Description: {resource.description}")
|
||||
print(f"MIME Type: {resource.mimeType}")
|
||||
# Access tags and other metadata
|
||||
if resource.meta:
|
||||
fastmcp_meta = resource.meta.get('fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
For manual pagination control, use `list_resources_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details.
|
||||
|
||||
### Resource Templates
|
||||
|
||||
Use `list_resource_templates()` to retrieve available resource templates. When the server paginates results, the client automatically fetches all pages and returns the complete list.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
templates = await client.list_resource_templates()
|
||||
# templates -> list[mcp.types.ResourceTemplate]
|
||||
|
||||
for template in templates:
|
||||
print(f"Template URI: {template.uriTemplate}")
|
||||
print(f"Name: {template.name}")
|
||||
print(f"Description: {template.description}")
|
||||
# Access tags and other metadata
|
||||
if template.meta:
|
||||
fastmcp_meta = template.meta.get('fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
For manual pagination control, use `list_resource_templates_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details.
|
||||
|
||||
### Filtering by Tags
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
You can use the `meta` field to filter resources based on their tags:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
|
||||
# Filter resources by tag
|
||||
config_resources = [
|
||||
resource for resource in resources
|
||||
if resource.meta and
|
||||
resource.meta.get('fastmcp', {}) and
|
||||
'config' in resource.meta.get('fastmcp', {}).get('tags', [])
|
||||
]
|
||||
|
||||
print(f"Found {len(config_resources)} config resources")
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. For versioned resources, `meta.fastmcp.version` shows the current version and `meta.fastmcp.versions` lists all available versions. Other MCP server implementations may not provide this metadata structure.
|
||||
</Note>
|
||||
|
||||
### Version Information
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
When a server registers multiple versions of a resource, the metadata includes version information.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
|
||||
for resource in resources:
|
||||
if resource.meta:
|
||||
fastmcp_meta = resource.meta.get("fastmcp", {})
|
||||
version = fastmcp_meta.get("version")
|
||||
all_versions = fastmcp_meta.get("versions")
|
||||
if all_versions:
|
||||
print(f"{resource.uri}: v{version} (available: {all_versions})")
|
||||
```
|
||||
|
||||
To read a specific version, use the `version` parameter:
|
||||
|
||||
```python
|
||||
# Read a specific version
|
||||
content = await client.read_resource("data://config", version="1.0")
|
||||
```
|
||||
Resources are data sources exposed by MCP servers. They can be static files with fixed content, or dynamic templates that generate content based on parameters in the URI.
|
||||
|
||||
## Reading Resources
|
||||
|
||||
### Static Resources
|
||||
|
||||
Read a static resource using its URI:
|
||||
Read a resource using its URI:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Read a static resource
|
||||
content = await client.read_resource("file:///path/to/README.md")
|
||||
# content -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
|
||||
|
||||
# content -> list[TextResourceContents | BlobResourceContents]
|
||||
|
||||
# Access text content
|
||||
if hasattr(content[0], 'text'):
|
||||
print(content[0].text)
|
||||
|
||||
|
||||
# Access binary content
|
||||
if hasattr(content[0], 'blob'):
|
||||
print(f"Binary data: {len(content[0].blob)} bytes")
|
||||
```
|
||||
|
||||
### Resource Templates
|
||||
|
||||
Read from a resource template by providing the URI with parameters:
|
||||
Resource templates generate content based on URI parameters. The template defines a pattern like `weather://{{city}}/current`, and you fill in the parameters when reading:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Read a resource generated from a template
|
||||
# For example, a template like "weather://{{city}}/current"
|
||||
# Read from a resource template
|
||||
weather_content = await client.read_resource("weather://london/current")
|
||||
|
||||
# Access the generated content
|
||||
print(weather_content[0].text) # Assuming text JSON response
|
||||
print(weather_content[0].text)
|
||||
```
|
||||
|
||||
## Content Types
|
||||
|
||||
Resources can return different content types:
|
||||
Resources return different content types depending on what they expose.
|
||||
|
||||
### Text Resources
|
||||
Text resources include configuration files, JSON data, and other human-readable content:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
content = await client.read_resource("resource://config/settings.json")
|
||||
|
||||
|
||||
for item in content:
|
||||
if hasattr(item, 'text'):
|
||||
print(f"Text content: {item.text}")
|
||||
print(f"MIME type: {item.mimeType}")
|
||||
```
|
||||
|
||||
### Binary Resources
|
||||
Binary resources include images, PDFs, and other non-text data:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
content = await client.read_resource("resource://images/logo.png")
|
||||
|
||||
|
||||
for item in content:
|
||||
if hasattr(item, 'blob'):
|
||||
print(f"Binary content: {len(item.blob)} bytes")
|
||||
print(f"MIME type: {item.mimeType}")
|
||||
|
||||
|
||||
# Save to file
|
||||
with open("downloaded_logo.png", "wb") as f:
|
||||
f.write(item.blob)
|
||||
```
|
||||
|
||||
## Working with Multi-Server Clients
|
||||
## Multi-Server Clients
|
||||
|
||||
When using multi-server clients, resource URIs are automatically prefixed with the server name:
|
||||
When using multi-server clients, resource URIs are prefixed with the server name:
|
||||
|
||||
```python
|
||||
async with client: # Multi-server client
|
||||
# Access resources from different servers
|
||||
weather_icons = await client.read_resource("weather://weather/icons/sunny")
|
||||
templates = await client.read_resource("resource://assistant/templates/list")
|
||||
|
||||
print(f"Weather icon: {weather_icons[0].blob}")
|
||||
print(f"Templates: {templates[0].text}")
|
||||
```
|
||||
|
||||
## Raw MCP Protocol Access
|
||||
## Version Selection
|
||||
|
||||
For access to the complete MCP protocol objects, use the `*_mcp` methods:
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
When a server exposes multiple versions of a resource, you can request a specific version:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Raw MCP methods return full protocol objects
|
||||
resources_result = await client.list_resources_mcp()
|
||||
# resources_result -> mcp.types.ListResourcesResult
|
||||
|
||||
templates_result = await client.list_resource_templates_mcp()
|
||||
# templates_result -> mcp.types.ListResourceTemplatesResult
|
||||
|
||||
content_result = await client.read_resource_mcp("resource://example")
|
||||
# content_result -> mcp.types.ReadResourceResult
|
||||
# Read the highest version (default)
|
||||
content = await client.read_resource("data://config")
|
||||
|
||||
# Read a specific version
|
||||
content_v1 = await client.read_resource("data://config", version="1.0")
|
||||
```
|
||||
|
||||
## Common Resource URI Patterns
|
||||
See [Metadata](/servers/versioning#version-discovery) for how to discover available versions.
|
||||
|
||||
Different MCP servers may use various URI schemes:
|
||||
## Raw Protocol Access
|
||||
|
||||
For complete control, use `read_resource_mcp()` which returns the full MCP protocol object:
|
||||
|
||||
```python
|
||||
# File system resources
|
||||
"file:///path/to/file.txt"
|
||||
|
||||
# Custom protocol resources
|
||||
"weather://london/current"
|
||||
"database://users/123"
|
||||
|
||||
# Generic resource protocol
|
||||
"resource://config/settings"
|
||||
"resource://templates/email"
|
||||
async with client:
|
||||
result = await client.read_resource_mcp("resource://example")
|
||||
# result -> mcp.types.ReadResourceResult
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Resource URIs and their formats depend on the specific MCP server implementation. Check the server's documentation for available resources and their URI patterns.
|
||||
</Tip>
|
||||
|
|
@ -9,23 +9,28 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Roots are a way for clients to inform servers about the resources they have access to. Servers can use this information to adjust behavior or provide more relevant responses.
|
||||
Use this when you need to tell servers what local resources the client has access to.
|
||||
|
||||
## Setting Static Roots
|
||||
Roots inform servers about resources the client can provide. Servers can use this information to adjust behavior or provide more relevant responses.
|
||||
|
||||
## Static Roots
|
||||
|
||||
Provide a list of roots when creating the client:
|
||||
|
||||
<CodeGroup>
|
||||
```python Static Roots
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
"my_mcp_server.py",
|
||||
roots=["/path/to/root1", "/path/to/root2"]
|
||||
)
|
||||
```
|
||||
|
||||
```python Dynamic Roots Callback
|
||||
## Dynamic Roots
|
||||
|
||||
Use a callback to compute roots dynamically when the server requests them:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.roots import RequestContext
|
||||
|
||||
|
|
@ -34,9 +39,7 @@ async def roots_callback(context: RequestContext) -> list[str]:
|
|||
return ["/path/to/root1", "/path/to/root2"]
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
"my_mcp_server.py",
|
||||
roots=roots_callback
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: LLM Sampling
|
||||
sidebarTitle: Sampling
|
||||
description: Handle server-initiated LLM sampling requests.
|
||||
description: Handle server-initiated LLM completion requests.
|
||||
icon: robot
|
||||
---
|
||||
|
||||
|
|
@ -9,137 +9,32 @@ import { VersionBadge } from "/snippets/version-badge.mdx";
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
MCP servers can request LLM completions from clients. The client handles these requests through a sampling handler callback.
|
||||
Use this when you need to respond to server requests for LLM completions.
|
||||
|
||||
## Sampling Handler
|
||||
MCP servers can request LLM completions from clients during tool execution. This enables servers to delegate AI reasoning to the client, which controls which LLM is used and how requests are made.
|
||||
|
||||
Provide a `sampling_handler` function when creating the client:
|
||||
## Handler Template
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling import (
|
||||
SamplingMessage,
|
||||
SamplingParams,
|
||||
RequestContext,
|
||||
)
|
||||
from fastmcp.client.sampling import SamplingMessage, SamplingParams, RequestContext
|
||||
|
||||
async def sampling_handler(
|
||||
messages: list[SamplingMessage],
|
||||
params: SamplingParams,
|
||||
context: RequestContext
|
||||
) -> str:
|
||||
# Your LLM integration logic here
|
||||
# Extract text from messages and generate a response
|
||||
return "Generated response based on the messages"
|
||||
"""
|
||||
Handle server requests for LLM completions.
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=sampling_handler,
|
||||
)
|
||||
```
|
||||
Args:
|
||||
messages: Conversation messages to send to the LLM
|
||||
params: Sampling parameters (temperature, max_tokens, etc.)
|
||||
context: Request context with metadata
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The sampling handler receives three parameters:
|
||||
|
||||
<Card icon="code" title="Sampling Handler Parameters">
|
||||
<ResponseField name="SamplingMessage" type="Sampling Message Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="role" type='Literal["user", "assistant"]'>
|
||||
The role of the message.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="content" type="TextContent | ImageContent | AudioContent">
|
||||
The content of the message.
|
||||
|
||||
TextContent is most common, and has a `.text` attribute.
|
||||
</ResponseField>
|
||||
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
<ResponseField name="SamplingParams" type="Sampling Parameters Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="messages" type="list[SamplingMessage]">
|
||||
The messages to sample from
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="modelPreferences" type="ModelPreferences | None">
|
||||
The server's preferences for which model to select. The client MAY ignore
|
||||
these preferences.
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="hints" type="list[ModelHint] | None">
|
||||
The hints to use for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="costPriority" type="float | None">
|
||||
The cost priority for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="speedPriority" type="float | None">
|
||||
The speed priority for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="intelligencePriority" type="float | None">
|
||||
The intelligence priority for model selection.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="systemPrompt" type="str | None">
|
||||
An optional system prompt the server wants to use for sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="includeContext" type="IncludeContext | None">
|
||||
A request to include context from one or more MCP servers (including the caller), to
|
||||
be attached to the prompt.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="temperature" type="float | None">
|
||||
The sampling temperature.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="maxTokens" type="int">
|
||||
The maximum number of tokens to sample.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="stopSequences" type="list[str] | None">
|
||||
The stop sequences to use for sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="metadata" type="dict[str, Any] | None">
|
||||
Optional metadata to pass through to the LLM provider.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="tools" type="list[Tool] | None">
|
||||
Optional list of tools the LLM can use during sampling. See [Using the OpenAI Handler](#using-the-openai-handler).
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="toolChoice" type="ToolChoice | None">
|
||||
Optional control over tool usage behavior (`auto`, `required`, or `none`).
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
|
||||
</ResponseField>
|
||||
<ResponseField name="RequestContext" type="Request Context Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="request_id" type="RequestId">
|
||||
Unique identifier for the MCP request
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Basic Example
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling import SamplingMessage, SamplingParams, RequestContext
|
||||
|
||||
async def basic_sampling_handler(
|
||||
messages: list[SamplingMessage],
|
||||
params: SamplingParams,
|
||||
context: RequestContext
|
||||
) -> str:
|
||||
Returns:
|
||||
Generated text response from your LLM
|
||||
"""
|
||||
# Extract message content
|
||||
conversation = []
|
||||
for message in messages:
|
||||
|
|
@ -149,44 +44,65 @@ async def basic_sampling_handler(
|
|||
# Use the system prompt if provided
|
||||
system_prompt = params.systemPrompt or "You are a helpful assistant."
|
||||
|
||||
# Here you would integrate with your preferred LLM service
|
||||
# This is just a placeholder response
|
||||
return f"Response based on conversation: {' | '.join(conversation)}"
|
||||
# Integrate with your LLM service here
|
||||
return "Generated response based on the messages"
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=basic_sampling_handler
|
||||
sampling_handler=sampling_handler,
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
If the client doesn't provide a sampling handler, servers can optionally configure a fallback handler. See [Server Sampling](/servers/sampling#sampling-fallback-handler) for details.
|
||||
</Note>
|
||||
## Handler Parameters
|
||||
|
||||
## Sampling Capabilities
|
||||
<Card icon="code" title="SamplingMessage">
|
||||
<ResponseField name="role" type='Literal["user", "assistant"]'>
|
||||
The role of the message
|
||||
</ResponseField>
|
||||
|
||||
When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support (for simpler handlers that don't support tools), pass `sampling_capabilities` explicitly:
|
||||
<ResponseField name="content" type="TextContent | ImageContent | AudioContent">
|
||||
The content of the message. TextContent has a `.text` attribute.
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingCapability
|
||||
<Card icon="code" title="SamplingParams">
|
||||
<ResponseField name="systemPrompt" type="str | None">
|
||||
Optional system prompt the server wants to use
|
||||
</ResponseField>
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=basic_handler,
|
||||
sampling_capabilities=SamplingCapability(), # No tool support
|
||||
)
|
||||
```
|
||||
<ResponseField name="modelPreferences" type="ModelPreferences | None">
|
||||
Server preferences for model selection (hints, cost/speed/intelligence priorities)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="temperature" type="float | None">
|
||||
Sampling temperature
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="maxTokens" type="int">
|
||||
Maximum tokens to generate
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="stopSequences" type="list[str] | None">
|
||||
Stop sequences for sampling
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="tools" type="list[Tool] | None">
|
||||
Tools the LLM can use during sampling
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="toolChoice" type="ToolChoice | None">
|
||||
Tool usage behavior (`auto`, `required`, or `none`)
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Built-in Handlers
|
||||
|
||||
FastMCP provides built-in sampling handlers for OpenAI and Anthropic APIs. These handlers support the full sampling API including tool use, handling message conversion and response formatting automatically.
|
||||
FastMCP provides built-in handlers for OpenAI and Anthropic APIs that support the full sampling API including tool use.
|
||||
|
||||
### OpenAI Handler
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
The OpenAI handler works with OpenAI's API and any OpenAI-compatible provider:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler
|
||||
|
|
@ -197,7 +113,7 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
For OpenAI-compatible APIs (like local models), pass a custom client:
|
||||
For OpenAI-compatible APIs (like local models):
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
|
|
@ -219,8 +135,6 @@ Install the OpenAI handler with `pip install fastmcp[openai]`.
|
|||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
The Anthropic handler uses Claude models via the Anthropic API:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler
|
||||
|
|
@ -231,28 +145,28 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
You can pass a custom client for advanced configuration:
|
||||
|
||||
```python
|
||||
from anthropic import AsyncAnthropic
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=AnthropicSamplingHandler(
|
||||
default_model="claude-sonnet-4-5",
|
||||
client=AsyncAnthropic(), # Uses ANTHROPIC_API_KEY env var
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Install the Anthropic handler with `pip install fastmcp[anthropic]`.
|
||||
</Note>
|
||||
|
||||
### Tool Execution
|
||||
## Sampling Capabilities
|
||||
|
||||
When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support for simpler handlers:
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingCapability
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=basic_handler,
|
||||
sampling_capabilities=SamplingCapability(), # No tool support
|
||||
)
|
||||
```
|
||||
|
||||
## Tool Execution
|
||||
|
||||
Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results.
|
||||
|
||||
<Tip>
|
||||
To implement a custom sampling handler, see the [handler source code](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/client/sampling/handlers) as a reference.
|
||||
</Tip>
|
||||
</Tip>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Background Tasks
|
||||
sidebarTitle: Background Tasks
|
||||
description: Execute operations asynchronously and track their progress
|
||||
sidebarTitle: Tasks
|
||||
description: Execute operations asynchronously and track their progress.
|
||||
icon: clock
|
||||
tag: "NEW"
|
||||
---
|
||||
|
|
@ -10,13 +10,13 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|||
|
||||
<VersionBadge version="2.14.0" />
|
||||
|
||||
The [MCP task protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) lets you request operations to run asynchronously. This returns a Task object immediately, letting you track progress, cancel operations, or await results.
|
||||
Use this when you need to run long operations asynchronously while doing other work.
|
||||
|
||||
See [Server Background Tasks](/servers/tasks) for how to enable this on the server side.
|
||||
The MCP task protocol lets you request operations to run in the background. The call returns a Task object immediately, letting you track progress, cancel operations, or await results.
|
||||
|
||||
## Requesting Background Execution
|
||||
|
||||
Pass `task=True` to run an operation as a background task. The call returns immediately with a Task object while the work executes on the server.
|
||||
Pass `task=True` to run an operation as a background task:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
|
@ -41,24 +41,35 @@ resource_task = await client.read_resource("file://large.txt", task=True)
|
|||
prompt_task = await client.get_prompt("my_prompt", args, task=True)
|
||||
```
|
||||
|
||||
## Working with Task Objects
|
||||
## Task API
|
||||
|
||||
All task types share a common interface for retrieving results, checking status, and receiving updates.
|
||||
All task types share a common interface.
|
||||
|
||||
To get the result, call `await task.result()` or simply `await task`. This blocks until the task completes and returns the result. You can also check status without blocking using `await task.status()`, which returns the current state (`"working"`, `"completed"`, `"failed"`, or `"cancelled"`) along with any progress message from the server.
|
||||
### Getting Results
|
||||
|
||||
Call `await task.result()` or simply `await task` to block until the task completes:
|
||||
|
||||
```python
|
||||
task = await client.call_tool("analyze", {"text": "hello"}, task=True)
|
||||
|
||||
# Check current status (non-blocking)
|
||||
status = await task.status()
|
||||
print(f"{status.status}: {status.statusMessage}")
|
||||
|
||||
# Wait for result (blocking)
|
||||
result = await task.result()
|
||||
# or: result = await task
|
||||
```
|
||||
|
||||
For more control over waiting, use `task.wait()` with an optional timeout or target state:
|
||||
### Checking Status
|
||||
|
||||
Check the current status without blocking:
|
||||
|
||||
```python
|
||||
status = await task.status()
|
||||
print(f"{status.status}: {status.statusMessage}")
|
||||
# status.status is "working", "completed", "failed", or "cancelled"
|
||||
```
|
||||
|
||||
### Waiting with Control
|
||||
|
||||
Use `task.wait()` for more control over waiting:
|
||||
|
||||
```python
|
||||
# Wait up to 30 seconds for completion
|
||||
|
|
@ -68,11 +79,17 @@ status = await task.wait(timeout=30.0)
|
|||
status = await task.wait(state="completed", timeout=30.0)
|
||||
```
|
||||
|
||||
To cancel a running task, call `await task.cancel()`.
|
||||
### Cancellation
|
||||
|
||||
### Real-Time Status Updates
|
||||
Cancel a running task:
|
||||
|
||||
Register callbacks to receive status updates as the server reports progress. Both sync and async callbacks are supported.
|
||||
```python
|
||||
await task.cancel()
|
||||
```
|
||||
|
||||
## Status Updates
|
||||
|
||||
Register callbacks to receive real-time status updates as the server reports progress:
|
||||
|
||||
```python
|
||||
def on_status_change(status):
|
||||
|
|
@ -87,9 +104,34 @@ async def on_status_async(status):
|
|||
task.on_status_change(on_status_async)
|
||||
```
|
||||
|
||||
### Handler Template
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
def status_handler(status):
|
||||
"""
|
||||
Handle task status updates.
|
||||
|
||||
Args:
|
||||
status: Task status object with:
|
||||
- taskId: Unique task identifier
|
||||
- status: "working", "completed", "failed", or "cancelled"
|
||||
- statusMessage: Optional progress message from server
|
||||
"""
|
||||
if status.status == "working":
|
||||
print(f"Progress: {status.statusMessage}")
|
||||
elif status.status == "completed":
|
||||
print("Task completed")
|
||||
elif status.status == "failed":
|
||||
print(f"Task failed: {status.statusMessage}")
|
||||
|
||||
task.on_status_change(status_handler)
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline. The Task API provides a consistent interface either way.
|
||||
You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline.
|
||||
|
||||
```python
|
||||
task = await client.call_tool("my_tool", args, task=True)
|
||||
|
|
@ -103,9 +145,9 @@ else:
|
|||
result = await task.result()
|
||||
```
|
||||
|
||||
This means you can write task-aware client code without worrying about server capabilities.
|
||||
This lets you write task-aware client code without worrying about server capabilities.
|
||||
|
||||
## Complete Example
|
||||
## Example
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
|
@ -136,3 +178,5 @@ async def main():
|
|||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
See [Server Background Tasks](/servers/tasks) for how to enable background task support on the server side.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Tool Operations
|
||||
title: Calling Tools
|
||||
sidebarTitle: Tools
|
||||
description: Discover and execute server-side tools with the FastMCP client.
|
||||
description: Execute server-side tools and handle structured results.
|
||||
icon: wrench
|
||||
---
|
||||
|
||||
|
|
@ -9,88 +9,40 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Tools are executable functions exposed by MCP servers. The FastMCP client provides methods to discover available tools and execute them with arguments.
|
||||
Use this when you need to execute server-side functions and process their results.
|
||||
|
||||
## Discovering Tools
|
||||
Tools are executable functions exposed by MCP servers. The client's `call_tool()` method executes a tool by name with arguments and returns structured results.
|
||||
|
||||
Use `list_tools()` to retrieve all tools available on the server. When the server paginates results, the client automatically fetches all pages and returns the complete list.
|
||||
## Basic Execution
|
||||
|
||||
```python
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
# tools -> list[mcp.types.Tool]
|
||||
|
||||
for tool in tools:
|
||||
print(f"Tool: {tool.name}")
|
||||
print(f"Description: {tool.description}")
|
||||
if tool.inputSchema:
|
||||
print(f"Parameters: {tool.inputSchema}")
|
||||
# Access tags and other metadata
|
||||
if tool.meta:
|
||||
fastmcp_meta = tool.meta.get('fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
For manual pagination control, use `list_tools_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details.
|
||||
|
||||
### Filtering by Tags
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
You can use the `meta` field to filter tools based on their tags:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
|
||||
# Filter tools by tag
|
||||
analysis_tools = [
|
||||
tool for tool in tools
|
||||
if tool.meta and
|
||||
tool.meta.get('fastmcp', {}) and
|
||||
'analysis' in tool.meta.get('fastmcp', {}).get('tags', [])
|
||||
]
|
||||
|
||||
print(f"Found {len(analysis_tools)} analysis tools")
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. Component versions are also included in the metadata when available (e.g., `meta.fastmcp.version`). Other MCP server implementations may not provide this metadata structure.
|
||||
</Note>
|
||||
|
||||
## Executing Tools
|
||||
|
||||
### Basic Execution
|
||||
|
||||
Execute a tool using `call_tool()` with the tool name and arguments:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Simple tool call
|
||||
result = await client.call_tool("add", {"a": 5, "b": 3})
|
||||
# result -> CallToolResult with structured and unstructured data
|
||||
|
||||
|
||||
# Access structured data (automatically deserialized)
|
||||
print(result.data) # 8 (int) or {"result": 8} for primitive types
|
||||
|
||||
# Access traditional content blocks
|
||||
print(result.content[0].text) # "8" (TextContent)
|
||||
print(result.data) # 8
|
||||
|
||||
# Access traditional content blocks
|
||||
print(result.content[0].text) # "8"
|
||||
```
|
||||
|
||||
### Advanced Execution Options
|
||||
Arguments are passed as a dictionary. For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server).
|
||||
|
||||
The `call_tool()` method supports additional parameters for timeout control and progress monitoring:
|
||||
## Execution Options
|
||||
|
||||
The `call_tool()` method supports timeout control and progress monitoring:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# With timeout (aborts if execution takes longer than 2 seconds)
|
||||
result = await client.call_tool(
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
timeout=2.0
|
||||
)
|
||||
|
||||
# With progress handler (to track execution progress)
|
||||
|
||||
# With progress handler
|
||||
result = await client.call_tool(
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
|
|
@ -98,19 +50,103 @@ async with client:
|
|||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name`: The tool name (string)
|
||||
- `arguments`: Dictionary of arguments to pass to the tool (optional)
|
||||
- `version`: Specific tool version to call (optional, see [Version Selection](#version-selection) below)
|
||||
- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout)
|
||||
- `progress_handler`: Progress callback function (optional, overrides client-level handler)
|
||||
- `meta`: Dictionary of metadata to send with the request (optional, see below)
|
||||
## Structured Results
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
Tool execution returns a `CallToolResult` object. The `.data` property provides fully hydrated Python objects including complex types like datetimes and UUIDs, reconstructed from the server's output schema.
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
async with client:
|
||||
result = await client.call_tool("get_weather", {"city": "London"})
|
||||
|
||||
# FastMCP reconstructs complete Python objects
|
||||
weather = result.data
|
||||
print(f"Temperature: {weather.temperature}C at {weather.timestamp}")
|
||||
|
||||
# Complex types are properly deserialized
|
||||
assert isinstance(weather.timestamp, datetime)
|
||||
assert isinstance(weather.station_id, UUID)
|
||||
|
||||
# Raw structured JSON is also available
|
||||
print(f"Raw JSON: {result.structured_content}")
|
||||
```
|
||||
|
||||
<Card icon="code" title="CallToolResult Properties">
|
||||
<ResponseField name=".data" type="Any">
|
||||
Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). FastMCP exclusive.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".content" type="list[mcp.types.ContentBlock]">
|
||||
Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.).
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".structured_content" type="dict[str, Any] | None">
|
||||
Standard MCP structured JSON data as sent by the server.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".is_error" type="bool">
|
||||
Boolean indicating if the tool execution failed.
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
For tools without output schemas or when deserialization fails, `.data` will be `None`. Fall back to content blocks in that case:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("legacy_tool", {"param": "value"})
|
||||
|
||||
if result.data is not None:
|
||||
print(f"Structured: {result.data}")
|
||||
else:
|
||||
for content in result.content:
|
||||
if hasattr(content, 'text'):
|
||||
print(f"Text result: {content.text}")
|
||||
```
|
||||
|
||||
<Tip>
|
||||
FastMCP servers automatically wrap primitive results (like `int`, `str`, `bool`) in a `{"result": value}` structure. FastMCP clients automatically unwrap this, so you get the original value in `.data`.
|
||||
</Tip>
|
||||
|
||||
## Error Handling
|
||||
|
||||
By default, `call_tool()` raises a `ToolError` if the tool execution fails:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
async with client:
|
||||
try:
|
||||
result = await client.call_tool("potentially_failing_tool", {"param": "value"})
|
||||
print("Tool succeeded:", result.data)
|
||||
except ToolError as e:
|
||||
print(f"Tool failed: {e}")
|
||||
```
|
||||
|
||||
To handle errors manually instead of catching exceptions, disable automatic error raising:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
"potentially_failing_tool",
|
||||
{"param": "value"},
|
||||
raise_on_error=False
|
||||
)
|
||||
|
||||
if result.is_error:
|
||||
print(f"Tool failed: {result.content[0].text}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.data}")
|
||||
```
|
||||
|
||||
## Sending Metadata
|
||||
|
||||
<VersionBadge version="2.13.1" />
|
||||
|
||||
The `meta` parameter sends ancillary information alongside tool calls. This can be used for various purposes like observability, debugging, client identification, or any context the server may need beyond the tool's primary arguments.
|
||||
The `meta` parameter sends ancillary information alongside tool calls for observability, debugging, or client identification:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
|
|
@ -128,215 +164,20 @@ async with client:
|
|||
)
|
||||
```
|
||||
|
||||
The structure and usage of `meta` is determined by your application. See [Client Metadata](/servers/context#client-metadata) in the server documentation to learn how to access this data in your tool implementations.
|
||||
See [Client Metadata](/servers/context#client-metadata) to learn how servers access this data.
|
||||
|
||||
## Handling Results
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes.
|
||||
|
||||
### CallToolResult Properties
|
||||
|
||||
<Card icon="code" title="CallToolResult Properties">
|
||||
<ResponseField name=".data" type="Any">
|
||||
**FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".content" type="list[mcp.types.ContentBlock]">
|
||||
Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.) available from all MCP servers.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".structured_content" type="dict[str, Any] | None">
|
||||
Standard MCP structured JSON data as sent by the server, available from all MCP servers that support structured outputs.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".is_error" type="bool">
|
||||
Boolean indicating if the tool execution failed.
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
### Structured Data Access
|
||||
|
||||
FastMCP's `.data` property provides fully hydrated Python objects, not just JSON dictionaries. This includes complex type reconstruction:
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
async with client:
|
||||
result = await client.call_tool("get_weather", {"city": "London"})
|
||||
|
||||
# FastMCP reconstructs complete Python objects from the server's output schema
|
||||
weather = result.data # Server-defined WeatherReport object
|
||||
print(f"Temperature: {weather.temperature}°C at {weather.timestamp}")
|
||||
print(f"Station: {weather.station_id}")
|
||||
print(f"Humidity: {weather.humidity}%")
|
||||
|
||||
# The timestamp is a real datetime object, not a string!
|
||||
assert isinstance(weather.timestamp, datetime)
|
||||
assert isinstance(weather.station_id, UUID)
|
||||
|
||||
# Compare with raw structured JSON (standard MCP)
|
||||
print(f"Raw JSON: {result.structured_content}")
|
||||
# {"temperature": 20, "timestamp": "2024-01-15T14:30:00Z", "station_id": "123e4567-..."}
|
||||
|
||||
# Traditional content blocks (standard MCP)
|
||||
print(f"Text content: {result.content[0].text}")
|
||||
```
|
||||
|
||||
### Fallback Behavior
|
||||
|
||||
For tools without output schemas or when deserialization fails, `.data` will be `None`:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("legacy_tool", {"param": "value"})
|
||||
|
||||
if result.data is not None:
|
||||
# Structured output available and successfully deserialized
|
||||
print(f"Structured: {result.data}")
|
||||
else:
|
||||
# No structured output or deserialization failed - use content blocks
|
||||
for content in result.content:
|
||||
if hasattr(content, 'text'):
|
||||
print(f"Text result: {content.text}")
|
||||
elif hasattr(content, 'data'):
|
||||
print(f"Binary data: {len(content.data)} bytes")
|
||||
```
|
||||
|
||||
### Primitive Type Unwrapping
|
||||
|
||||
<Tip>
|
||||
FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object.
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("calculate_sum", {"a": 5, "b": 3})
|
||||
|
||||
# FastMCP client automatically unwraps for convenience
|
||||
print(result.data) # 8 (int) - the original value
|
||||
|
||||
# Raw structured content shows the server-side wrapping
|
||||
print(result.structured_content) # {"result": 8}
|
||||
|
||||
# Other MCP clients would need to manually access ["result"]
|
||||
# value = result.structured_content["result"] # Not needed with FastMCP!
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exception-Based Error Handling
|
||||
|
||||
By default, `call_tool()` raises a `ToolError` if the tool execution fails:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
async with client:
|
||||
try:
|
||||
result = await client.call_tool("potentially_failing_tool", {"param": "value"})
|
||||
print("Tool succeeded:", result.data)
|
||||
except ToolError as e:
|
||||
print(f"Tool failed: {e}")
|
||||
```
|
||||
|
||||
### Manual Error Checking
|
||||
|
||||
You can disable automatic error raising and manually check the result:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
"potentially_failing_tool",
|
||||
{"param": "value"},
|
||||
raise_on_error=False
|
||||
)
|
||||
|
||||
if result.is_error:
|
||||
print(f"Tool failed: {result.content[0].text}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.data}")
|
||||
```
|
||||
|
||||
### Raw MCP Protocol Access
|
||||
## Raw Protocol Access
|
||||
|
||||
For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool_mcp("potentially_failing_tool", {"param": "value"})
|
||||
result = await client.call_tool_mcp("my_tool", {"param": "value"})
|
||||
# result -> mcp.types.CallToolResult
|
||||
|
||||
|
||||
if result.isError:
|
||||
print(f"Tool failed: {result.content}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.content}")
|
||||
# Note: No automatic deserialization with call_tool_mcp()
|
||||
```
|
||||
|
||||
## Argument Handling
|
||||
|
||||
Arguments are passed as a dictionary to the tool:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Simple arguments
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
|
||||
# Complex arguments
|
||||
result = await client.call_tool("process_data", {
|
||||
"config": {"format": "json", "validate": True},
|
||||
"items": [1, 2, 3, 4, 5],
|
||||
"metadata": {"source": "api", "version": "1.0"}
|
||||
})
|
||||
```
|
||||
|
||||
<Tip>
|
||||
For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server).
|
||||
</Tip>
|
||||
|
||||
## Version Selection
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
FastMCP servers can expose multiple versions of the same tool. By default, clients receive and call the highest version, but you can request a specific version when needed.
|
||||
|
||||
### Discovering Versions
|
||||
|
||||
When a server registers multiple versions of a tool, the `list_tools()` response includes version information in the metadata. The `meta.fastmcp.version` field shows which version is being returned, while `meta.fastmcp.versions` lists all available versions sorted from highest to lowest.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
|
||||
for tool in tools:
|
||||
if tool.meta:
|
||||
fastmcp_meta = tool.meta.get("fastmcp", {})
|
||||
version = fastmcp_meta.get("version")
|
||||
all_versions = fastmcp_meta.get("versions")
|
||||
if all_versions:
|
||||
print(f"{tool.name}: v{version} (available: {all_versions})")
|
||||
```
|
||||
|
||||
Unversioned tools omit these metadata fields entirely.
|
||||
|
||||
### Calling Specific Versions
|
||||
|
||||
Pass the `version` parameter to `call_tool()` to execute a specific version instead of the highest.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Call the highest version (default)
|
||||
result = await client.call_tool("calculate", {"x": 1, "y": 2})
|
||||
|
||||
# Call version 1.0 specifically
|
||||
result_v1 = await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0")
|
||||
```
|
||||
|
||||
If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version.
|
||||
|
||||
<Note>
|
||||
Version selection is a FastMCP extension to the MCP protocol. See [Versioning](/servers/versioning#requesting-specific-versions) for details on how this works at the protocol level for non-FastMCP clients.
|
||||
</Note>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Client Transports
|
||||
sidebarTitle: Transports
|
||||
description: Configure how FastMCP Clients connect to and communicate with servers.
|
||||
description: Configure how clients connect to and communicate with MCP servers.
|
||||
icon: link
|
||||
---
|
||||
|
||||
|
|
@ -9,88 +9,30 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
The FastMCP `Client` communicates with MCP servers through transport objects that handle the underlying connection mechanics. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration—environment variables, authentication, session management, and more.
|
||||
|
||||
Think of transports as configurable adapters between your client code and MCP servers. Each transport type handles a different communication pattern: subprocesses with pipes, HTTP connections, or direct in-memory calls.
|
||||
|
||||
## Choosing the Right Transport
|
||||
|
||||
- **Use [STDIO Transport](#stdio-transport)** when you need to run local MCP servers with full control over their environment and lifecycle
|
||||
- **Use [Remote Transports](#remote-transports)** when connecting to production services or shared MCP servers running independently
|
||||
- **Use [In-Memory Transport](#in-memory-transport)** for testing FastMCP servers without subprocess or network overhead
|
||||
- **Use [MCP JSON Configuration](#mcp-json-configuration-transport)** when you need to connect to multiple servers defined in configuration files
|
||||
Transports handle the underlying connection between your client and MCP servers. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration.
|
||||
|
||||
## STDIO Transport
|
||||
|
||||
STDIO (Standard Input/Output) transport communicates with MCP servers through subprocess pipes. This is the standard mechanism used by desktop clients like Claude Desktop and is the primary way to run local MCP servers.
|
||||
|
||||
### The Client Runs the Server
|
||||
STDIO transport communicates with MCP servers through subprocess pipes. When using STDIO, your client launches and manages the server process, controlling its lifecycle and environment.
|
||||
|
||||
<Warning>
|
||||
**Critical Concept**: When using STDIO transport, your client actually launches and manages the server process. This is fundamentally different from network transports where you connect to an already-running server. Understanding this relationship is key to using STDIO effectively.
|
||||
STDIO servers run in isolated environments by default. They do not inherit your shell's environment variables. You must explicitly pass any configuration the server needs.
|
||||
</Warning>
|
||||
|
||||
With STDIO transport, your client:
|
||||
- Starts the server as a subprocess when you connect
|
||||
- Manages the server's lifecycle (start, stop, restart)
|
||||
- Controls the server's environment and configuration
|
||||
- Communicates through stdin/stdout pipes
|
||||
|
||||
This architecture enables powerful local integrations but requires understanding environment isolation and process management.
|
||||
|
||||
### Environment Isolation
|
||||
|
||||
STDIO servers run in isolated environments by default. This is a security feature enforced by the MCP protocol to prevent accidental exposure of sensitive data.
|
||||
|
||||
When your client launches an MCP server:
|
||||
- The server does NOT inherit your shell's environment variables
|
||||
- API keys, paths, and other configuration must be explicitly passed
|
||||
- The working directory and system paths may differ from your shell
|
||||
|
||||
To pass environment variables to your server, use the `env` parameter:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# If your server needs environment variables (like API keys),
|
||||
# you must explicitly pass them:
|
||||
client = Client(
|
||||
"my_server.py",
|
||||
env={"API_KEY": "secret", "DEBUG": "true"}
|
||||
)
|
||||
|
||||
# This won't work - the server runs in isolation:
|
||||
# export API_KEY="secret" # in your shell
|
||||
# client = Client("my_server.py") # server can't see API_KEY
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
To use STDIO transport, you create a transport instance with the command and arguments needed to run your server:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["my_server.py"]
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
You can configure additional settings like environment variables, working directory, or command arguments:
|
||||
|
||||
```python
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["my_server.py", "--verbose"],
|
||||
env={"LOG_LEVEL": "DEBUG"},
|
||||
env={"API_KEY": "secret", "LOG_LEVEL": "DEBUG"},
|
||||
cwd="/path/to/server"
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
For convenience, the client can also infer STDIO transport from file paths, but this doesn't allow configuration:
|
||||
For convenience, the client can infer STDIO transport from file paths, though this limits configuration options:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
|
@ -100,26 +42,18 @@ client = Client("my_server.py") # Limited - no configuration options
|
|||
|
||||
### Environment Variables
|
||||
|
||||
Since STDIO servers don't inherit your environment, you need strategies for passing configuration. Here are two common approaches:
|
||||
Since STDIO servers do not inherit your environment, you need strategies for passing configuration.
|
||||
|
||||
**Selective forwarding** passes only the variables your server actually needs:
|
||||
**Selective forwarding** passes only the variables your server needs:
|
||||
|
||||
```python
|
||||
import os
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
required_vars = ["API_KEY", "DATABASE_URL", "REDIS_HOST"]
|
||||
env = {
|
||||
var: os.environ[var]
|
||||
for var in required_vars
|
||||
if var in os.environ
|
||||
}
|
||||
env = {var: os.environ[var] for var in required_vars if var in os.environ}
|
||||
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
env=env
|
||||
)
|
||||
transport = StdioTransport(command="python", args=["server.py"], env=env)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
|
|
@ -130,33 +64,24 @@ from dotenv import dotenv_values
|
|||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
env = dotenv_values(".env")
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
env=env
|
||||
)
|
||||
transport = StdioTransport(command="python", args=["server.py"], env=env)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
### Session Persistence
|
||||
|
||||
STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This improves performance by reusing the same subprocess for multiple connections, but can be controlled when you need isolation.
|
||||
|
||||
By default, the subprocess persists between connections:
|
||||
STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This reuses the same subprocess for multiple connections, improving performance.
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"]
|
||||
)
|
||||
transport = StdioTransport(command="python", args=["server.py"])
|
||||
client = Client(transport)
|
||||
|
||||
async def efficient_multiple_operations():
|
||||
async with client:
|
||||
await client.ping()
|
||||
|
||||
|
||||
async with client: # Reuses the same subprocess
|
||||
await client.call_tool("process_data", {"file": "data.csv"})
|
||||
```
|
||||
|
|
@ -164,51 +89,19 @@ async def efficient_multiple_operations():
|
|||
For complete isolation between connections, disable session persistence:
|
||||
|
||||
```python
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
keep_alive=False
|
||||
)
|
||||
client = Client(transport)
|
||||
transport = StdioTransport(command="python", args=["server.py"], keep_alive=False)
|
||||
```
|
||||
|
||||
Use `keep_alive=False` when you need complete isolation (e.g., in test suites) or when server state could cause issues between connections.
|
||||
|
||||
### Specialized STDIO Transports
|
||||
|
||||
FastMCP provides convenience transports that are thin wrappers around `StdioTransport` with pre-configured commands:
|
||||
|
||||
- **`PythonStdioTransport`** - Uses `python` command for `.py` files
|
||||
- **`NodeStdioTransport`** - Uses `node` command for `.js` files
|
||||
- **`UvStdioTransport`** - Uses `uv` for Python packages (uses `env_vars` parameter)
|
||||
- **`UvxStdioTransport`** - Uses `uvx` for Python packages (uses `env_vars` parameter)
|
||||
- **`NpxStdioTransport`** - Uses `npx` for Node packages (uses `env_vars` parameter)
|
||||
|
||||
For most use cases, instantiate `StdioTransport` directly with your desired command. These specialized transports are primarily useful for client inference shortcuts.
|
||||
|
||||
## Remote Transports
|
||||
|
||||
Remote transports connect to MCP servers running as web services. This is a fundamentally different model from STDIO transports—instead of your client launching and managing a server process, you connect to an already-running service that manages its own environment and lifecycle.
|
||||
|
||||
### Streamable HTTP Transport
|
||||
## HTTP Transport
|
||||
|
||||
<VersionBadge version="2.3.0" />
|
||||
|
||||
Streamable HTTP is the recommended transport for production deployments, providing efficient bidirectional streaming over HTTP connections.
|
||||
|
||||
- **Class:** `StreamableHttpTransport`
|
||||
- **Server compatibility:** FastMCP servers running with `mcp run --transport http`
|
||||
|
||||
The transport requires a URL and optionally supports custom headers for authentication and configuration:
|
||||
HTTP transport connects to MCP servers running as web services. This is the recommended transport for production deployments.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
# Basic connection
|
||||
transport = StreamableHttpTransport(url="https://api.example.com/mcp")
|
||||
client = Client(transport)
|
||||
|
||||
# With custom headers for authentication
|
||||
transport = StreamableHttpTransport(
|
||||
url="https://api.example.com/mcp",
|
||||
headers={
|
||||
|
|
@ -219,9 +112,10 @@ transport = StreamableHttpTransport(
|
|||
client = Client(transport)
|
||||
```
|
||||
|
||||
For convenience, FastMCP also provides authentication helpers:
|
||||
FastMCP also provides authentication helpers:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.auth import BearerAuth
|
||||
|
||||
client = Client(
|
||||
|
|
@ -230,14 +124,9 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
### SSE Transport (Legacy)
|
||||
### SSE Transport
|
||||
|
||||
Server-Sent Events transport is maintained for backward compatibility but is superseded by Streamable HTTP for new deployments.
|
||||
|
||||
- **Class:** `SSETransport`
|
||||
- **Server compatibility:** FastMCP servers running with `mcp run --transport sse`
|
||||
|
||||
SSE transport supports the same configuration options as Streamable HTTP:
|
||||
Server-Sent Events transport is maintained for backward compatibility. Use Streamable HTTP for new deployments unless you have specific infrastructure requirements.
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import SSETransport
|
||||
|
|
@ -249,17 +138,9 @@ transport = SSETransport(
|
|||
client = Client(transport)
|
||||
```
|
||||
|
||||
Use Streamable HTTP for new deployments unless you have specific infrastructure requirements for SSE.
|
||||
|
||||
## In-Memory Transport
|
||||
|
||||
In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing and development.
|
||||
|
||||
- **Class:** `FastMCPTransport`
|
||||
|
||||
<Note>
|
||||
Unlike STDIO transports, in-memory servers have full access to your Python process's environment. They share the same memory space and environment variables as your client code—no isolation or explicit environment passing required.
|
||||
</Note>
|
||||
In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Client
|
||||
|
|
@ -278,15 +159,19 @@ async with client:
|
|||
result = await client.call_tool("greet", {"name": "World"})
|
||||
```
|
||||
|
||||
## MCP JSON Configuration Transport
|
||||
<Note>
|
||||
Unlike STDIO transports, in-memory servers share the same memory space and environment variables as your client code.
|
||||
</Note>
|
||||
|
||||
## Multi-Server Configuration
|
||||
|
||||
<VersionBadge version="2.4.0" />
|
||||
|
||||
This transport supports the emerging MCP JSON configuration standard for defining multiple servers:
|
||||
|
||||
- **Class:** `MCPConfigTransport`
|
||||
Connect to multiple servers defined in a configuration dictionary:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
|
|
@ -309,9 +194,9 @@ async with client:
|
|||
answer = await client.call_tool("assistant_ask", {"question": "What?"})
|
||||
```
|
||||
|
||||
### Tool Transformation with FastMCP and MCPConfig
|
||||
### Tool Transformations
|
||||
|
||||
FastMCP supports basic tool transformations to be defined alongside the MCP Servers in the MCPConfig file.
|
||||
FastMCP supports tool transformations within the configuration. You can change names, descriptions, tags, and arguments for tools from a server.
|
||||
|
||||
```python
|
||||
config = {
|
||||
|
|
@ -319,65 +204,32 @@ config = {
|
|||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http",
|
||||
"tools": { } # <--- This is the tool transformation section
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With these transformations, you can transform (change) the name, title, description, tags, enablement, and arguments of a tool.
|
||||
|
||||
For each argument the tool takes, you can transform (change) the name, description, default, visibility, whether it's required, and you can provide example values.
|
||||
|
||||
In the following example, we're transforming the `weather_get_forecast` tool to only retrieve the weather for `Miami` and hiding the `city` argument from the client.
|
||||
|
||||
```python
|
||||
tool_transformations = {
|
||||
"weather_get_forecast": {
|
||||
"name": "miami_weather",
|
||||
"description": "Get the weather for Miami",
|
||||
"arguments": {
|
||||
"city": {
|
||||
"name": "city",
|
||||
"default": "Miami",
|
||||
"hide": True,
|
||||
"tools": {
|
||||
"weather_get_forecast": {
|
||||
"name": "miami_weather",
|
||||
"description": "Get the weather for Miami",
|
||||
"arguments": {
|
||||
"city": {
|
||||
"default": "Miami",
|
||||
"hide": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To filter tools by tag, use `include_tags` or `exclude_tags` at the server level:
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http",
|
||||
"tools": tool_transformations
|
||||
"include_tags": ["forecast"] # Only tools with this tag
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Allowlisting and Blocklisting Tools
|
||||
|
||||
Tools can be allowlisted or blocklisted from the client by applying `tags` to the tools on the server. In the following example, we're allowlisting only tools marked with the `forecast` tag, all other tools will be unavailable to the client.
|
||||
|
||||
```python
|
||||
tool_transformations = {
|
||||
"weather_get_forecast": {
|
||||
"enabled": True,
|
||||
"tags": ["forecast"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http",
|
||||
"tools": tool_transformations,
|
||||
"include_tags": ["forecast"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -17,4 +17,43 @@ h6 code:not(pre code) {
|
|||
background-color: rgba(247, 37, 133, 0.09);
|
||||
}
|
||||
|
||||
/* V2 banner - inside content-container, breaks out of padding with negative margins */
|
||||
#v2-banner {
|
||||
display: block;
|
||||
background: linear-gradient(135deg, #4cc9f0 0%, #2d00f7 100%);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 16px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
margin: -2rem -2rem 1.5rem -2rem;
|
||||
width: calc(100% + 4rem);
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
#v2-banner a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#v2-banner a:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
#v2-banner {
|
||||
margin: -3rem -4rem 1.5rem -4rem;
|
||||
width: calc(100% + 8rem);
|
||||
}
|
||||
}
|
||||
|
||||
.dark #v2-banner {
|
||||
background: linear-gradient(135deg, #2d00f7 0%, #4cc9f0 100%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: HTTP Deployment
|
|||
sidebarTitle: HTTP Deployment
|
||||
description: Deploy your FastMCP server over HTTP for remote access
|
||||
icon: server
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
|
|
|||
1142
docs/docs.json
1142
docs/docs.json
File diff suppressed because it is too large
Load diff
|
|
@ -47,7 +47,7 @@ You should see output like the following:
|
|||
```bash
|
||||
$ fastmcp version
|
||||
|
||||
FastMCP version: 2.11.3
|
||||
FastMCP version: 3.0.0
|
||||
MCP version: 1.12.4
|
||||
Python version: 3.12.2
|
||||
Platform: macOS-15.3.1-arm64-arm-64bit
|
||||
|
|
@ -69,7 +69,7 @@ Alternatively, wait for the stable v5 release. See [this issue](https://github.c
|
|||
</Info>
|
||||
## Upgrading from the Official MCP SDK
|
||||
|
||||
Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
|
||||
Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 3.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
|
||||
|
||||
|
||||
```python {5}
|
||||
|
|
@ -83,7 +83,7 @@ mcp = FastMCP("My MCP Server")
|
|||
```
|
||||
|
||||
<Warning>
|
||||
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
|
||||
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 3.x. Please refer to this documentation for details on new capabilities.
|
||||
</Warning>
|
||||
|
||||
## Versioning Policy
|
||||
|
|
@ -92,8 +92,8 @@ FastMCP follows semantic versioning with pragmatic adaptations for the rapidly e
|
|||
|
||||
For production use, always pin to exact versions:
|
||||
```
|
||||
fastmcp==2.11.0 # Good
|
||||
fastmcp>=2.11.0 # Bad - will install breaking changes
|
||||
fastmcp==3.0.0 # Good
|
||||
fastmcp>=3.0.0 # Bad - may install breaking changes
|
||||
```
|
||||
|
||||
See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
---
|
||||
title: "Welcome to FastMCP 2.0!"
|
||||
title: "Welcome to FastMCP 3.0!"
|
||||
sidebarTitle: "Welcome!"
|
||||
description: The fast, Pythonic way to build MCP servers and clients.
|
||||
icon: hand-wave
|
||||
---
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves.png"
|
||||
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves.png"
|
||||
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl block dark:hidden"
|
||||
/>
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves-dark.png"
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves-dark.png"
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl hidden dark:block"
|
||||
|
|
@ -35,12 +35,22 @@ if __name__ == "__main__":
|
|||
mcp.run()
|
||||
```
|
||||
|
||||
<Tip>
|
||||
**This documentation is for FastMCP 3.0**, which is currently in beta. For the 2.x release, see the [FastMCP 2.0 documentation](/v2/getting-started/welcome).
|
||||
</Tip>
|
||||
|
||||
## Beyond Basic MCP
|
||||
## Production-Ready MCP
|
||||
|
||||
FastMCP pioneered Python MCP development, and FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
||||
FastMCP pioneered Python MCP development—FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024. Today, roughly 70% of MCP servers run on some version of FastMCP.
|
||||
|
||||
**This is FastMCP 2.0,** the actively maintained version that extends far beyond basic protocol implementation. While the SDK provides core functionality, FastMCP 2.0 delivers everything needed for production: advanced MCP patterns (server composition, proxying, OpenAPI/FastAPI generation, tool transformation), enterprise auth (Google, GitHub, Azure, Auth0, WorkOS, and more), deployment tools, testing frameworks, and comprehensive client libraries.
|
||||
**FastMCP 3.0 is built for enterprise MCP applications.** When you need to compose multiple servers, proxy remote APIs, add enterprise auth, or deploy multi-tenant systems, FastMCP provides the right abstractions:
|
||||
|
||||
- **[Providers](/servers/providers/overview)** let you source components from anywhere—decorated functions, filesystem discovery, remote servers, OpenAPI specs, or your own custom sources.
|
||||
- **[Transforms](/servers/providers/transforms)** control what clients see—namespace for composition, filter by version or user permissions, convert resources to tools for compatibility.
|
||||
- **[Servers](/servers/server)** expose everything over stdio, HTTP, or WebSocket with session management and protocol handling built in.
|
||||
- **[Middleware](/servers/middleware)** handles cross-cutting concerns—authentication, authorization, logging, rate limiting, custom business logic.
|
||||
|
||||
Mount a weather API and a calendar server together with automatic namespacing. Proxy your company's internal MCP servers through a single authenticated gateway. Build a SaaS product where each tenant gets their own isolated component set. These aren't edge cases—they're what production MCP looks like.
|
||||
|
||||
Ready to build? Start with our [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart).
|
||||
|
||||
|
|
@ -59,7 +69,7 @@ FastMCP provides a high-level, Pythonic interface for building, managing, and in
|
|||
|
||||
## Why FastMCP?
|
||||
|
||||
FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need — FastMCP handles the rest.
|
||||
FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need—FastMCP handles the rest.
|
||||
|
||||
🚀 **Fast**: High-level interface means less code and faster development
|
||||
|
||||
|
|
@ -67,12 +77,14 @@ FastMCP handles all the complex protocol details so you can focus on building. I
|
|||
|
||||
🐍 **Pythonic**: Feels natural to Python developers
|
||||
|
||||
🔍 **Complete**: Everything for production — enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more
|
||||
🔍 **Complete**: Everything for production—enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more
|
||||
|
||||
🔧 **Composable**: The v3 architecture scales from a single tool to complex multi-server deployments
|
||||
|
||||
FastMCP provides the shortest path from idea to production. Deploy locally, to the cloud with [FastMCP Cloud](https://fastmcp.cloud) (free for personal servers), or to your own infrastructure.
|
||||
|
||||
<Tip>
|
||||
**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 2.13.1`) to indicate when they were introduced. Note that this may include features that are not yet released.
|
||||
**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 3.0.0`) to indicate when they were introduced. Note that this may include features that are not yet released.
|
||||
</Tip>
|
||||
|
||||
## LLM-Friendly Docs
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Auth0 OAuth 🤝 FastMCP
|
|||
sidebarTitle: Auth0
|
||||
description: Secure your FastMCP server with Auth0 OAuth
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: AuthKit 🤝 FastMCP
|
|||
sidebarTitle: AuthKit
|
||||
description: Secure your FastMCP server with AuthKit by WorkOS
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: AWS Cognito OAuth 🤝 FastMCP
|
|||
sidebarTitle: AWS Cognito
|
||||
description: Secure your FastMCP server with AWS Cognito user pools
|
||||
icon: aws
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Azure (Microsoft Entra ID) OAuth 🤝 FastMCP
|
|||
sidebarTitle: Azure (Entra ID)
|
||||
description: Secure your FastMCP server with Azure/Microsoft Entra OAuth
|
||||
icon: microsoft
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: ChatGPT 🤝 FastMCP
|
|||
sidebarTitle: ChatGPT
|
||||
description: Connect FastMCP servers to ChatGPT in Chat and Deep Research modes
|
||||
icon: message-smile
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
ChatGPT supports MCP servers through remote HTTP connections in two modes: **Chat mode** for interactive conversations and **Deep Research mode** for comprehensive information retrieval.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Descope 🤝 FastMCP
|
|||
sidebarTitle: Descope
|
||||
description: Secure your FastMCP server with Descope
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Discord OAuth 🤝 FastMCP
|
|||
sidebarTitle: Discord
|
||||
description: Secure your FastMCP server with Discord OAuth
|
||||
icon: discord
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Gemini CLI 🤝 FastMCP
|
|||
sidebarTitle: Gemini CLI
|
||||
description: Install and use FastMCP servers in Gemini CLI
|
||||
icon: message-smile
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: GitHub OAuth 🤝 FastMCP
|
|||
sidebarTitle: GitHub
|
||||
description: Secure your FastMCP server with GitHub OAuth
|
||||
icon: github
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Google OAuth 🤝 FastMCP
|
|||
sidebarTitle: Google
|
||||
description: Secure your FastMCP server with Google OAuth
|
||||
icon: google
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: OCI IAM OAuth 🤝 FastMCP
|
|||
sidebarTitle: Oracle
|
||||
description: Secure your FastMCP server with OCI IAM OAuth
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Scalekit 🤝 FastMCP
|
|||
sidebarTitle: Scalekit
|
||||
description: Secure your FastMCP server with Scalekit
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Supabase 🤝 FastMCP
|
|||
sidebarTitle: Supabase
|
||||
description: Secure your FastMCP server with Supabase Auth
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: WorkOS 🤝 FastMCP
|
|||
sidebarTitle: WorkOS
|
||||
description: Authenticate FastMCP servers with WorkOS Connect
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: OIDC Proxy
|
|||
sidebarTitle: OIDC Proxy
|
||||
description: Bridge OIDC providers to work seamlessly with MCP's authentication flow.
|
||||
icon: share
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Remote OAuth
|
|||
sidebarTitle: Remote OAuth
|
||||
description: Integrate your FastMCP server with external identity providers like Descope, WorkOS, Auth0, and corporate SSO systems.
|
||||
icon: camera-cctv
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: MCP Context
|
|||
sidebarTitle: Context
|
||||
description: Access MCP capabilities like logging, progress, and resources within your MCP objects.
|
||||
icon: rectangle-code
|
||||
tag: NEW
|
||||
---
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: User Elicitation
|
|||
sidebarTitle: Elicitation
|
||||
description: Request structured input from users during tool execution through the MCP context.
|
||||
icon: message-question
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
@ -12,26 +11,20 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
User elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, tools can interactively ask for missing parameters, clarification, or additional context as needed.
|
||||
|
||||
<Tip>
|
||||
Most of the examples in this document assume you have a FastMCP server instance named `mcp` and show how to use the `ctx.elicit` method to request user input from an `@mcp.tool`-decorated function.
|
||||
</Tip>
|
||||
|
||||
## What is Elicitation?
|
||||
|
||||
Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for:
|
||||
Elicitation enables tools to pause execution and request specific information from users:
|
||||
|
||||
- **Missing parameters**: Ask for required information not provided initially
|
||||
- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios
|
||||
- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios
|
||||
- **Progressive disclosure**: Collect complex information step-by-step
|
||||
- **Dynamic workflows**: Adapt tool behavior based on user responses
|
||||
|
||||
For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?"
|
||||
|
||||
### Basic Usage
|
||||
## Overview
|
||||
|
||||
Use the `ctx.elicit()` method within any tool function to request user input:
|
||||
Use the `ctx.elicit()` method within any tool function to request user input. Specify the message to display and the type of response you expect.
|
||||
|
||||
```python {14-17}
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
|
@ -49,7 +42,7 @@ async def collect_user_info(ctx: Context) -> str:
|
|||
message="Please provide your information",
|
||||
response_type=UserInfo
|
||||
)
|
||||
|
||||
|
||||
if result.action == "accept":
|
||||
user = result.data
|
||||
return f"Hello {user.name}, you are {user.age} years old"
|
||||
|
|
@ -59,72 +52,27 @@ async def collect_user_info(ctx: Context) -> str:
|
|||
return "Operation cancelled"
|
||||
```
|
||||
|
||||
## Method Signature
|
||||
|
||||
<Card icon="code" title="Context Elicitation Method">
|
||||
<ResponseField name="ctx.elicit" type="async method">
|
||||
<Expandable title="Parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The prompt message to display to the user
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="response_type" type="type" default="None">
|
||||
The Python type defining the expected response structure (dataclass, primitive type, etc.) Note that elicitation responses are subject to a restricted subset of JSON Schema types. See [Supported Response Types](#supported-response-types) for more details.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
|
||||
<Expandable title="Response">
|
||||
<ResponseField name="ElicitationResult" type="object">
|
||||
Result object containing the user's response
|
||||
|
||||
<Expandable title="properties">
|
||||
<ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
|
||||
How the user responded to the request
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="data" type="response_type | None">
|
||||
The user's input data (only present when action is "accept")
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Elicitation Actions
|
||||
|
||||
The elicitation result contains an `action` field indicating how the user responded:
|
||||
|
||||
- **`accept`**: User provided valid input - data is available in the `data` field
|
||||
- **`decline`**: User chose not to provide the requested information and the data field is `None`
|
||||
- **`cancel`**: User cancelled the entire operation and the data field is `None`
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| `accept` | User provided valid input—data is available in the `data` field |
|
||||
| `decline` | User chose not to provide the requested information |
|
||||
| `cancel` | User cancelled the entire operation |
|
||||
|
||||
```python {5, 7}
|
||||
@mcp.tool
|
||||
async def my_tool(ctx: Context) -> str:
|
||||
result = await ctx.elicit("Choose an action")
|
||||
FastMCP also provides typed result classes for pattern matching:
|
||||
|
||||
if result.action == "accept":
|
||||
return "Accepted!"
|
||||
elif result.action == "decline":
|
||||
return "Declined!"
|
||||
else:
|
||||
return "Cancelled!"
|
||||
```
|
||||
|
||||
FastMCP also provides typed result classes for pattern matching on the `action` field:
|
||||
|
||||
```python {1-5, 12, 14, 16}
|
||||
```python
|
||||
from fastmcp.server.elicitation import (
|
||||
AcceptedElicitation,
|
||||
DeclinedElicitation,
|
||||
AcceptedElicitation,
|
||||
DeclinedElicitation,
|
||||
CancelledElicitation,
|
||||
)
|
||||
|
||||
@mcp.tool
|
||||
async def pattern_example(ctx: Context) -> str:
|
||||
result = await ctx.elicit("Enter your name:", response_type=str)
|
||||
|
||||
|
||||
match result:
|
||||
case AcceptedElicitation(data=name):
|
||||
return f"Hello {name}!"
|
||||
|
|
@ -134,50 +82,74 @@ async def pattern_example(ctx: Context) -> str:
|
|||
return "Operation cancelled"
|
||||
```
|
||||
|
||||
## Response Types
|
||||
### Multi-Turn Elicitation
|
||||
|
||||
The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. If the request is `accept`-ed, the client must send a response that matches the schema.
|
||||
Tools can make multiple elicitation calls to gather information progressively:
|
||||
|
||||
The MCP spec only supports a limited subset of JSON Schema types for elicitation responses. Specifically, it only supports JSON **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean` and `enum` fields.
|
||||
```python
|
||||
@mcp.tool
|
||||
async def plan_meeting(ctx: Context) -> str:
|
||||
"""Plan a meeting by gathering details step by step."""
|
||||
|
||||
title_result = await ctx.elicit("What's the meeting title?", response_type=str)
|
||||
if title_result.action != "accept":
|
||||
return "Meeting planning cancelled"
|
||||
|
||||
duration_result = await ctx.elicit("Duration in minutes?", response_type=int)
|
||||
if duration_result.action != "accept":
|
||||
return "Meeting planning cancelled"
|
||||
|
||||
priority_result = await ctx.elicit(
|
||||
"Is this urgent?",
|
||||
response_type=["yes", "no"]
|
||||
)
|
||||
if priority_result.action != "accept":
|
||||
return "Meeting planning cancelled"
|
||||
|
||||
urgent = priority_result.data == "yes"
|
||||
return f"Meeting '{title_result.data}' for {duration_result.data} minutes (Urgent: {urgent})"
|
||||
```
|
||||
|
||||
### Client Requirements
|
||||
|
||||
Elicitation requires the client to implement an elicitation handler. If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported.
|
||||
|
||||
See [Client Elicitation](/clients/elicitation) for details on how clients handle these requests.
|
||||
|
||||
## Schema and Response Types
|
||||
|
||||
The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. The MCP spec only supports a limited subset of JSON Schema types for elicitation responses—specifically JSON **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean`, and `enum` fields.
|
||||
|
||||
FastMCP makes it easy to request a broader range of types, including scalars (e.g. `str`) or no response at all, by automatically wrapping them in MCP-compatible object schemas.
|
||||
|
||||
|
||||
### Scalar Types
|
||||
|
||||
You can request simple scalar data types for basic input, such as a string, integer, or boolean.
|
||||
|
||||
When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a corresponding schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned to your tool function as the `data` field of the `ElicitationResult` object.
|
||||
|
||||
As a developer, this means you do not have to worry about creating or accessing a structured object when you only need a scalar value.
|
||||
You can request simple scalar data types for basic input, such as a string, integer, or boolean. When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned directly in the `data` field.
|
||||
|
||||
<CodeGroup>
|
||||
```python {4} title="Request a string"
|
||||
```python title="String"
|
||||
@mcp.tool
|
||||
async def get_user_name(ctx: Context) -> str:
|
||||
"""Get the user's name."""
|
||||
result = await ctx.elicit("What's your name?", response_type=str)
|
||||
|
||||
|
||||
if result.action == "accept":
|
||||
return f"Hello, {result.data}!"
|
||||
return "No name provided"
|
||||
```
|
||||
```python {4} title="Request an integer"
|
||||
```python title="Integer"
|
||||
@mcp.tool
|
||||
async def pick_a_number(ctx: Context) -> str:
|
||||
"""Pick a number."""
|
||||
result = await ctx.elicit("Pick a number!", response_type=int)
|
||||
|
||||
|
||||
if result.action == "accept":
|
||||
return f"You picked {result.data}"
|
||||
return "No number provided"
|
||||
```
|
||||
```python {4} title="Request a boolean"
|
||||
```python title="Boolean"
|
||||
@mcp.tool
|
||||
async def pick_a_boolean(ctx: Context) -> str:
|
||||
"""Pick a boolean."""
|
||||
result = await ctx.elicit("True or false?", response_type=bool)
|
||||
|
||||
|
||||
if result.action == "accept":
|
||||
return f"You picked {result.data}"
|
||||
return "No boolean provided"
|
||||
|
|
@ -186,12 +158,11 @@ async def pick_a_boolean(ctx: Context) -> str:
|
|||
|
||||
### No Response
|
||||
|
||||
Sometimes, the goal of an elicitation is to simply get a user to approve or reject an action. In this case, you can pass `None` as the response type to indicate that no response is expected. In order to comply with the MCP spec, the client will see a schema requesting an empty object in response. In this case, the `data` field of the `ElicitationResult` object will be `None` when the user accepts the elicitation.
|
||||
Sometimes, the goal of an elicitation is to simply get a user to approve or reject an action. Pass `None` as the response type to indicate that no data is expected. The `data` field will be `None` when the user accepts.
|
||||
|
||||
```python {4} title="No response"
|
||||
```python
|
||||
@mcp.tool
|
||||
async def approve_action(ctx: Context) -> str:
|
||||
"""Approve an action."""
|
||||
result = await ctx.elicit("Approve this action?", response_type=None)
|
||||
|
||||
if result.action == "accept":
|
||||
|
|
@ -202,13 +173,12 @@ async def approve_action(ctx: Context) -> str:
|
|||
|
||||
### Constrained Options
|
||||
|
||||
Often you'll want to constrain the user's response to a specific set of values. You can do this by using a `Literal` type or a Python enum as the response type, or by passing a list of strings to the `response_type` parameter as a convenient shortcut.
|
||||
Constrain the user's response to a specific set of values using a `Literal` type, Python enum, or a list of strings as a convenient shortcut.
|
||||
|
||||
<CodeGroup>
|
||||
```python {6} title="Using a list of strings"
|
||||
```python title="List of strings"
|
||||
@mcp.tool
|
||||
async def set_priority(ctx: Context) -> str:
|
||||
"""Set task priority level."""
|
||||
result = await ctx.elicit(
|
||||
"What priority level?",
|
||||
response_type=["low", "medium", "high"],
|
||||
|
|
@ -217,12 +187,11 @@ async def set_priority(ctx: Context) -> str:
|
|||
if result.action == "accept":
|
||||
return f"Priority set to: {result.data}"
|
||||
```
|
||||
```python {1, 8} title="Using a Literal type"
|
||||
```python title="Literal type"
|
||||
from typing import Literal
|
||||
|
||||
@mcp.tool
|
||||
async def set_priority(ctx: Context) -> str:
|
||||
"""Set task priority level."""
|
||||
result = await ctx.elicit(
|
||||
"What priority level?",
|
||||
response_type=Literal["low", "medium", "high"]
|
||||
|
|
@ -232,7 +201,7 @@ async def set_priority(ctx: Context) -> str:
|
|||
return f"Priority set to: {result.data}"
|
||||
return "No priority set"
|
||||
```
|
||||
```python {1, 11} title="Using a Python enum"
|
||||
```python title="Python enum"
|
||||
from enum import Enum
|
||||
|
||||
class Priority(Enum):
|
||||
|
|
@ -242,7 +211,6 @@ class Priority(Enum):
|
|||
|
||||
@mcp.tool
|
||||
async def set_priority(ctx: Context) -> str:
|
||||
"""Set task priority level."""
|
||||
result = await ctx.elicit("What priority level?", response_type=Priority)
|
||||
|
||||
if result.action == "accept":
|
||||
|
|
@ -251,28 +219,26 @@ async def set_priority(ctx: Context) -> str:
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Multi-Select
|
||||
### Multi-Select
|
||||
|
||||
<VersionBadge version="2.14.0" />
|
||||
|
||||
Enable multi-select by wrapping your choices in an additional list level. This allows users to select multiple values from the available options.
|
||||
|
||||
<CodeGroup>
|
||||
```python {6-8} title="List of a list of strings"
|
||||
```python title="List of strings"
|
||||
@mcp.tool
|
||||
async def select_tags(ctx: Context) -> str:
|
||||
"""Select multiple tags."""
|
||||
result = await ctx.elicit(
|
||||
"Choose tags",
|
||||
response_type=[["bug", "feature", "documentation"]] # Note: list of a list
|
||||
)
|
||||
|
||||
if result.action == "accept":
|
||||
tags = result.data # List of selected strings
|
||||
tags = result.data
|
||||
return f"Selected tags: {', '.join(tags)}"
|
||||
```
|
||||
|
||||
```python {1, 3-6, 11-14} title="list[Enum] type annotation"
|
||||
```python title="list[Enum] type"
|
||||
from enum import Enum
|
||||
|
||||
class Tag(Enum):
|
||||
|
|
@ -284,7 +250,7 @@ class Tag(Enum):
|
|||
async def select_tags(ctx: Context) -> str:
|
||||
result = await ctx.elicit(
|
||||
"Choose tags",
|
||||
response_type=list[Tag] # Type annotation for multi-select
|
||||
response_type=list[Tag]
|
||||
)
|
||||
if result.action == "accept":
|
||||
tags = [tag.value for tag in result.data]
|
||||
|
|
@ -292,38 +258,15 @@ async def select_tags(ctx: Context) -> str:
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
For titled multi-select, wrap a dict in a list (see [Titled Options](#titled-options) for dict syntax):
|
||||
|
||||
```python {6-12}
|
||||
@mcp.tool
|
||||
async def select_priorities(ctx: Context) -> str:
|
||||
"""Select multiple priorities."""
|
||||
result = await ctx.elicit(
|
||||
"Choose priorities",
|
||||
response_type=[{ # Note: list containing a dict
|
||||
"low": {"title": "Low Priority"},
|
||||
"medium": {"title": "Medium Priority"},
|
||||
"high": {"title": "High Priority"}
|
||||
}]
|
||||
)
|
||||
|
||||
if result.action == "accept":
|
||||
priorities = result.data # List of selected strings
|
||||
return f"Selected: {', '.join(priorities)}"
|
||||
```
|
||||
|
||||
#### Titled Options
|
||||
### Titled Options
|
||||
|
||||
<VersionBadge version="2.14.0" />
|
||||
|
||||
For better UI display, you can provide human-readable titles for enum options. FastMCP generates SEP-1330 compliant schemas using the `oneOf` pattern with `const` and `title` fields.
|
||||
For better UI display, provide human-readable titles for enum options. FastMCP generates SEP-1330 compliant schemas using the `oneOf` pattern with `const` and `title` fields.
|
||||
|
||||
Use a dict to specify titles for enum values:
|
||||
|
||||
```python {6-10}
|
||||
```python
|
||||
@mcp.tool
|
||||
async def set_priority(ctx: Context) -> str:
|
||||
"""Set task priority level."""
|
||||
result = await ctx.elicit(
|
||||
"What priority level?",
|
||||
response_type={
|
||||
|
|
@ -339,13 +282,12 @@ async def set_priority(ctx: Context) -> str:
|
|||
|
||||
For multi-select with titles, wrap the dict in a list:
|
||||
|
||||
```python {6-12}
|
||||
```python
|
||||
@mcp.tool
|
||||
async def select_priorities(ctx: Context) -> str:
|
||||
"""Select multiple priorities."""
|
||||
result = await ctx.elicit(
|
||||
"Choose priorities",
|
||||
response_type=[{ # List containing a dict for multi-select
|
||||
response_type=[{
|
||||
"low": {"title": "Low Priority"},
|
||||
"medium": {"title": "Medium Priority"},
|
||||
"high": {"title": "High Priority"}
|
||||
|
|
@ -353,15 +295,14 @@ async def select_priorities(ctx: Context) -> str:
|
|||
)
|
||||
|
||||
if result.action == "accept":
|
||||
priorities = result.data # List of selected strings
|
||||
return f"Selected: {', '.join(priorities)}"
|
||||
return f"Selected: {', '.join(result.data)}"
|
||||
```
|
||||
|
||||
### Structured Responses
|
||||
|
||||
You can request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties.
|
||||
Request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties.
|
||||
|
||||
```python {1, 16, 20}
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
|
|
@ -374,12 +315,11 @@ class TaskDetails:
|
|||
|
||||
@mcp.tool
|
||||
async def create_task(ctx: Context) -> str:
|
||||
"""Create a new task with user-provided details."""
|
||||
result = await ctx.elicit(
|
||||
"Please provide task details",
|
||||
response_type=TaskDetails
|
||||
)
|
||||
|
||||
|
||||
if result.action == "accept":
|
||||
task = result.data
|
||||
return f"Created task: {task.title} (Priority: {task.priority})"
|
||||
|
|
@ -390,16 +330,7 @@ async def create_task(ctx: Context) -> str:
|
|||
|
||||
<VersionBadge version="2.14.0" />
|
||||
|
||||
You can provide default values for elicitation fields using Pydantic's `Field(default=...)`. Clients will pre-populate form fields with these defaults, making it easier for users to provide input.
|
||||
|
||||
Default values are supported for all primitive types:
|
||||
- Strings: `Field(default="[email protected]")`
|
||||
- Integers: `Field(default=50)`
|
||||
- Numbers: `Field(default=3.14)`
|
||||
- Booleans: `Field(default=False)`
|
||||
- Enums: `Field(default=EnumValue.A)`
|
||||
|
||||
Fields with default values are automatically marked as optional (not included in the `required` list), so users can accept the default or provide their own value.
|
||||
Provide default values for elicitation fields using Pydantic's `Field(default=...)`. Clients will pre-populate form fields with these defaults. Fields with default values are automatically marked as optional.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
|
@ -423,40 +354,4 @@ async def create_task(ctx: Context) -> str:
|
|||
return "Task creation cancelled"
|
||||
```
|
||||
|
||||
## Multi-Turn Elicitation
|
||||
|
||||
Tools can make multiple elicitation calls to gather information progressively:
|
||||
|
||||
```python {6, 11, 16-19}
|
||||
@mcp.tool
|
||||
async def plan_meeting(ctx: Context) -> str:
|
||||
"""Plan a meeting by gathering details step by step."""
|
||||
|
||||
# Get meeting title
|
||||
title_result = await ctx.elicit("What's the meeting title?", response_type=str)
|
||||
if title_result.action != "accept":
|
||||
return "Meeting planning cancelled"
|
||||
|
||||
# Get duration
|
||||
duration_result = await ctx.elicit("Duration in minutes?", response_type=int)
|
||||
if duration_result.action != "accept":
|
||||
return "Meeting planning cancelled"
|
||||
|
||||
# Get priority
|
||||
priority_result = await ctx.elicit(
|
||||
"Is this urgent?",
|
||||
response_type=Literal["yes", "no"]
|
||||
)
|
||||
if priority_result.action != "accept":
|
||||
return "Meeting planning cancelled"
|
||||
|
||||
urgent = priority_result.data == "yes"
|
||||
return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})"
|
||||
```
|
||||
|
||||
|
||||
## Client Requirements
|
||||
|
||||
Elicitation requires the client to implement an elicitation handler. See [Client Elicitation](/clients/elicitation) for details on how clients can handle these requests.
|
||||
|
||||
If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported.
|
||||
Default values are supported for strings, integers, numbers, booleans, and enums.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
title: Icons
|
||||
description: Add visual icons to your servers, tools, resources, and prompts
|
||||
icon: image
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
@ -13,11 +12,7 @@ Icons provide visual representations for your MCP servers and components, helpin
|
|||
|
||||
## Icon Format
|
||||
|
||||
Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies:
|
||||
|
||||
- **src**: URL or data URI pointing to the icon image
|
||||
- **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml")
|
||||
- **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"])
|
||||
Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies a source URL or data URI, and optionally includes MIME type and size information.
|
||||
|
||||
```python
|
||||
from mcp.types import Icon
|
||||
|
|
@ -29,9 +24,15 @@ icon = Icon(
|
|||
)
|
||||
```
|
||||
|
||||
The fields serve different purposes:
|
||||
|
||||
- **src**: URL or data URI pointing to the icon image
|
||||
- **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml")
|
||||
- **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"])
|
||||
|
||||
## Server Icons
|
||||
|
||||
Add icons and a website URL to your server for display in client applications:
|
||||
Add icons and a website URL to your server for display in client applications. Multiple icons at different sizes help clients choose the best resolution for their display context.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -59,7 +60,7 @@ Server icons appear in MCP client interfaces to help users identify your server
|
|||
|
||||
## Component Icons
|
||||
|
||||
Icons can be added to individual tools, resources, resource templates, and prompts:
|
||||
Icons can be added to individual tools, resources, resource templates, and prompts. This helps users visually distinguish between different component types and purposes.
|
||||
|
||||
### Tool Icons
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ def analyze_code(code: str):
|
|||
|
||||
## Using Data URIs
|
||||
|
||||
For small icons or when you want to embed the icon directly, use data URIs:
|
||||
For small icons or when you want to embed the icon directly without external dependencies, use data URIs. This approach eliminates the need for hosting and ensures the icon is always available.
|
||||
|
||||
```python
|
||||
from mcp.types import Icon
|
||||
|
|
@ -127,8 +128,17 @@ svg_icon = Icon(
|
|||
def my_tool() -> str:
|
||||
"""A tool with an embedded SVG icon."""
|
||||
return "result"
|
||||
```
|
||||
|
||||
# Generating a data URI from a local image file.
|
||||
### Generating Data URIs from Files
|
||||
|
||||
FastMCP provides the `Image` utility class to convert local image files into data URIs.
|
||||
|
||||
```python
|
||||
from mcp.types import Icon
|
||||
from fastmcp.utilities.types import Image
|
||||
|
||||
# Generate a data URI from a local image file
|
||||
img = Image(path="./assets/brand/favicon.png")
|
||||
icon = Icon(src=img.to_data_uri())
|
||||
|
||||
|
|
@ -137,3 +147,5 @@ def file_icon_tool() -> str:
|
|||
"""A tool with an icon generated from a local file."""
|
||||
return "result"
|
||||
```
|
||||
|
||||
This approach is useful when you have local image assets and want to embed them directly in your server definition.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Lifespans
|
||||
sidebarTitle: Lifespan
|
||||
description: Server-level setup and teardown with composable lifespans
|
||||
icon: heart-pulse
|
||||
tag: NEW
|
||||
|
|
|
|||
|
|
@ -8,27 +8,16 @@ icon: receipt
|
|||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<Tip>
|
||||
This documentation covers **MCP client logging** - sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module.
|
||||
This documentation covers **MCP client logging**—sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module.
|
||||
</Tip>
|
||||
|
||||
Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. This provides visibility into function execution and helps with debugging during development and operation.
|
||||
Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs.
|
||||
|
||||
## Why Use Server Logging?
|
||||
|
||||
Server logging is essential for:
|
||||
|
||||
- **Debugging**: Send detailed execution information to help diagnose issues
|
||||
- **Progress visibility**: Keep users informed about what the tool is doing
|
||||
- **Error reporting**: Communicate problems and their context to clients
|
||||
- **Audit trails**: Create records of tool execution for compliance or analysis
|
||||
|
||||
Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs.
|
||||
|
||||
### Basic Usage
|
||||
## Basic Usage
|
||||
|
||||
Use the context logging methods within any tool function:
|
||||
|
||||
```python {8-9, 13, 17, 21}
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP("LoggingDemo")
|
||||
|
|
@ -38,24 +27,33 @@ async def analyze_data(data: list[float], ctx: Context) -> dict:
|
|||
"""Analyze numerical data with comprehensive logging."""
|
||||
await ctx.debug("Starting analysis of numerical data")
|
||||
await ctx.info(f"Analyzing {len(data)} data points")
|
||||
|
||||
|
||||
try:
|
||||
if not data:
|
||||
await ctx.warning("Empty data list provided")
|
||||
return {"error": "Empty data list"}
|
||||
|
||||
|
||||
result = sum(data) / len(data)
|
||||
await ctx.info(f"Analysis complete, average: {result}")
|
||||
return {"average": result, "count": len(data)}
|
||||
|
||||
|
||||
except Exception as e:
|
||||
await ctx.error(f"Analysis failed: {str(e)}")
|
||||
raise
|
||||
```
|
||||
|
||||
## Structured Logging with `extra`
|
||||
## Log Levels
|
||||
|
||||
All logging methods (`debug`, `info`, `warning`, `error`, `log`) now accept an `extra` parameter, which is a dictionary of arbitrary data. This allows you to send structured data to the client, which is useful for creating rich, queryable logs.
|
||||
| Level | Use Case |
|
||||
|-------|----------|
|
||||
| `ctx.debug()` | Detailed execution information for diagnosing problems |
|
||||
| `ctx.info()` | General information about normal program execution |
|
||||
| `ctx.warning()` | Potentially harmful situations that don't prevent execution |
|
||||
| `ctx.error()` | Error events that might still allow the application to continue |
|
||||
|
||||
## Structured Logging
|
||||
|
||||
All logging methods accept an `extra` parameter for sending structured data to the client. This is useful for creating rich, queryable logs.
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
|
|
@ -68,182 +66,22 @@ async def process_transaction(transaction_id: str, amount: float, ctx: Context):
|
|||
"currency": "USD"
|
||||
}
|
||||
)
|
||||
# ... processing logic ...
|
||||
```
|
||||
|
||||
## Server Logs
|
||||
## Server-Side Logs
|
||||
|
||||
Client Logging in the form of `ctx.log()` and its convenience methods (`debug`, `info`, `warning`, `error`) are meant for sending messages to the MCP clients. Messages sent to clients are also logged to the server's log at `DEBUG` level. Enable debug logging on the server or enable debug logging on the `fastmcp.server.context.to_client` logger to see these messages in the server's log.
|
||||
Messages sent to clients via `ctx.log()` and its convenience methods are also logged to the server's log at `DEBUG` level. Enable debug logging on the `fastmcp.server.context.to_client` logger to see these messages:
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
to_client_logger = get_logger(name="fastmcp.server.context.to_client")
|
||||
to_client_logger.setLevel(level=logging.DEBUG)
|
||||
```
|
||||
|
||||
## Logging Methods
|
||||
|
||||
<Card icon="code" title="Context Logging Methods">
|
||||
<ResponseField name="ctx.debug" type="async method">
|
||||
Send debug-level messages for detailed execution information
|
||||
|
||||
<Expandable title="parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The debug message to send to the client
|
||||
</ResponseField>
|
||||
<ResponseField name="extra" type="dict | None" default="None">
|
||||
Optional dictionary for structured logging data
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="ctx.info" type="async method">
|
||||
Send informational messages about normal execution
|
||||
|
||||
<Expandable title="parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The information message to send to the client
|
||||
</ResponseField>
|
||||
<ResponseField name="extra" type="dict | None" default="None">
|
||||
Optional dictionary for structured logging data
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="ctx.warning" type="async method">
|
||||
Send warning messages for potential issues that didn't prevent execution
|
||||
|
||||
<Expandable title="parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The warning message to send to the client
|
||||
</ResponseField>
|
||||
<ResponseField name="extra" type="dict | None" default="None">
|
||||
Optional dictionary for structured logging data
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="ctx.error" type="async method">
|
||||
Send error messages for problems that occurred during execution
|
||||
|
||||
<Expandable title="parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The error message to send to the client
|
||||
</ResponseField>
|
||||
<ResponseField name="extra" type="dict | None" default="None">
|
||||
Optional dictionary for structured logging data
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="ctx.log" type="async method">
|
||||
Generic logging method with custom level and logger name
|
||||
|
||||
<Expandable title="parameters">
|
||||
<ResponseField name="level" type="Literal['debug', 'info', 'warning', 'error']">
|
||||
The log level for the message
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="message" type="str">
|
||||
The message to send to the client
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="logger_name" type="str | None" default="None">
|
||||
Optional custom logger name for categorizing messages
|
||||
</ResponseField>
|
||||
<ResponseField name="extra" type="dict | None" default="None">
|
||||
Optional dictionary for structured logging data
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Log Levels
|
||||
|
||||
### Debug
|
||||
Use for detailed information that's typically only useful when diagnosing problems:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def process_file(file_path: str, ctx: Context) -> str:
|
||||
"""Process a file with detailed debug logging."""
|
||||
await ctx.debug(f"Starting to process file: {file_path}")
|
||||
await ctx.debug("Checking file permissions")
|
||||
|
||||
# File processing logic
|
||||
await ctx.debug("File processing completed successfully")
|
||||
return "File processed"
|
||||
```
|
||||
|
||||
### Info
|
||||
Use for general information about normal program execution:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def backup_database(ctx: Context) -> str:
|
||||
"""Backup database with progress information."""
|
||||
await ctx.info("Starting database backup")
|
||||
await ctx.info("Connecting to database")
|
||||
await ctx.info("Backup completed successfully")
|
||||
return "Database backed up"
|
||||
```
|
||||
|
||||
### Warning
|
||||
Use for potentially harmful situations that don't prevent execution:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def validate_config(config: dict, ctx: Context) -> dict:
|
||||
"""Validate configuration with warnings for deprecated options."""
|
||||
if "old_api_key" in config:
|
||||
await ctx.warning(
|
||||
"Using deprecated 'old_api_key' field. Please use 'api_key' instead",
|
||||
extra={"deprecated_field": "old_api_key"}
|
||||
)
|
||||
|
||||
if config.get("timeout", 30) > 300:
|
||||
await ctx.warning(
|
||||
"Timeout value is very high (>5 minutes), this may cause issues",
|
||||
extra={"timeout_value": config.get("timeout")}
|
||||
)
|
||||
|
||||
return {"status": "valid", "warnings": "see logs"}
|
||||
```
|
||||
|
||||
### Error
|
||||
Use for error events that might still allow the application to continue:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def batch_process(items: list[str], ctx: Context) -> dict:
|
||||
"""Process multiple items, logging errors for failed items."""
|
||||
successful = 0
|
||||
failed = 0
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
# Process item
|
||||
successful += 1
|
||||
except Exception as e:
|
||||
await ctx.error(
|
||||
f"Failed to process item '{item}': {str(e)}",
|
||||
extra={"failed_item": item}
|
||||
)
|
||||
failed += 1
|
||||
|
||||
return {"successful": successful, "failed": failed}
|
||||
```
|
||||
|
||||
|
||||
## Client Handling
|
||||
|
||||
Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation:
|
||||
Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation—development clients may display logs in real-time, production clients may store them for analysis, and integration clients may forward them to external logging systems.
|
||||
|
||||
- **Development clients**: May display logs in real-time for debugging
|
||||
- **Production clients**: May store logs for later analysis or display to users
|
||||
- **Integration clients**: May forward logs to external logging systems
|
||||
|
||||
See [Client Logging](/clients/logging) for details on how clients can handle server log messages.
|
||||
See [Client Logging](/clients/logging) for details on how clients handle server log messages.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,6 +3,7 @@ title: Pagination
|
|||
sidebarTitle: Pagination
|
||||
description: Control how servers return large lists of components to clients.
|
||||
icon: page
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
|
|
@ -7,22 +7,13 @@ icon: chart-line
|
|||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
Progress reporting allows MCP tools to notify clients about the progress of long-running operations. This enables clients to display progress indicators and provide better user experience during time-consuming tasks.
|
||||
Progress reporting allows MCP tools to notify clients about the progress of long-running operations. Clients can display progress indicators and provide better user experience during time-consuming tasks.
|
||||
|
||||
## Why Use Progress Reporting?
|
||||
## Basic Usage
|
||||
|
||||
Progress reporting is valuable for:
|
||||
Use `ctx.report_progress()` to send progress updates to the client. The method accepts a `progress` value representing how much work is complete, and an optional `total` representing the full scope of work.
|
||||
|
||||
- **User experience**: Keep users informed about long-running operations
|
||||
- **Progress indicators**: Enable clients to show progress bars or percentages
|
||||
- **Timeout prevention**: Demonstrate that operations are actively progressing
|
||||
- **Debugging**: Track execution progress for performance analysis
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Use `ctx.report_progress()` to send progress updates to the client:
|
||||
|
||||
```python {14, 21}
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
import asyncio
|
||||
|
||||
|
|
@ -33,157 +24,28 @@ async def process_items(items: list[str], ctx: Context) -> dict:
|
|||
"""Process a list of items with progress updates."""
|
||||
total = len(items)
|
||||
results = []
|
||||
|
||||
|
||||
for i, item in enumerate(items):
|
||||
# Report progress as we process each item
|
||||
await ctx.report_progress(progress=i, total=total)
|
||||
|
||||
# Simulate processing time
|
||||
await asyncio.sleep(0.1)
|
||||
results.append(item.upper())
|
||||
|
||||
# Report 100% completion
|
||||
|
||||
await ctx.report_progress(progress=total, total=total)
|
||||
|
||||
return {"processed": len(results), "results": results}
|
||||
```
|
||||
|
||||
## Method Signature
|
||||
|
||||
<Card icon="code" title="Context Progress Method">
|
||||
<ResponseField name="ctx.report_progress" type="async method">
|
||||
Report progress to the client for long-running operations
|
||||
|
||||
<Expandable title="Parameters">
|
||||
<ResponseField name="progress" type="float">
|
||||
Current progress value (e.g., 24, 0.75, 1500)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="total" type="float | None" default="None">
|
||||
Optional total value (e.g., 100, 1.0, 2000). When provided, clients may interpret this as enabling percentage calculation.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Progress Patterns
|
||||
|
||||
### Percentage-Based Progress
|
||||
|
||||
Report progress as a percentage (0-100):
|
||||
|
||||
```python {13-14}
|
||||
@mcp.tool
|
||||
async def download_file(url: str, ctx: Context) -> str:
|
||||
"""Download a file with percentage progress."""
|
||||
total_size = 1000 # KB
|
||||
downloaded = 0
|
||||
|
||||
while downloaded < total_size:
|
||||
# Download chunk
|
||||
chunk_size = min(50, total_size - downloaded)
|
||||
downloaded += chunk_size
|
||||
|
||||
# Report percentage progress
|
||||
percentage = (downloaded / total_size) * 100
|
||||
await ctx.report_progress(progress=percentage, total=100)
|
||||
|
||||
await asyncio.sleep(0.1) # Simulate download time
|
||||
|
||||
return f"Downloaded file from {url}"
|
||||
```
|
||||
|
||||
### Absolute Progress
|
||||
|
||||
Report progress with absolute values:
|
||||
|
||||
```python {10}
|
||||
@mcp.tool
|
||||
async def backup_database(ctx: Context) -> str:
|
||||
"""Backup database tables with absolute progress."""
|
||||
tables = ["users", "orders", "products", "inventory", "logs"]
|
||||
|
||||
for i, table in enumerate(tables):
|
||||
await ctx.info(f"Backing up table: {table}")
|
||||
|
||||
# Report absolute progress
|
||||
await ctx.report_progress(progress=i + 1, total=len(tables))
|
||||
|
||||
# Simulate backup time
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
return "Database backup completed"
|
||||
```
|
||||
|
||||
### Indeterminate Progress
|
||||
|
||||
Report progress without a known total for operations where the endpoint is unknown:
|
||||
|
||||
```python {11}
|
||||
@mcp.tool
|
||||
async def scan_directory(directory: str, ctx: Context) -> dict:
|
||||
"""Scan directory with indeterminate progress."""
|
||||
files_found = 0
|
||||
|
||||
# Simulate directory scanning
|
||||
for i in range(10): # Unknown number of files
|
||||
files_found += 1
|
||||
|
||||
# Report progress without total for indeterminate operations
|
||||
await ctx.report_progress(progress=files_found)
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
return {"files_found": files_found, "directory": directory}
|
||||
```
|
||||
|
||||
### Multi-Stage Operations
|
||||
|
||||
Break complex operations into stages with progress for each:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def data_migration(source: str, destination: str, ctx: Context) -> str:
|
||||
"""Migrate data with multi-stage progress reporting."""
|
||||
|
||||
# Stage 1: Validation (0-25%)
|
||||
await ctx.info("Validating source data")
|
||||
for i in range(5):
|
||||
await ctx.report_progress(progress=i * 5, total=100)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Stage 2: Export (25-60%)
|
||||
await ctx.info("Exporting data from source")
|
||||
for i in range(7):
|
||||
progress = 25 + (i * 5)
|
||||
await ctx.report_progress(progress=progress, total=100)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Stage 3: Transform (60-80%)
|
||||
await ctx.info("Transforming data format")
|
||||
for i in range(4):
|
||||
progress = 60 + (i * 5)
|
||||
await ctx.report_progress(progress=progress, total=100)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Stage 4: Import (80-100%)
|
||||
await ctx.info("Importing to destination")
|
||||
for i in range(4):
|
||||
progress = 80 + (i * 5)
|
||||
await ctx.report_progress(progress=progress, total=100)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Final completion
|
||||
await ctx.report_progress(progress=100, total=100)
|
||||
|
||||
return f"Migration from {source} to {destination} completed"
|
||||
```
|
||||
| Pattern | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| Percentage | Progress as 0-100 percentage | `progress=75, total=100` |
|
||||
| Absolute | Completed items of a known count | `progress=3, total=10` |
|
||||
| Indeterminate | Progress without known endpoint | `progress=files_found` (no total) |
|
||||
|
||||
For multi-stage operations, map each stage to a portion of the total progress range. A four-stage operation might allocate 0-25% to validation, 25-60% to export, 60-80% to transform, and 80-100% to import.
|
||||
|
||||
## Client Requirements
|
||||
|
||||
Progress reporting requires clients to support progress handling:
|
||||
Progress reporting requires clients to support progress handling. Clients must send a `progressToken` in the initial request to receive progress updates. If no progress token is provided, progress calls have no effect (they don't error).
|
||||
|
||||
- Clients must send a `progressToken` in the initial request to receive progress updates
|
||||
- If no progress token is provided, progress calls will have no effect (they won't error)
|
||||
- See [Client Progress](/clients/progress) for details on implementing client-side progress handling
|
||||
See [Client Progress](/clients/progress) for details on implementing client-side progress handling.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Custom Providers
|
|||
sidebarTitle: Custom
|
||||
description: Build providers that source components from any data source
|
||||
icon: code
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Filesystem Provider
|
|||
sidebarTitle: Filesystem
|
||||
description: Automatic component discovery from Python files
|
||||
icon: folder-tree
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Local Provider
|
|||
sidebarTitle: Local
|
||||
description: The default provider for decorator-registered components
|
||||
icon: house
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
64
docs/servers/providers/namespace.mdx
Normal file
64
docs/servers/providers/namespace.mdx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: Namespace Transform
|
||||
sidebarTitle: Namespace
|
||||
description: Prefix component names to prevent conflicts
|
||||
icon: tag
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
The `Namespace` transform prefixes all component names, preventing conflicts when composing multiple servers.
|
||||
|
||||
Tools and prompts receive an underscore-separated prefix. Resources and templates receive a path-segment prefix in their URIs.
|
||||
|
||||
| Component | 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}` |
|
||||
|
||||
The most common use is through the `mount()` method's `namespace` parameter.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
weather = FastMCP("Weather")
|
||||
calendar = FastMCP("Calendar")
|
||||
|
||||
@weather.tool
|
||||
def get_data() -> str:
|
||||
return "Weather data"
|
||||
|
||||
@calendar.tool
|
||||
def get_data() -> str:
|
||||
return "Calendar data"
|
||||
|
||||
# Without namespacing, these would conflict
|
||||
main = FastMCP("Main")
|
||||
main.mount(weather, namespace="weather")
|
||||
main.mount(calendar, namespace="calendar")
|
||||
|
||||
# Clients see: weather_get_data, calendar_get_data
|
||||
```
|
||||
|
||||
You can also apply namespacing directly using the `Namespace` transform.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import Namespace
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
# Namespace all components
|
||||
mcp.add_transform(Namespace("api"))
|
||||
|
||||
# Tool is now: api_greet
|
||||
```
|
||||
|
|
@ -3,6 +3,7 @@ title: Providers
|
|||
sidebarTitle: Overview
|
||||
description: How FastMCP sources tools, resources, and prompts
|
||||
icon: layer-group
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Prompts as Tools
|
|||
sidebarTitle: Prompts as Tools
|
||||
description: Expose prompts to tool-only clients
|
||||
icon: message-lines
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Remote Proxies
|
||||
sidebarTitle: Proxying
|
||||
description: Expose remote MCP servers through your local server
|
||||
title: MCP Proxy Provider
|
||||
sidebarTitle: MCP Proxy
|
||||
description: Source components from other MCP servers
|
||||
icon: arrows-retweet
|
||||
---
|
||||
|
||||
|
|
@ -9,25 +9,25 @@ 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.
|
||||
The Proxy Provider sources components from another MCP server through a client connection. This lets you expose any MCP server's tools, resources, and prompts through your own server, whether the source is local or accessed over the network.
|
||||
|
||||
## Why Proxy Servers
|
||||
## Why Use Proxy Provider
|
||||
|
||||
Proxying lets you:
|
||||
The Proxy Provider enables:
|
||||
|
||||
- **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
|
||||
- **Bridge transports**: Make an HTTP server available via stdio, or vice versa
|
||||
- **Aggregate servers**: Combine multiple source servers into one unified server
|
||||
- **Add security**: Act as a controlled gateway with authentication and authorization
|
||||
- **Simplify access**: Provide a stable endpoint even if backend servers change
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Your Client
|
||||
participant Proxy as FastMCP Proxy
|
||||
participant Backend as Remote Server
|
||||
participant Backend as Source Server
|
||||
|
||||
Client->>Proxy: MCP Request (stdio)
|
||||
Proxy->>Backend: MCP Request (HTTP)
|
||||
Proxy->>Backend: MCP Request (HTTP/stdio/SSE)
|
||||
Backend-->>Proxy: MCP Response
|
||||
Proxy-->>Client: MCP Response
|
||||
```
|
||||
|
|
@ -59,17 +59,17 @@ To mount a proxy inside another FastMCP server, see [Mounting External Servers](
|
|||
|
||||
## Transport Bridging
|
||||
|
||||
A common use case is bridging transports - making a remote server available locally:
|
||||
A common use case is bridging transports between servers:
|
||||
|
||||
```python
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Bridge remote HTTP to local stdio
|
||||
remote_proxy = create_proxy("http://example.com/mcp/sse", name="Remote-to-Local")
|
||||
# Bridge HTTP server to local stdio
|
||||
http_proxy = create_proxy("http://example.com/mcp/sse", name="HTTP-to-stdio")
|
||||
|
||||
# Run locally via stdio for Claude Desktop
|
||||
if __name__ == "__main__":
|
||||
remote_proxy.run() # Defaults to stdio
|
||||
http_proxy.run() # Defaults to stdio
|
||||
```
|
||||
|
||||
Or expose a local server via HTTP:
|
||||
|
|
@ -78,7 +78,7 @@ Or expose a local server via HTTP:
|
|||
from fastmcp.server import create_proxy
|
||||
|
||||
# Bridge local server to HTTP
|
||||
local_proxy = create_proxy("local_server.py", name="Local-to-HTTP")
|
||||
local_proxy = create_proxy("local_server.py", name="stdio-to-HTTP")
|
||||
|
||||
if __name__ == "__main__":
|
||||
local_proxy.run(transport="http", host="0.0.0.0", port=8080)
|
||||
|
|
@ -280,7 +280,7 @@ This gives you full control over session creation and reuse strategies.
|
|||
|
||||
### Adding Proxied Components to Existing Server
|
||||
|
||||
Mount a proxy to add remote components to an existing server:
|
||||
Mount a proxy to add components from another server:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -293,9 +293,9 @@ server = FastMCP("My Server")
|
|||
def local_tool() -> str:
|
||||
return "Local result"
|
||||
|
||||
# Mount proxied tools from remote server
|
||||
remote = create_proxy("http://remote-server/mcp")
|
||||
server.mount(remote)
|
||||
# Mount proxied tools from another server
|
||||
external = create_proxy("http://external-server/mcp")
|
||||
server.mount(external)
|
||||
|
||||
# Now server has both local and proxied tools
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Resources as Tools
|
|||
sidebarTitle: Resources as Tools
|
||||
description: Expose resources to tool-only clients
|
||||
icon: toolbox
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Skills Provider
|
|||
sidebarTitle: Skills
|
||||
description: Expose agent skills as MCP resources
|
||||
icon: wand-magic-sparkles
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
@ -247,45 +248,6 @@ With `reload=True`, the provider re-discovers skills on each `list_resources()`
|
|||
Reload mode adds overhead to every request. Use it during development when you're actively editing skills, but disable it in production.
|
||||
</Warning>
|
||||
|
||||
## Complete Example
|
||||
|
||||
This example creates a server that exposes Claude Code skills, then uses a client to discover and read them.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.server.providers.skills import ClaudeSkillsProvider
|
||||
|
||||
async def main():
|
||||
# Create server with Claude skills
|
||||
mcp = FastMCP("Skills Demo")
|
||||
mcp.add_provider(ClaudeSkillsProvider(reload=True))
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# List available skill resources
|
||||
resources = await client.list_resources()
|
||||
print("Available skills:")
|
||||
for r in resources:
|
||||
if r.uri.path and r.uri.path.endswith("SKILL.md"):
|
||||
print(f" - {r.name}: {r.description}")
|
||||
|
||||
# Read a specific skill's manifest
|
||||
manifest_result = await client.read_resource("skill://pdf-processing/_manifest")
|
||||
manifest = json.loads(manifest_result[0].text)
|
||||
print(f"\nFiles in pdf-processing skill:")
|
||||
for f in manifest["files"]:
|
||||
print(f" - {f['path']} ({f['size']} bytes)")
|
||||
|
||||
# Read the main skill file
|
||||
skill_result = await client.read_resource("skill://pdf-processing/SKILL.md")
|
||||
print(f"\nSkill content:\n{skill_result[0].text[:500]}...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Client Utilities
|
||||
|
||||
FastMCP provides utilities for downloading skills from any MCP server that exposes them. These are standalone functions in `fastmcp.utilities.skills`.
|
||||
|
|
|
|||
192
docs/servers/providers/tool-transformation.mdx
Normal file
192
docs/servers/providers/tool-transformation.mdx
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
---
|
||||
title: Tool Transformation
|
||||
sidebarTitle: Tool Transformation
|
||||
description: Modify tool schemas - rename, reshape arguments, and customize behavior
|
||||
icon: wrench
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
Tool transformation lets you modify tool schemas - renaming tools, changing descriptions, adjusting tags, and reshaping argument schemas. FastMCP provides two mechanisms that share the same configuration options but differ in timing.
|
||||
|
||||
**Deferred transformation** with `ToolTransform` applies modifications when tools flow through a transform chain. Use this for tools from mounted servers, proxies, or other providers where you don't control the source directly.
|
||||
|
||||
**Immediate transformation** with `Tool.from_tool()` creates a modified tool object right away. Use this when you have direct access to a tool and want to transform it before registration.
|
||||
|
||||
## ToolTransform
|
||||
|
||||
The `ToolTransform` class is a transform that modifies tools as they flow through a provider. Provide a dictionary mapping original tool names to their transformation configuration.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import ToolTransform
|
||||
from fastmcp.tools.tool_transform import ToolTransformConfig
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
|
||||
@mcp.tool
|
||||
def verbose_internal_data_fetcher(query: str) -> str:
|
||||
"""Fetches data from the internal database."""
|
||||
return f"Results for: {query}"
|
||||
|
||||
# Rename the tool to something simpler
|
||||
mcp.add_transform(ToolTransform({
|
||||
"verbose_internal_data_fetcher": ToolTransformConfig(
|
||||
name="search",
|
||||
description="Search the database.",
|
||||
)
|
||||
}))
|
||||
|
||||
# Clients see "search" with the cleaner description
|
||||
```
|
||||
|
||||
`ToolTransform` is useful when you want to modify tools from mounted or proxied servers without changing the original source.
|
||||
|
||||
## Tool.from_tool()
|
||||
|
||||
Use `Tool.from_tool()` when you have the tool object and want to create a transformed version for registration.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool, tool
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
# Create a tool without registering it
|
||||
@tool
|
||||
def search(q: str, limit: int = 10) -> list[str]:
|
||||
"""Search for items."""
|
||||
return [f"Result {i} for {q}" for i in range(limit)]
|
||||
|
||||
# Transform it before registration
|
||||
better_search = Tool.from_tool(
|
||||
search,
|
||||
name="find_items",
|
||||
description="Find items matching your search query.",
|
||||
transform_args={
|
||||
"q": ArgTransform(
|
||||
name="query",
|
||||
description="The search terms to look for.",
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
mcp.add_tool(better_search)
|
||||
```
|
||||
|
||||
The standalone `@tool` decorator (from `fastmcp.tools`) creates a Tool object without registering it to any server. This separates creation from registration, letting you transform tools before deciding where they go.
|
||||
|
||||
## Modification Options
|
||||
|
||||
Both mechanisms support the same modifications.
|
||||
|
||||
**Tool-level options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `name` | New name for the tool |
|
||||
| `description` | New description |
|
||||
| `title` | Human-readable title |
|
||||
| `tags` | Set of tags for categorization |
|
||||
| `annotations` | MCP ToolAnnotations |
|
||||
| `meta` | Custom metadata dictionary |
|
||||
|
||||
**Argument-level options** (via `ArgTransform` or `ArgTransformConfig`):
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `name` | Rename the argument |
|
||||
| `description` | New description for the argument |
|
||||
| `default` | New default value |
|
||||
| `default_factory` | Callable that generates a default (requires `hide=True`) |
|
||||
| `hide` | Remove from client-visible schema |
|
||||
| `required` | Make an optional argument required |
|
||||
| `type` | Change the argument's type |
|
||||
| `examples` | Example values for the argument |
|
||||
|
||||
## Hiding Arguments
|
||||
|
||||
Hide arguments to simplify the interface or inject values the client shouldn't control.
|
||||
|
||||
```python
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
# Hide with a constant value
|
||||
transform_args = {
|
||||
"api_key": ArgTransform(hide=True, default="secret-key"),
|
||||
}
|
||||
|
||||
# Hide with a dynamic value
|
||||
import uuid
|
||||
transform_args = {
|
||||
"request_id": ArgTransform(hide=True, default_factory=lambda: str(uuid.uuid4())),
|
||||
}
|
||||
```
|
||||
|
||||
Hidden arguments disappear from the tool's schema. The client never sees them, but the underlying function receives the configured value.
|
||||
|
||||
<Warning>
|
||||
`default_factory` requires `hide=True`. Visible arguments need static defaults that can be represented in JSON Schema.
|
||||
</Warning>
|
||||
|
||||
## Renaming Arguments
|
||||
|
||||
Rename arguments to make them more intuitive for LLMs or match your API conventions.
|
||||
|
||||
```python
|
||||
from fastmcp.tools import Tool, tool
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
@tool
|
||||
def search(q: str, n: int = 10) -> list[str]:
|
||||
"""Search for items."""
|
||||
return []
|
||||
|
||||
better_search = Tool.from_tool(
|
||||
search,
|
||||
transform_args={
|
||||
"q": ArgTransform(name="query", description="Search terms"),
|
||||
"n": ArgTransform(name="max_results", description="Maximum results to return"),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Custom Transform Functions
|
||||
|
||||
For advanced scenarios, provide a `transform_fn` that intercepts tool execution. The function can validate inputs, modify outputs, or add custom logic while still calling the original tool via `forward()`.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool, tool
|
||||
from fastmcp.tools.tool_transform import forward, ArgTransform
|
||||
|
||||
@tool
|
||||
def divide(a: float, b: float) -> float:
|
||||
"""Divide a by b."""
|
||||
return a / b
|
||||
|
||||
async def safe_divide(numerator: float, denominator: float) -> float:
|
||||
if denominator == 0:
|
||||
raise ValueError("Cannot divide by zero")
|
||||
return await forward(numerator=numerator, denominator=denominator)
|
||||
|
||||
safe_division = Tool.from_tool(
|
||||
divide,
|
||||
name="safe_divide",
|
||||
transform_fn=safe_divide,
|
||||
transform_args={
|
||||
"a": ArgTransform(name="numerator"),
|
||||
"b": ArgTransform(name="denominator"),
|
||||
},
|
||||
)
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
mcp.add_tool(safe_division)
|
||||
```
|
||||
|
||||
The `forward()` function handles argument mapping automatically. Call it with the transformed argument names, and it maps them back to the original function's parameters.
|
||||
|
||||
For direct access to the original function without mapping, use `forward_raw()` with the original parameter names.
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
title: Transforms
|
||||
sidebarTitle: Transforms
|
||||
title: Transforms Overview
|
||||
sidebarTitle: Overview
|
||||
description: Modify components as they flow through your server
|
||||
icon: wand-magic-sparkles
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
@ -21,243 +22,15 @@ Provider → [Transform A] → [Transform B] → Client
|
|||
|
||||
When listing components, transforms receive sequences and return transformed sequences—a pure function pattern. When getting a specific component by name, transforms use a middleware pattern with `call_next`, working in reverse: mapping the client's requested name back to the original, then transforming the result.
|
||||
|
||||
## Namespace
|
||||
## Built-in Transforms
|
||||
|
||||
The `Namespace` transform prefixes all component names, preventing conflicts when composing multiple servers.
|
||||
FastMCP provides several transforms for common use cases:
|
||||
|
||||
Tools and prompts receive an underscore-separated prefix. Resources and templates receive a path-segment prefix in their URIs.
|
||||
|
||||
| Component | 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}` |
|
||||
|
||||
The most common use is through the `mount()` method's `namespace` parameter.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
weather = FastMCP("Weather")
|
||||
calendar = FastMCP("Calendar")
|
||||
|
||||
@weather.tool
|
||||
def get_data() -> str:
|
||||
return "Weather data"
|
||||
|
||||
@calendar.tool
|
||||
def get_data() -> str:
|
||||
return "Calendar data"
|
||||
|
||||
# Without namespacing, these would conflict
|
||||
main = FastMCP("Main")
|
||||
main.mount(weather, namespace="weather")
|
||||
main.mount(calendar, namespace="calendar")
|
||||
|
||||
# Clients see: weather_get_data, calendar_get_data
|
||||
```
|
||||
|
||||
You can also apply namespacing directly using the `Namespace` transform.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import Namespace
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
# Namespace all components
|
||||
mcp.add_transform(Namespace("api"))
|
||||
|
||||
# Tool is now: api_greet
|
||||
```
|
||||
|
||||
## Tool Transformation
|
||||
|
||||
Tool transformation lets you modify tool schemas - renaming tools, changing descriptions, adjusting tags, and reshaping argument schemas. FastMCP provides two mechanisms that share the same configuration options but differ in timing.
|
||||
|
||||
**Deferred transformation** with `ToolTransform` applies modifications when tools flow through a transform chain. Use this for tools from mounted servers, proxies, or other providers where you don't control the source directly.
|
||||
|
||||
**Immediate transformation** with `Tool.from_tool()` creates a modified tool object right away. Use this when you have direct access to a tool and want to transform it before registration.
|
||||
|
||||
### ToolTransform
|
||||
|
||||
The `ToolTransform` class is a transform that modifies tools as they flow through a provider. Provide a dictionary mapping original tool names to their transformation configuration.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import ToolTransform
|
||||
from fastmcp.tools.tool_transform import ToolTransformConfig
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
|
||||
@mcp.tool
|
||||
def verbose_internal_data_fetcher(query: str) -> str:
|
||||
"""Fetches data from the internal database."""
|
||||
return f"Results for: {query}"
|
||||
|
||||
# Rename the tool to something simpler
|
||||
mcp.add_transform(ToolTransform({
|
||||
"verbose_internal_data_fetcher": ToolTransformConfig(
|
||||
name="search",
|
||||
description="Search the database.",
|
||||
)
|
||||
}))
|
||||
|
||||
# Clients see "search" with the cleaner description
|
||||
```
|
||||
|
||||
`ToolTransform` is useful when you want to modify tools from mounted or proxied servers without changing the original source.
|
||||
|
||||
### Tool.from_tool()
|
||||
|
||||
Use `Tool.from_tool()` when you have the tool object and want to create a transformed version for registration.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool, tool
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
# Create a tool without registering it
|
||||
@tool
|
||||
def search(q: str, limit: int = 10) -> list[str]:
|
||||
"""Search for items."""
|
||||
return [f"Result {i} for {q}" for i in range(limit)]
|
||||
|
||||
# Transform it before registration
|
||||
better_search = Tool.from_tool(
|
||||
search,
|
||||
name="find_items",
|
||||
description="Find items matching your search query.",
|
||||
transform_args={
|
||||
"q": ArgTransform(
|
||||
name="query",
|
||||
description="The search terms to look for.",
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
mcp.add_tool(better_search)
|
||||
```
|
||||
|
||||
The standalone `@tool` decorator (from `fastmcp.tools`) creates a Tool object without registering it to any server. This separates creation from registration, letting you transform tools before deciding where they go.
|
||||
|
||||
### Modification Options
|
||||
|
||||
Both mechanisms support the same modifications.
|
||||
|
||||
**Tool-level options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `name` | New name for the tool |
|
||||
| `description` | New description |
|
||||
| `title` | Human-readable title |
|
||||
| `tags` | Set of tags for categorization |
|
||||
| `annotations` | MCP ToolAnnotations |
|
||||
| `meta` | Custom metadata dictionary |
|
||||
|
||||
**Argument-level options** (via `ArgTransform` or `ArgTransformConfig`):
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `name` | Rename the argument |
|
||||
| `description` | New description for the argument |
|
||||
| `default` | New default value |
|
||||
| `default_factory` | Callable that generates a default (requires `hide=True`) |
|
||||
| `hide` | Remove from client-visible schema |
|
||||
| `required` | Make an optional argument required |
|
||||
| `type` | Change the argument's type |
|
||||
| `examples` | Example values for the argument |
|
||||
|
||||
### Hiding Arguments
|
||||
|
||||
Hide arguments to simplify the interface or inject values the client shouldn't control.
|
||||
|
||||
```python
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
# Hide with a constant value
|
||||
transform_args = {
|
||||
"api_key": ArgTransform(hide=True, default="secret-key"),
|
||||
}
|
||||
|
||||
# Hide with a dynamic value
|
||||
import uuid
|
||||
transform_args = {
|
||||
"request_id": ArgTransform(hide=True, default_factory=lambda: str(uuid.uuid4())),
|
||||
}
|
||||
```
|
||||
|
||||
Hidden arguments disappear from the tool's schema. The client never sees them, but the underlying function receives the configured value.
|
||||
|
||||
<Warning>
|
||||
`default_factory` requires `hide=True`. Visible arguments need static defaults that can be represented in JSON Schema.
|
||||
</Warning>
|
||||
|
||||
### Renaming Arguments
|
||||
|
||||
Rename arguments to make them more intuitive for LLMs or match your API conventions.
|
||||
|
||||
```python
|
||||
from fastmcp.tools import Tool, tool
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
@tool
|
||||
def search(q: str, n: int = 10) -> list[str]:
|
||||
"""Search for items."""
|
||||
return []
|
||||
|
||||
better_search = Tool.from_tool(
|
||||
search,
|
||||
transform_args={
|
||||
"q": ArgTransform(name="query", description="Search terms"),
|
||||
"n": ArgTransform(name="max_results", description="Maximum results to return"),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Custom Transform Functions
|
||||
|
||||
For advanced scenarios, provide a `transform_fn` that intercepts tool execution. The function can validate inputs, modify outputs, or add custom logic while still calling the original tool via `forward()`.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool, tool
|
||||
from fastmcp.tools.tool_transform import forward, ArgTransform
|
||||
|
||||
@tool
|
||||
def divide(a: float, b: float) -> float:
|
||||
"""Divide a by b."""
|
||||
return a / b
|
||||
|
||||
async def safe_divide(numerator: float, denominator: float) -> float:
|
||||
if denominator == 0:
|
||||
raise ValueError("Cannot divide by zero")
|
||||
return await forward(numerator=numerator, denominator=denominator)
|
||||
|
||||
safe_division = Tool.from_tool(
|
||||
divide,
|
||||
name="safe_divide",
|
||||
transform_fn=safe_divide,
|
||||
transform_args={
|
||||
"a": ArgTransform(name="numerator"),
|
||||
"b": ArgTransform(name="denominator"),
|
||||
},
|
||||
)
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
mcp.add_tool(safe_division)
|
||||
```
|
||||
|
||||
The `forward()` function handles argument mapping automatically. Call it with the transformed argument names, and it maps them back to the original function's parameters.
|
||||
|
||||
For direct access to the original function without mapping, use `forward_raw()` with the original parameter names.
|
||||
- **[Namespace](/servers/providers/namespace)** - Prefix component names to prevent conflicts when composing servers
|
||||
- **[Tool Transformation](/servers/providers/tool-transformation)** - Rename tools, modify descriptions, reshape arguments
|
||||
- **[Enabled](/servers/enabled)** - Control which components are visible at runtime
|
||||
- **[Resources as Tools](/servers/providers/resources-as-tools)** - Expose resources to tool-only clients
|
||||
- **[Prompts as Tools](/servers/providers/prompts-as-tools)** - Expose prompts to tool-only clients
|
||||
|
||||
## Server vs Provider Transforms
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
---
|
||||
title: LLM Sampling
|
||||
title: Sampling
|
||||
sidebarTitle: Sampling
|
||||
description: Request LLM text generation from the client or a configured provider through the MCP context.
|
||||
icon: robot
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
|
|
@ -14,6 +13,417 @@ LLM sampling allows your MCP tools to request text generation from an LLM during
|
|||
|
||||
By default, sampling requests are routed to the client's LLM. You can also configure a fallback handler to use a specific provider (like OpenAI) when the client doesn't support sampling, or to always use your own LLM regardless of client capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
The simplest use of sampling is passing a prompt string to `ctx.sample()`. The method sends the prompt to the LLM, waits for the complete response, and returns a `SamplingResult`. You can access the generated text through the `.text` attribute.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def summarize(content: str, ctx: Context) -> str:
|
||||
"""Generate a summary of the provided content."""
|
||||
result = await ctx.sample(f"Please summarize this:\n\n{content}")
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
The `SamplingResult` also provides `.result` (identical to `.text` for plain text responses) and `.history` containing the full message exchange—useful if you need to continue the conversation or debug the interaction.
|
||||
|
||||
### System Prompts
|
||||
|
||||
System prompts let you establish the LLM's role and behavioral guidelines before it processes your request. This is useful for controlling tone, enforcing constraints, or providing context that shouldn't clutter the user-facing prompt.
|
||||
|
||||
````python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def generate_code(concept: str, ctx: Context) -> str:
|
||||
"""Generate a Python code example for a concept."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Write a Python example demonstrating '{concept}'.",
|
||||
system_prompt=(
|
||||
"You are an expert Python programmer. "
|
||||
"Provide concise, working code without explanations."
|
||||
),
|
||||
temperature=0.7,
|
||||
max_tokens=300
|
||||
)
|
||||
return f"```python\n{result.text}\n```"
|
||||
````
|
||||
|
||||
The `temperature` parameter controls randomness—higher values (up to 1.0) produce more varied outputs, while lower values make responses more deterministic. The `max_tokens` parameter limits response length.
|
||||
|
||||
### Model Preferences
|
||||
|
||||
Model preferences let you hint at which LLM the client should use for a request. You can pass a single model name or a list of preferences in priority order. These are hints rather than requirements—the actual model used depends on what the client has available.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def technical_analysis(data: str, ctx: Context) -> str:
|
||||
"""Analyze data using a reasoning-focused model."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Analyze this data:\n\n{data}",
|
||||
model_preferences=["claude-opus-4-5", "gpt-5-2"],
|
||||
temperature=0.2,
|
||||
)
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
Use model preferences when different tasks benefit from different model characteristics. Creative writing might prefer faster models with higher temperature, while complex analysis might benefit from larger reasoning-focused models.
|
||||
|
||||
### Multi-Turn Conversations
|
||||
|
||||
For requests that need conversational context, construct a list of `SamplingMessage` objects representing the conversation history. Each message has a `role` ("user" or "assistant") and `content` (a `TextContent` object).
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingMessage, TextContent
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def contextual_analysis(query: str, data: str, ctx: Context) -> str:
|
||||
"""Analyze data with conversational context."""
|
||||
messages = [
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=TextContent(type="text", text=f"Here's my data: {data}"),
|
||||
),
|
||||
SamplingMessage(
|
||||
role="assistant",
|
||||
content=TextContent(type="text", text="I see the data. What would you like to know?"),
|
||||
),
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=TextContent(type="text", text=query),
|
||||
),
|
||||
]
|
||||
result = await ctx.sample(messages=messages)
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
The LLM receives the full conversation thread and responds with awareness of the preceding context.
|
||||
|
||||
### Fallback Handlers
|
||||
|
||||
Client support for sampling is optional—some clients may not implement it. To ensure your tools work regardless of client capabilities, configure a `sampling_handler` that sends requests directly to an LLM provider.
|
||||
|
||||
FastMCP provides built-in handlers for [OpenAI and Anthropic APIs](/clients/sampling#built-in-handlers). These handlers support the full sampling API including tools, automatically converting your Python functions to each provider's format.
|
||||
|
||||
<Note>
|
||||
Install handlers with `pip install fastmcp[openai]` or `pip install fastmcp[anthropic]`.
|
||||
</Note>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler
|
||||
|
||||
server = FastMCP(
|
||||
name="My Server",
|
||||
sampling_handler=OpenAISamplingHandler(default_model="gpt-4o-mini"),
|
||||
sampling_handler_behavior="fallback",
|
||||
)
|
||||
```
|
||||
|
||||
The `sampling_handler_behavior` parameter controls when the handler is used:
|
||||
|
||||
- **`"fallback"`** (default): Use the handler only when the client doesn't support sampling. This lets capable clients use their own LLM while ensuring your tools still work with clients that lack sampling support.
|
||||
- **`"always"`**: Always use the handler, bypassing the client entirely. Use this when you need guaranteed control over which LLM processes requests—for cost control, compliance requirements, or when specific model characteristics are essential.
|
||||
|
||||
## Structured Output
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
When you need validated, typed data instead of free-form text, use the `result_type` parameter. FastMCP ensures the LLM returns data matching your type, handling validation and retries automatically.
|
||||
|
||||
The `result_type` parameter accepts Pydantic models, dataclasses, and basic types like `int`, `list[str]`, or `dict[str, int]`. When you specify a result type, FastMCP automatically creates a `final_response` tool that the LLM calls to provide its response. If validation fails, the error is sent back to the LLM for retry.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
class SentimentResult(BaseModel):
|
||||
sentiment: str
|
||||
confidence: float
|
||||
reasoning: str
|
||||
|
||||
@mcp.tool
|
||||
async def analyze_sentiment(text: str, ctx: Context) -> SentimentResult:
|
||||
"""Analyze text sentiment with structured output."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Analyze the sentiment of: {text}",
|
||||
result_type=SentimentResult,
|
||||
)
|
||||
return result.result # A validated SentimentResult object
|
||||
```
|
||||
|
||||
When you call this tool, the LLM returns a structured response that FastMCP validates against your Pydantic model. You access the validated object through `result.result`, while `result.text` contains the JSON representation.
|
||||
|
||||
### Structured Output with Tools
|
||||
|
||||
Combine structured output with tools for agentic workflows that return validated data. The LLM uses your tools to gather information, then returns a response matching your type.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
def search(query: str) -> str:
|
||||
"""Search the web for information."""
|
||||
return f"Results for: {query}"
|
||||
|
||||
def fetch_url(url: str) -> str:
|
||||
"""Fetch content from a URL."""
|
||||
return f"Content from: {url}"
|
||||
|
||||
class ResearchResult(BaseModel):
|
||||
summary: str
|
||||
sources: list[str]
|
||||
confidence: float
|
||||
|
||||
@mcp.tool
|
||||
async def research(topic: str, ctx: Context) -> ResearchResult:
|
||||
"""Research a topic and return structured findings."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Research: {topic}",
|
||||
tools=[search, fetch_url],
|
||||
result_type=ResearchResult,
|
||||
)
|
||||
return result.result
|
||||
```
|
||||
|
||||
<Note>
|
||||
Structured output with automatic validation only applies to `sample()`. With `sample_step()`, you must manage structured output yourself.
|
||||
</Note>
|
||||
|
||||
## Tool Use
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
Sampling with tools enables agentic workflows where the LLM can call functions to gather information before responding. This implements [SEP-1577](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577), allowing the LLM to autonomously orchestrate multi-step operations.
|
||||
|
||||
Pass Python functions to the `tools` parameter, and FastMCP handles the execution loop automatically—calling tools, returning results to the LLM, and continuing until the LLM provides a final response.
|
||||
|
||||
### Defining Tools
|
||||
|
||||
Define regular Python functions with type hints and docstrings. FastMCP extracts the function's name, docstring, and parameter types to create tool schemas that the LLM can understand.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
def search(query: str) -> str:
|
||||
"""Search the web for information."""
|
||||
return f"Results for: {query}"
|
||||
|
||||
def get_time() -> str:
|
||||
"""Get the current time."""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def research(question: str, ctx: Context) -> str:
|
||||
"""Answer questions using available tools."""
|
||||
result = await ctx.sample(
|
||||
messages=question,
|
||||
tools=[search, get_time],
|
||||
)
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
The LLM sees each function's signature and docstring, using this information to decide when and how to call them. Tool errors are caught and sent back to the LLM, allowing it to recover gracefully. An internal safety limit prevents infinite loops.
|
||||
|
||||
### Custom Tool Definitions
|
||||
|
||||
For custom names or descriptions, use `SamplingTool.from_function()`:
|
||||
|
||||
```python
|
||||
from fastmcp.server.sampling import SamplingTool
|
||||
|
||||
tool = SamplingTool.from_function(
|
||||
my_func,
|
||||
name="custom_name",
|
||||
description="Custom description"
|
||||
)
|
||||
|
||||
result = await ctx.sample(messages="...", tools=[tool])
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
By default, when a sampling tool raises an exception, the error message (including details) is sent back to the LLM so it can attempt recovery. To prevent sensitive information from leaking to the LLM, use the `mask_error_details` parameter:
|
||||
|
||||
```python
|
||||
result = await ctx.sample(
|
||||
messages=question,
|
||||
tools=[search],
|
||||
mask_error_details=True, # Generic error messages only
|
||||
)
|
||||
```
|
||||
|
||||
When `mask_error_details=True`, tool errors become generic messages like `"Error executing tool 'search'"` instead of exposing stack traces or internal details.
|
||||
|
||||
To intentionally provide specific error messages to the LLM regardless of masking, raise `ToolError`:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
def search(query: str) -> str:
|
||||
"""Search for information."""
|
||||
if not query.strip():
|
||||
raise ToolError("Search query cannot be empty")
|
||||
return f"Results for: {query}"
|
||||
```
|
||||
|
||||
`ToolError` messages always pass through to the LLM, making it the escape hatch for errors you want the LLM to see and handle.
|
||||
|
||||
### Client Requirements
|
||||
|
||||
<Note>
|
||||
Sampling with tools requires the client to advertise the `sampling.tools` capability. FastMCP clients do this automatically. For external clients that don't support tool-enabled sampling, configure a fallback handler with `sampling_handler_behavior="always"`.
|
||||
</Note>
|
||||
|
||||
## Advanced Control
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
While `sample()` handles the tool execution loop automatically, some scenarios require fine-grained control over each step. The `sample_step()` method makes a single LLM call and returns a `SampleStep` containing the response and updated history.
|
||||
|
||||
Unlike `sample()`, `sample_step()` is stateless—it doesn't remember previous calls. You control the conversation by passing the full message history each time. The returned `step.history` includes all messages up through the current response, making it easy to continue the loop.
|
||||
|
||||
Use `sample_step()` when you need to:
|
||||
|
||||
- Inspect tool calls before they execute
|
||||
- Implement custom termination conditions
|
||||
- Add logging, metrics, or checkpointing between steps
|
||||
- Build custom agentic loops with domain-specific logic
|
||||
|
||||
### Basic Loop
|
||||
|
||||
By default, `sample_step()` executes any tool calls and includes the results in the history. Call it in a loop, passing the updated history each time, until a stop condition is met.
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingMessage
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
def search(query: str) -> str:
|
||||
return f"Results for: {query}"
|
||||
|
||||
def get_time() -> str:
|
||||
return "12:00 PM"
|
||||
|
||||
@mcp.tool
|
||||
async def controlled_agent(question: str, ctx: Context) -> str:
|
||||
"""Agent with manual loop control."""
|
||||
messages: list[str | SamplingMessage] = [question]
|
||||
|
||||
while True:
|
||||
step = await ctx.sample_step(
|
||||
messages=messages,
|
||||
tools=[search, get_time],
|
||||
)
|
||||
|
||||
if step.is_tool_use:
|
||||
# Tools already executed (execute_tools=True by default)
|
||||
for call in step.tool_calls:
|
||||
print(f"Called tool: {call.name}")
|
||||
|
||||
if not step.is_tool_use:
|
||||
return step.text or ""
|
||||
|
||||
messages = step.history
|
||||
```
|
||||
|
||||
### SampleStep Properties
|
||||
|
||||
Each `SampleStep` provides information about what the LLM returned:
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `step.is_tool_use` | True if the LLM requested tool calls |
|
||||
| `step.tool_calls` | List of tool calls requested (if any) |
|
||||
| `step.text` | The text content (if any) |
|
||||
| `step.history` | All messages exchanged so far |
|
||||
|
||||
The contents of `step.history` depend on `execute_tools`:
|
||||
- **`execute_tools=True`** (default): Includes tool results, ready for the next iteration
|
||||
- **`execute_tools=False`**: Includes the assistant's tool request, but you add results yourself
|
||||
|
||||
### Manual Tool Execution
|
||||
|
||||
Set `execute_tools=False` to handle tool execution yourself. When disabled, `step.history` contains the user message and the assistant's response with tool calls—but no tool results. You execute the tools and append the results as a user message.
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingMessage, ToolResultContent, TextContent
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def research(question: str, ctx: Context) -> str:
|
||||
"""Research with manual tool handling."""
|
||||
|
||||
def search(query: str) -> str:
|
||||
return f"Results for: {query}"
|
||||
|
||||
def get_time() -> str:
|
||||
return "12:00 PM"
|
||||
|
||||
tools = {"search": search, "get_time": get_time}
|
||||
messages: list[SamplingMessage] = [question]
|
||||
|
||||
while True:
|
||||
step = await ctx.sample_step(
|
||||
messages=messages,
|
||||
tools=list(tools.values()),
|
||||
execute_tools=False,
|
||||
)
|
||||
|
||||
if not step.is_tool_use:
|
||||
return step.text or ""
|
||||
|
||||
# Execute tools and collect results
|
||||
tool_results = []
|
||||
for call in step.tool_calls:
|
||||
fn = tools[call.name]
|
||||
result = fn(**call.input)
|
||||
tool_results.append(
|
||||
ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=call.id,
|
||||
content=[TextContent(type="text", text=result)],
|
||||
)
|
||||
)
|
||||
|
||||
messages = list(step.history)
|
||||
messages.append(SamplingMessage(role="user", content=tool_results))
|
||||
```
|
||||
|
||||
To report an error to the LLM, set `isError=True` on the tool result:
|
||||
|
||||
```python
|
||||
tool_result = ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=call.id,
|
||||
content=[TextContent(type="text", text="Permission denied")],
|
||||
isError=True,
|
||||
)
|
||||
```
|
||||
|
||||
## Method Reference
|
||||
|
||||
<Card icon="code" title="ctx.sample()">
|
||||
|
|
@ -70,7 +480,25 @@ By default, sampling requests are routed to the client's LLM. You can also confi
|
|||
Make a single LLM sampling call. Use this for fine-grained control over the sampling loop.
|
||||
|
||||
<Expandable title="Parameters">
|
||||
Same as `sample()`, plus:
|
||||
<ResponseField name="messages" type="str | list[str | SamplingMessage]">
|
||||
The prompt or conversation history.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="system_prompt" type="str | None" default="None">
|
||||
Instructions that establish the LLM's role and behavior.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="temperature" type="float | None" default="None">
|
||||
Controls randomness (0.0 = deterministic, 1.0 = creative).
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="max_tokens" type="int | None" default="512">
|
||||
Maximum tokens to generate.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="tools" type="list[Callable] | None" default="None">
|
||||
Functions the LLM can call during sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="tool_choice" type="str | None" default="None">
|
||||
Controls tool usage: `"auto"`, `"required"`, or `"none"`.
|
||||
|
|
@ -81,9 +509,8 @@ By default, sampling requests are routed to the client's LLM. You can also confi
|
|||
</ResponseField>
|
||||
|
||||
<ResponseField name="mask_error_details" type="bool | None" default="None">
|
||||
If True, mask detailed error messages from tool execution. When None (default), uses the global `settings.mask_error_details` value. Tools can raise `ToolError` to bypass masking.
|
||||
If True, mask detailed error messages from tool execution.
|
||||
</ResponseField>
|
||||
|
||||
</Expandable>
|
||||
|
||||
<Expandable title="Response">
|
||||
|
|
@ -97,392 +524,3 @@ By default, sampling requests are routed to the client's LLM. You can also confi
|
|||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Basic Sampling
|
||||
|
||||
The simplest use of sampling is passing a prompt string to `ctx.sample()`. The method sends the prompt to the LLM, waits for the complete response, and returns a `SamplingResult`. You can access the generated text through the `.text` attribute.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def summarize(content: str, ctx: Context) -> str:
|
||||
"""Generate a summary of the provided content."""
|
||||
result = await ctx.sample(f"Please summarize this:\n\n{content}")
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
The `SamplingResult` also provides `.result` (identical to `.text` for plain text responses) and `.history` containing the full message exchange—useful if you need to continue the conversation or debug the interaction.
|
||||
|
||||
### System Prompts
|
||||
|
||||
System prompts let you establish the LLM's role and behavioral guidelines before it processes your request. This is useful for controlling tone, enforcing constraints, or providing context that shouldn't clutter the user-facing prompt.
|
||||
|
||||
````python
|
||||
@mcp.tool
|
||||
async def generate_code(concept: str, ctx: Context) -> str:
|
||||
"""Generate a Python code example for a concept."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Write a Python example demonstrating '{concept}'.",
|
||||
system_prompt=(
|
||||
"You are an expert Python programmer. "
|
||||
"Provide concise, working code without explanations."
|
||||
),
|
||||
temperature=0.7,
|
||||
max_tokens=300
|
||||
)
|
||||
return f"```python\n{result.text}\n```"
|
||||
````
|
||||
|
||||
The `temperature` parameter controls randomness—higher values (up to 1.0) produce more varied outputs, while lower values make responses more deterministic. The `max_tokens` parameter limits response length.
|
||||
|
||||
### Model Preferences
|
||||
|
||||
Model preferences let you hint at which LLM the client should use for a request. You can pass a single model name or a list of preferences in priority order. These are hints rather than requirements—the actual model used depends on what the client has available.
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def technical_analysis(data: str, ctx: Context) -> str:
|
||||
"""Analyze data using a reasoning-focused model."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Analyze this data:\n\n{data}",
|
||||
model_preferences=["claude-opus-4-5", "gpt-5-2"],
|
||||
temperature=0.2,
|
||||
)
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
Use model preferences when different tasks benefit from different model characteristics. Creative writing might prefer faster models with higher temperature, while complex analysis might benefit from larger reasoning-focused models.
|
||||
|
||||
### Multi-Turn Conversations
|
||||
|
||||
For requests that need conversational context, construct a list of `SamplingMessage` objects representing the conversation history. Each message has a `role` ("user" or "assistant") and `content` (a `TextContent` object).
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingMessage, TextContent
|
||||
|
||||
@mcp.tool
|
||||
async def contextual_analysis(query: str, data: str, ctx: Context) -> str:
|
||||
"""Analyze data with conversational context."""
|
||||
messages = [
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=TextContent(type="text", text=f"Here's my data: {data}"),
|
||||
),
|
||||
SamplingMessage(
|
||||
role="assistant",
|
||||
content=TextContent(type="text", text="I see the data. What would you like to know?"),
|
||||
),
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=TextContent(type="text", text=query),
|
||||
),
|
||||
]
|
||||
result = await ctx.sample(messages=messages)
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
The LLM receives the full conversation thread and responds with awareness of the preceding context.
|
||||
|
||||
## Structured Output
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
When you need validated, typed data instead of free-form text, use the `result_type` parameter. FastMCP ensures the LLM returns data matching your type, handling validation and retries automatically. The `result_type` parameter accepts Pydantic models, dataclasses, and basic types like `int`, `list[str]`, or `dict[str, int]`.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
class SentimentResult(BaseModel):
|
||||
sentiment: str
|
||||
confidence: float
|
||||
reasoning: str
|
||||
|
||||
@mcp.tool
|
||||
async def analyze_sentiment(text: str, ctx: Context) -> SentimentResult:
|
||||
"""Analyze text sentiment with structured output."""
|
||||
result = await ctx.sample(
|
||||
messages=f"Analyze the sentiment of: {text}",
|
||||
result_type=SentimentResult,
|
||||
)
|
||||
return result.result # A validated SentimentResult object
|
||||
```
|
||||
|
||||
When you call this tool, the LLM returns a structured response that FastMCP validates against your Pydantic model. You access the validated object through `result.result`, while `result.text` contains the JSON representation.
|
||||
|
||||
<Note>
|
||||
When you pass `result_type`, `sample()` automatically creates a
|
||||
`final_response` tool that the LLM calls to provide its response. If
|
||||
validation fails, the error is sent back to the LLM for retry. This automatic
|
||||
handling only applies to `sample()`—with `sample_step()`, you must manage
|
||||
structured output yourself.
|
||||
</Note>
|
||||
|
||||
## Sampling with Tools
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
Sampling with tools enables agentic workflows where the LLM can call functions to gather information before responding. This implements [SEP-1577](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577), allowing the LLM to autonomously orchestrate multi-step operations.
|
||||
|
||||
Pass Python functions to the `tools` parameter, and FastMCP handles the execution loop automatically—calling tools, returning results to the LLM, and continuing until the LLM provides a final response.
|
||||
|
||||
### Defining Tools
|
||||
|
||||
Define regular Python functions with type hints and docstrings. FastMCP extracts the function's name, docstring, and parameter types to create tool schemas that the LLM can understand.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
def search(query: str) -> str:
|
||||
"""Search the web for information."""
|
||||
return f"Results for: {query}"
|
||||
|
||||
def get_time() -> str:
|
||||
"""Get the current time."""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def research(question: str, ctx: Context) -> str:
|
||||
"""Answer questions using available tools."""
|
||||
result = await ctx.sample(
|
||||
messages=question,
|
||||
tools=[search, get_time],
|
||||
)
|
||||
return result.text or ""
|
||||
```
|
||||
|
||||
The LLM sees each function's signature and docstring, using this information to decide when and how to call them. Tool errors are caught and sent back to the LLM, allowing it to recover gracefully. An internal safety limit prevents infinite loops.
|
||||
|
||||
### Tool Error Handling
|
||||
|
||||
By default, when a sampling tool raises an exception, the error message (including details) is sent back to the LLM so it can attempt recovery. To prevent sensitive information from leaking to the LLM, use the `mask_error_details` parameter:
|
||||
|
||||
```python
|
||||
result = await ctx.sample(
|
||||
messages=question,
|
||||
tools=[search],
|
||||
mask_error_details=True, # Generic error messages only
|
||||
)
|
||||
```
|
||||
|
||||
When `mask_error_details=True`, tool errors become generic messages like `"Error executing tool 'search'"` instead of exposing stack traces or internal details.
|
||||
|
||||
To intentionally provide specific error messages to the LLM regardless of masking, raise `ToolError`:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
def search(query: str) -> str:
|
||||
"""Search for information."""
|
||||
if not query.strip():
|
||||
raise ToolError("Search query cannot be empty")
|
||||
return f"Results for: {query}"
|
||||
```
|
||||
|
||||
`ToolError` messages always pass through to the LLM, making it the escape hatch for errors you want the LLM to see and handle.
|
||||
|
||||
For custom names or descriptions, use `SamplingTool.from_function()`:
|
||||
|
||||
```python
|
||||
from fastmcp.server.sampling import SamplingTool
|
||||
|
||||
tool = SamplingTool.from_function(
|
||||
my_func,
|
||||
name="custom_name",
|
||||
description="Custom description"
|
||||
)
|
||||
|
||||
result = await ctx.sample(messages="...", tools=[tool])
|
||||
```
|
||||
|
||||
### Combining with Structured Output
|
||||
|
||||
Combine tools with `result_type` for agentic workflows that return validated, structured data. The LLM uses your tools to gather information, then returns a response matching your type.
|
||||
|
||||
```python
|
||||
result = await ctx.sample(
|
||||
messages="Research Python async patterns",
|
||||
tools=[search, fetch_url],
|
||||
result_type=ResearchResult,
|
||||
)
|
||||
```
|
||||
|
||||
## Loop Control
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
While `sample()` handles the tool execution loop automatically, some scenarios require fine-grained control over each step. The `sample_step()` method makes a single LLM call and returns a `SampleStep` containing the response and updated history.
|
||||
|
||||
Unlike `sample()`, `sample_step()` is stateless—it doesn't remember previous calls. You control the conversation by passing the full message history each time. The returned `step.history` includes all messages up through the current response, making it easy to continue the loop.
|
||||
|
||||
Use `sample_step()` when you need to:
|
||||
|
||||
- Inspect tool calls before they execute
|
||||
- Implement custom termination conditions
|
||||
- Add logging, metrics, or checkpointing between steps
|
||||
- Build custom agentic loops with domain-specific logic
|
||||
|
||||
### Using sample_step()
|
||||
|
||||
By default, `sample_step()` executes any tool calls and includes the results in the history. Call it in a loop, passing the updated history each time, until a stop condition is met.
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingMessage
|
||||
|
||||
@mcp.tool
|
||||
async def controlled_agent(question: str, ctx: Context) -> str:
|
||||
"""Agent with manual loop control."""
|
||||
messages: list[str | SamplingMessage] = [question] # strings auto-convert
|
||||
|
||||
while True:
|
||||
step = await ctx.sample_step(
|
||||
messages=messages,
|
||||
tools=[search, get_time],
|
||||
)
|
||||
|
||||
if step.is_tool_use:
|
||||
# Tools already executed (execute_tools=True by default)
|
||||
# Log what was called before continuing
|
||||
for call in step.tool_calls:
|
||||
print(f"Called tool: {call.name}")
|
||||
|
||||
if not step.is_tool_use:
|
||||
return step.text or ""
|
||||
|
||||
# Continue with updated history
|
||||
messages = step.history
|
||||
```
|
||||
|
||||
### SampleStep Properties
|
||||
|
||||
Each `SampleStep` provides information about what the LLM returned:
|
||||
|
||||
- `step.is_tool_use` — True if the LLM requested tool calls
|
||||
- `step.tool_calls` — List of tool calls requested (if any)
|
||||
- `step.text` — The text content (if any)
|
||||
- `step.history` — All messages exchanged so far
|
||||
|
||||
The contents of `step.history` depend on `execute_tools`:
|
||||
- **`execute_tools=True`** (default): Includes tool results, ready for the next iteration
|
||||
- **`execute_tools=False`**: Includes the assistant's tool request, but you add results yourself
|
||||
|
||||
### Manual Tool Execution
|
||||
|
||||
Set `execute_tools=False` to handle tool execution yourself. When disabled, `step.history` contains the user message and the assistant's response with tool calls—but no tool results. You execute the tools and append the results as a user message.
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingMessage, ToolResultContent, TextContent
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
async def research(question: str, ctx: Context) -> str:
|
||||
"""Research with manual tool handling."""
|
||||
|
||||
def search(query: str) -> str:
|
||||
return f"Results for: {query}"
|
||||
|
||||
def get_time() -> str:
|
||||
return "12:00 PM"
|
||||
|
||||
# Map tool names to functions
|
||||
tools = {"search": search, "get_time": get_time}
|
||||
|
||||
messages: list[SamplingMessage] = [question] # strings are converted automatically
|
||||
|
||||
while True:
|
||||
step = await ctx.sample_step(
|
||||
messages=messages,
|
||||
tools=list(tools.values()),
|
||||
execute_tools=False,
|
||||
)
|
||||
|
||||
if not step.is_tool_use:
|
||||
return step.text or ""
|
||||
|
||||
# Execute tools and collect results
|
||||
tool_results = []
|
||||
for call in step.tool_calls:
|
||||
fn = tools[call.name]
|
||||
result = fn(**call.input)
|
||||
tool_results.append(
|
||||
ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=call.id,
|
||||
content=[TextContent(type="text", text=result)],
|
||||
)
|
||||
)
|
||||
|
||||
messages = list(step.history)
|
||||
messages.append(SamplingMessage(role="user", content=tool_results))
|
||||
```
|
||||
|
||||
#### Error Handling
|
||||
|
||||
To report an error, set `isError=True`. The LLM will see the error and can decide how to proceed:
|
||||
|
||||
```python
|
||||
tool_result = ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=call.id,
|
||||
content=[TextContent(type="text", text="Permission denied")],
|
||||
isError=True,
|
||||
)
|
||||
```
|
||||
|
||||
## Fallback Handlers
|
||||
|
||||
Client support for sampling is optional—some clients may not implement it. To ensure your tools work regardless of client capabilities, configure a `sampling_handler` that sends requests directly to an LLM provider.
|
||||
|
||||
FastMCP provides built-in handlers for [OpenAI and Anthropic APIs](/clients/sampling#built-in-handlers). These handlers support the full sampling API including tools, automatically converting your Python functions to each provider's format.
|
||||
|
||||
<Note>
|
||||
Install handlers with `pip install fastmcp[openai]` or `pip install fastmcp[anthropic]`.
|
||||
</Note>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler
|
||||
|
||||
server = FastMCP(
|
||||
name="My Server",
|
||||
sampling_handler=OpenAISamplingHandler(default_model="gpt-4o-mini"),
|
||||
sampling_handler_behavior="fallback",
|
||||
)
|
||||
```
|
||||
|
||||
Or with Anthropic:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler
|
||||
|
||||
server = FastMCP(
|
||||
name="My Server",
|
||||
sampling_handler=AnthropicSamplingHandler(default_model="claude-sonnet-4-5"),
|
||||
sampling_handler_behavior="fallback",
|
||||
)
|
||||
```
|
||||
|
||||
### Behavior Modes
|
||||
|
||||
The `sampling_handler_behavior` parameter controls when the handler is used:
|
||||
|
||||
- **`"fallback"`** (default): Use the handler only when the client doesn't support sampling. This lets capable clients use their own LLM while ensuring your tools still work with clients that lack sampling support.
|
||||
- **`"always"`**: Always use the handler, bypassing the client entirely. Use this when you need guaranteed control over which LLM processes requests—for cost control, compliance requirements, or when specific model characteristics are essential.
|
||||
|
||||
<Note>
|
||||
Sampling with tools requires the client to advertise the `sampling.tools`
|
||||
capability. FastMCP clients do this automatically. For external clients that
|
||||
don't support tool-enabled sampling, configure a fallback handler with
|
||||
`sampling_handler_behavior="always"`.
|
||||
</Note>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
---
|
||||
title: The FastMCP Server
|
||||
sidebarTitle: Overview
|
||||
description: The core FastMCP server class for building MCP applications with tools, resources, and prompts.
|
||||
description: The core FastMCP server class for building MCP applications
|
||||
icon: server
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
The central piece of a FastMCP application is the `FastMCP` server class. This class acts as the main container for your application's tools, resources, and prompts, and manages communication with MCP clients.
|
||||
The `FastMCP` class is the central piece of every FastMCP application. It acts as the container for your tools, resources, and prompts, managing communication with MCP clients and orchestrating the entire server lifecycle.
|
||||
|
||||
## Creating a Server
|
||||
|
||||
Instantiating a server is straightforward. You typically provide a name for your server, which helps identify it in client applications or logs.
|
||||
Instantiate a server by providing a name that identifies it in client applications and logs. You can also provide instructions that help clients understand the server's purpose.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Create a basic server instance
|
||||
mcp = FastMCP(name="MyAssistantServer")
|
||||
|
||||
# You can also add instructions for how to interact with the server
|
||||
# Instructions help clients understand how to interact with the server
|
||||
mcp_with_instructions = FastMCP(
|
||||
name="HelpfulAssistant",
|
||||
instructions="""
|
||||
|
|
@ -29,7 +28,7 @@ mcp_with_instructions = FastMCP(
|
|||
)
|
||||
```
|
||||
|
||||
The `FastMCP` constructor accepts several arguments:
|
||||
The `FastMCP` constructor accepts several configuration options. The most commonly used parameters control server identity, authentication, and component behavior.
|
||||
|
||||
<Card icon="code" title="FastMCP Constructor Parameters">
|
||||
<ParamField body="name" type="str" default="FastMCP">
|
||||
|
|
@ -92,7 +91,7 @@ The `FastMCP` constructor accepts several arguments:
|
|||
|
||||
<ParamField body="strict_input_validation" type="bool" default="False">
|
||||
<VersionBadge version="2.13.0" />
|
||||
Controls how tool input parameters are validated. When `False` (default), FastMCP uses Pydantic's flexible validation that coerces compatible inputs (e.g., `"10"` → `10` for int parameters). When `True`, uses the MCP SDK's JSON Schema validation to validate inputs against the exact schema before passing them to your function, rejecting any type mismatches. The default mode improves compatibility with LLM clients while maintaining type safety. See [Input Validation Modes](/servers/tools#input-validation-modes) for details
|
||||
Controls how tool input parameters are validated. When `False` (default), FastMCP uses Pydantic's flexible validation that coerces compatible inputs (e.g., `"10"` to `10` for int parameters). When `True`, uses the MCP SDK's JSON Schema validation to validate inputs against the exact schema before passing them to your function, rejecting any type mismatches. The default mode improves compatibility with LLM clients while maintaining type safety. See [Input Validation Modes](/servers/tools#input-validation-modes) for details
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="list_page_size" type="int | None" default="None">
|
||||
|
|
@ -101,13 +100,14 @@ The `FastMCP` constructor accepts several arguments:
|
|||
</ParamField>
|
||||
|
||||
</Card>
|
||||
|
||||
## Components
|
||||
|
||||
FastMCP servers expose several types of components to the client:
|
||||
FastMCP servers expose three types of components to clients. Each type serves a distinct purpose in the MCP protocol.
|
||||
|
||||
### Tools
|
||||
|
||||
Tools are functions that the client can call to perform actions or access external systems.
|
||||
Tools are functions that clients can invoke to perform actions or access external systems. They're the primary way clients interact with your server's capabilities.
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
|
|
@ -120,7 +120,7 @@ See [Tools](/servers/tools) for detailed documentation.
|
|||
|
||||
### Resources
|
||||
|
||||
Resources expose data sources that the client can read.
|
||||
Resources expose data that clients can read. Unlike tools, resources are passive data sources that clients pull from rather than invoke.
|
||||
|
||||
```python
|
||||
@mcp.resource("data://config")
|
||||
|
|
@ -129,25 +129,24 @@ def get_config() -> dict:
|
|||
return {"theme": "dark", "version": "1.0"}
|
||||
```
|
||||
|
||||
See [Resources & Templates](/servers/resources) for detailed documentation.
|
||||
See [Resources](/servers/resources) for detailed documentation.
|
||||
|
||||
### Resource Templates
|
||||
|
||||
Resource templates are parameterized resources that allow the client to request specific data.
|
||||
Resource templates are parameterized resources. The client provides values for template parameters in the URI, and the server returns data specific to those parameters.
|
||||
|
||||
```python
|
||||
@mcp.resource("users://{user_id}/profile")
|
||||
def get_user_profile(user_id: int) -> dict:
|
||||
"""Retrieves a user's profile by ID."""
|
||||
# The {user_id} in the URI is extracted and passed to this function
|
||||
return {"id": user_id, "name": f"User {user_id}", "status": "active"}
|
||||
```
|
||||
|
||||
See [Resources & Templates](/servers/resources) for detailed documentation.
|
||||
See [Resource Templates](/servers/resources#resource-templates) for detailed documentation.
|
||||
|
||||
### Prompts
|
||||
|
||||
Prompts are reusable message templates for guiding the LLM.
|
||||
Prompts are reusable message templates that guide LLM interactions. They help establish consistent patterns for how clients should frame requests.
|
||||
|
||||
```python
|
||||
@mcp.prompt
|
||||
|
|
@ -163,9 +162,9 @@ See [Prompts](/servers/prompts) for detailed documentation.
|
|||
|
||||
<VersionBadge version="2.8.0" />
|
||||
|
||||
FastMCP supports tag-based filtering to selectively expose components based on configurable include/exclude tag sets. This is useful for creating different views of your server for different environments or users.
|
||||
Tags let you categorize components and selectively expose them based on configurable include/exclude sets. This is useful for creating different views of your server for different environments or user types.
|
||||
|
||||
Components can be tagged when defined using the `tags` parameter:
|
||||
Components can be tagged when defined using the `tags` parameter. A component can have multiple tags, and filtering operates on tag membership.
|
||||
|
||||
```python
|
||||
@mcp.tool(tags={"public", "utility"})
|
||||
|
|
@ -177,23 +176,22 @@ def admin_tool() -> str:
|
|||
return "This tool is for admins only"
|
||||
```
|
||||
|
||||
|
||||
The filtering logic works as follows:
|
||||
- **Include tags**: If specified, only components with at least one matching tag are exposed
|
||||
- **Exclude tags**: Components with any matching tag are filtered out
|
||||
- **Precedence**: Exclude tags always take priority over include tags
|
||||
|
||||
<Tip>
|
||||
To ensure a component is never exposed, you can set `enabled=False` on the component itself. To learn more, see the component-specific documentation.
|
||||
To ensure a component is never exposed, you can set `enabled=False` on the component itself. See the component-specific documentation for details.
|
||||
</Tip>
|
||||
|
||||
You configure tag-based filtering when creating your server:
|
||||
Configure tag-based filtering when creating your server.
|
||||
|
||||
```python
|
||||
# Only expose components tagged with "public"
|
||||
mcp = FastMCP(include_tags={"public"})
|
||||
|
||||
# Hide components tagged as "internal" or "deprecated"
|
||||
# Hide components tagged as "internal" or "deprecated"
|
||||
mcp = FastMCP(exclude_tags={"internal", "deprecated"})
|
||||
|
||||
# Combine both: show admin tools but hide deprecated ones
|
||||
|
|
@ -204,10 +202,9 @@ This filtering applies to all component types (tools, resources, resource templa
|
|||
|
||||
## Running the Server
|
||||
|
||||
FastMCP servers need a transport mechanism to communicate with clients. You typically start your server by calling the `mcp.run()` method on your `FastMCP` instance, often within an `if __name__ == "__main__":` block in your main server script. This pattern ensures compatibility with various MCP clients.
|
||||
FastMCP servers communicate with clients through transport mechanisms. Start your server by calling `mcp.run()`, typically within an `if __name__ == "__main__":` block. This pattern ensures compatibility with various MCP clients.
|
||||
|
||||
```python
|
||||
# my_server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="MyServer")
|
||||
|
|
@ -218,25 +215,23 @@ def greet(name: str) -> str:
|
|||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
# This runs the server, defaulting to STDIO transport
|
||||
# Defaults to STDIO transport
|
||||
mcp.run()
|
||||
|
||||
# To use a different transport, e.g., HTTP:
|
||||
|
||||
# Or use HTTP transport
|
||||
# mcp.run(transport="http", host="127.0.0.1", port=9000)
|
||||
```
|
||||
|
||||
FastMCP supports several transport options:
|
||||
- STDIO (default, for local tools)
|
||||
- HTTP (recommended for web services, uses Streamable HTTP protocol)
|
||||
- SSE (legacy web transport, deprecated)
|
||||
FastMCP supports several transports:
|
||||
- **STDIO** (default): For local integrations and CLI tools
|
||||
- **HTTP**: For web services using the Streamable HTTP protocol
|
||||
- **SSE**: Legacy web transport (deprecated)
|
||||
|
||||
The server can also be run using the FastMCP CLI.
|
||||
|
||||
For detailed information on each transport, how to configure them (host, port, paths), and when to use which, please refer to the [**Running Your FastMCP Server**](/deployment/running-server) guide.
|
||||
The server can also be run using the FastMCP CLI. For detailed information on transports and configuration, see the [Running Your Server](/deployment/running-server) guide.
|
||||
|
||||
## Custom Routes
|
||||
|
||||
When running your server with HTTP transport, you can add custom web routes alongside your MCP endpoint using the `@custom_route` decorator. This is useful for simple endpoints like health checks that need to be served alongside your MCP server:
|
||||
When running with HTTP transport, you can add custom web routes alongside your MCP endpoint using the `@custom_route` decorator. This is useful for auxiliary endpoints like health checks.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -259,142 +254,3 @@ Custom routes are served alongside your MCP endpoint and are useful for:
|
|||
- Basic webhooks or callbacks
|
||||
|
||||
For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/http#integration-with-web-frameworks).
|
||||
|
||||
## Composing Servers
|
||||
|
||||
<VersionBadge version="2.2.0" />
|
||||
|
||||
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 [Mounting Servers](/servers/providers/mounting) guide for full details, best practices, and examples.
|
||||
|
||||
```python
|
||||
# Example: Importing a subserver
|
||||
from fastmcp import FastMCP
|
||||
import asyncio
|
||||
|
||||
main = FastMCP(name="Main")
|
||||
sub = FastMCP(name="Sub")
|
||||
|
||||
@sub.tool
|
||||
def hello():
|
||||
return "hi"
|
||||
|
||||
# Mount directly
|
||||
main.mount(sub, namespace="sub")
|
||||
```
|
||||
|
||||
## Proxying Servers
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
FastMCP can act as a proxy for any MCP server (local or remote) using `create_proxy`, 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.
|
||||
|
||||
Proxies automatically handle concurrent operations safely by creating fresh sessions for each request when using disconnected clients.
|
||||
|
||||
See the [Remote Proxies](/servers/providers/proxy) guide for details and advanced usage.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
backend = Client("http://example.com/mcp/sse")
|
||||
proxy = create_proxy(backend, name="ProxyServer")
|
||||
# Now use the proxy like any FastMCP server
|
||||
```
|
||||
|
||||
## OpenAPI Integration
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
FastMCP can automatically generate servers from OpenAPI specifications or existing FastAPI applications using `FastMCP.from_openapi()` and `FastMCP.from_fastapi()`. This allows you to instantly convert existing APIs into MCP servers without manual tool creation.
|
||||
|
||||
See the [FastAPI Integration](/integrations/fastapi) and [OpenAPI Integration](/integrations/openapi) guides for detailed examples and configuration options.
|
||||
|
||||
```python
|
||||
import httpx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# From OpenAPI spec
|
||||
spec = httpx.get("https://api.example.com/openapi.json").json()
|
||||
mcp = FastMCP.from_openapi(openapi_spec=spec, client=httpx.AsyncClient())
|
||||
|
||||
# From FastAPI app
|
||||
from fastapi import FastAPI
|
||||
app = FastAPI()
|
||||
mcp = FastMCP.from_fastapi(app=app)
|
||||
```
|
||||
|
||||
## Server Configuration
|
||||
|
||||
Servers can be configured using a combination of initialization arguments, global settings, and transport-specific settings.
|
||||
|
||||
### Server-Specific Configuration
|
||||
|
||||
Server-specific settings are passed when creating the `FastMCP` instance and control server behavior:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Configure server-specific settings
|
||||
mcp = FastMCP(
|
||||
name="ConfiguredServer",
|
||||
include_tags={"public", "api"}, # Only expose these tagged components
|
||||
exclude_tags={"internal", "deprecated"}, # Hide these tagged components
|
||||
on_duplicate_tools="error", # Handle duplicate registrations
|
||||
on_duplicate_resources="warn",
|
||||
on_duplicate_prompts="replace",
|
||||
)
|
||||
```
|
||||
|
||||
### Global Settings
|
||||
|
||||
Global settings affect all FastMCP servers and can be configured via environment variables (prefixed with `FASTMCP_`) or in a `.env` file:
|
||||
|
||||
```python
|
||||
import fastmcp
|
||||
|
||||
# Access global settings
|
||||
print(fastmcp.settings.log_level) # Default: "INFO"
|
||||
print(fastmcp.settings.mask_error_details) # Default: False
|
||||
print(fastmcp.settings.strict_input_validation) # Default: False
|
||||
```
|
||||
|
||||
Common global settings include:
|
||||
- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL`
|
||||
- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS`
|
||||
- **`strict_input_validation`**: Controls tool input validation mode (default: False for flexible coercion), set with `FASTMCP_STRICT_INPUT_VALIDATION`. See [Input Validation Modes](/servers/tools#input-validation-modes)
|
||||
- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv
|
||||
|
||||
### Transport-Specific Configuration
|
||||
|
||||
Transport settings are provided when running the server and control network behavior:
|
||||
|
||||
```python
|
||||
# Configure transport when running
|
||||
mcp.run(
|
||||
transport="http",
|
||||
host="0.0.0.0", # Bind to all interfaces
|
||||
port=9000, # Custom port
|
||||
log_level="DEBUG", # Override global log level
|
||||
)
|
||||
|
||||
# Or for async usage
|
||||
await mcp.run_async(
|
||||
transport="http",
|
||||
host="127.0.0.1",
|
||||
port=8080,
|
||||
)
|
||||
```
|
||||
|
||||
### Setting Global Configuration
|
||||
|
||||
Global FastMCP settings can be configured via environment variables (prefixed with `FASTMCP_`):
|
||||
|
||||
```bash
|
||||
# Configure global FastMCP behavior
|
||||
export FASTMCP_LOG_LEVEL=DEBUG
|
||||
export FASTMCP_MASK_ERROR_DETAILS=True
|
||||
export FASTMCP_STRICT_INPUT_VALIDATION=False
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: OpenTelemetry
|
|||
sidebarTitle: Telemetry
|
||||
description: Native OpenTelemetry instrumentation for distributed tracing.
|
||||
icon: chart-line
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
FastMCP includes native OpenTelemetry instrumentation for observability. Traces are automatically generated for tool, prompt, resource, and resource template operations, providing visibility into server behavior, request handling, and provider delegation chains.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ title: Versioning
|
|||
sidebarTitle: Versioning
|
||||
description: Serve multiple API versions from a single codebase
|
||||
icon: code-branch
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
@ -15,7 +16,7 @@ The primary use case is serving different API versions from one codebase. Instea
|
|||
|
||||
## Versioned API Surfaces
|
||||
|
||||
Consider a server that needs to support both v1 and v2 clients. The v2 API adds new parameters to existing tools, and you want both versions to coexist cleanly. You define your components on a shared provider, then create separate servers with different version filters.
|
||||
Consider a server that needs to support both v1 and v2 clients. The v2 API adds new parameters to existing tools, and you want both versions to coexist cleanly. Define your components on a shared provider, then create separate servers with different version filters.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -59,7 +60,7 @@ VersionFilter(version_gte="2.0", version_lt="3.0")
|
|||
```
|
||||
|
||||
<Note>
|
||||
**Unversioned components are exempt from version filtering.** A `VersionFilter` only affects versioned components—unversioned components always pass through regardless of the filter's constraints. This ensures that adding version filtering to a server with mixed versioned and unversioned tools doesn't accidentally hide the unversioned ones. To prevent confusion, FastMCP forbids mixing versioned and unversioned components with the same name.
|
||||
**Unversioned components are exempt from version filtering.** A `VersionFilter` only affects versioned components - unversioned components always pass through regardless of the filter's constraints. This ensures that adding version filtering to a server with mixed versioned and unversioned tools doesn't accidentally hide the unversioned ones. To prevent confusion, FastMCP forbids mixing versioned and unversioned components with the same name.
|
||||
</Note>
|
||||
|
||||
### Filtering Mounted Servers
|
||||
|
|
@ -139,6 +140,8 @@ The error message explains the conflict: "Cannot add versioned tool 'calculate'
|
|||
|
||||
This restriction exists because unversioned components always pass through version filters. If you could mix versioned and unversioned components, you'd have no way to filter out the unversioned one using `VersionFilter`. By enforcing consistency at registration, FastMCP ensures version filtering behaves predictably.
|
||||
|
||||
Resources and prompts follow the same pattern.
|
||||
|
||||
```python
|
||||
@mcp.resource("config://app", version="1.0")
|
||||
def config_v1() -> str:
|
||||
|
|
@ -243,8 +246,8 @@ For [PEP 440](https://peps.python.org/pep-0440/) versions (like `"1.0"`, `"2.1.3
|
|||
|
||||
```python
|
||||
# PEP 440 versions compare semantically
|
||||
"1" < "2" < "10" # Not string order ("1" < "10" < "2")
|
||||
"1.9" < "1.10" # Not string order ("1.10" < "1.9")
|
||||
"1" < "2" < "10" # Numeric order (not "1" < "10" < "2")
|
||||
"1.9" < "1.10" # Numeric order (not "1.10" < "1.9")
|
||||
"1.0a1" < "1.0b1" < "1.0" # Pre-releases sort before releases
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Component Visibility
|
||||
sidebarTitle: Component Visibility
|
||||
sidebarTitle: Visibility
|
||||
description: Control which components are available to clients
|
||||
icon: toggle-on
|
||||
tag: NEW
|
||||
|
|
@ -259,33 +259,6 @@ mcp.disable(tags={"beta"})
|
|||
# new_feature is disabled (server's later disable overrides provider's enable)
|
||||
```
|
||||
|
||||
## Dynamic Changes
|
||||
|
||||
Visibility state changes take effect immediately. You can adjust during request handling based on context.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import Context
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
|
||||
@mcp.tool(tags={"admin"})
|
||||
def admin_action() -> str:
|
||||
return "Admin action performed"
|
||||
|
||||
@mcp.tool
|
||||
def check_permissions(ctx: Context) -> str:
|
||||
"""Check if admin tools should be available."""
|
||||
user = ctx.request_context.get_user()
|
||||
|
||||
if user and user.is_admin:
|
||||
mcp.enable(tags={"admin"})
|
||||
return "Admin tools enabled"
|
||||
else:
|
||||
mcp.disable(tags={"admin"})
|
||||
return "Admin tools disabled"
|
||||
```
|
||||
|
||||
## Per-Session Visibility
|
||||
|
||||
Server-level visibility changes affect all connected clients simultaneously. When you need different clients to see different components, use per-session visibility instead.
|
||||
|
|
|
|||
39
docs/v2-banner.js
Normal file
39
docs/v2-banner.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Add v2 banner inside content-container with negative margins
|
||||
(function() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
function addBanner() {
|
||||
const isV2 = window.location.pathname.includes('/v2/');
|
||||
const container = document.getElementById('content-container');
|
||||
let banner = document.getElementById('v2-banner');
|
||||
|
||||
if (isV2 && container) {
|
||||
if (!banner) {
|
||||
banner = document.createElement('div');
|
||||
banner.id = 'v2-banner';
|
||||
banner.innerHTML = 'These are the docs for FastMCP 2.0. The beta of <a href="/getting-started/welcome" style="color: white; text-decoration: underline; font-weight: 700;">FastMCP 3.0</a> is now available.';
|
||||
container.insertBefore(banner, container.firstChild);
|
||||
}
|
||||
} else if (!isV2 && banner) {
|
||||
banner.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function run() {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', addBanner);
|
||||
} else {
|
||||
addBanner();
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
let lastUrl = location.href;
|
||||
new MutationObserver(() => {
|
||||
if (location.href !== lastUrl) {
|
||||
lastUrl = location.href;
|
||||
setTimeout(addBanner, 100);
|
||||
}
|
||||
}).observe(document.body, {subtree: true, childList: true});
|
||||
})();
|
||||
2280
docs/v2/changelog.mdx
Normal file
2280
docs/v2/changelog.mdx
Normal file
File diff suppressed because it is too large
Load diff
88
docs/v2/clients/auth/bearer.mdx
Normal file
88
docs/v2/clients/auth/bearer.mdx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: Bearer Token Authentication
|
||||
sidebarTitle: Bearer Auth
|
||||
description: Authenticate your FastMCP client with a Bearer token.
|
||||
icon: key
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.6.0" />
|
||||
|
||||
<Tip>
|
||||
Bearer Token authentication is only relevant for HTTP-based transports.
|
||||
</Tip>
|
||||
|
||||
You can configure your FastMCP client to use **bearer authentication** by supplying a valid access token. This is most appropriate for service accounts, long-lived API keys, CI/CD, applications where authentication is managed separately, or other non-interactive authentication methods.
|
||||
|
||||
A Bearer token is a JSON Web Token (JWT) that is used to authenticate a request. It is most commonly used in the `Authorization` header of an HTTP request, using the `Bearer` scheme:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
|
||||
## Client Usage
|
||||
|
||||
The most straightforward way to use a pre-existing Bearer token is to provide it as a string to the `auth` parameter of the `fastmcp.Client` or transport instance. FastMCP will automatically format it correctly for the `Authorization` header and bearer scheme.
|
||||
|
||||
<Tip>
|
||||
If you're using a string token, do not include the `Bearer` prefix. FastMCP will add it for you.
|
||||
</Tip>
|
||||
|
||||
```python {5}
|
||||
from fastmcp import Client
|
||||
|
||||
async with Client(
|
||||
"https://fastmcp.cloud/mcp",
|
||||
auth="<your-token>",
|
||||
) as client:
|
||||
await client.ping()
|
||||
```
|
||||
|
||||
You can also supply a Bearer token to a transport instance, such as `StreamableHttpTransport` or `SSETransport`:
|
||||
|
||||
```python {6}
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
transport = StreamableHttpTransport(
|
||||
"http://fastmcp.cloud/mcp",
|
||||
auth="<your-token>",
|
||||
)
|
||||
|
||||
async with Client(transport) as client:
|
||||
await client.ping()
|
||||
```
|
||||
|
||||
## `BearerAuth` Helper
|
||||
|
||||
If you prefer to be more explicit and not rely on FastMCP to transform your string token, you can use the `BearerAuth` class yourself, which implements the `httpx.Auth` interface.
|
||||
|
||||
```python {6}
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.auth import BearerAuth
|
||||
|
||||
async with Client(
|
||||
"https://fastmcp.cloud/mcp",
|
||||
auth=BearerAuth(token="<your-token>"),
|
||||
) as client:
|
||||
await client.ping()
|
||||
```
|
||||
|
||||
## Custom Headers
|
||||
|
||||
If the MCP server expects a custom header or token scheme, you can manually set the client's `headers` instead of using the `auth` parameter by setting them on your transport:
|
||||
|
||||
```python {5}
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
async with Client(
|
||||
transport=StreamableHttpTransport(
|
||||
"https://fastmcp.cloud/mcp",
|
||||
headers={"X-API-Key": "<your-token>"},
|
||||
),
|
||||
) as client:
|
||||
await client.ping()
|
||||
```
|
||||
132
docs/v2/clients/auth/oauth.mdx
Normal file
132
docs/v2/clients/auth/oauth.mdx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
title: OAuth Authentication
|
||||
sidebarTitle: OAuth
|
||||
description: Authenticate your FastMCP client via OAuth 2.1.
|
||||
icon: window
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.6.0" />
|
||||
|
||||
<Tip>
|
||||
OAuth authentication is only relevant for HTTP-based transports and requires user interaction via a web browser.
|
||||
</Tip>
|
||||
|
||||
When your FastMCP client needs to access an MCP server protected by OAuth 2.1, and the process requires user interaction (like logging in and granting consent), you should use the Authorization Code Flow. FastMCP provides the `fastmcp.client.auth.OAuth` helper to simplify this entire process.
|
||||
|
||||
This flow is common for user-facing applications where the application acts on behalf of the user.
|
||||
|
||||
## Client Usage
|
||||
|
||||
|
||||
### Default Configuration
|
||||
|
||||
The simplest way to use OAuth is to pass the string `"oauth"` to the `auth` parameter of the `Client` or transport instance. FastMCP will automatically configure the client to use OAuth with default settings:
|
||||
|
||||
```python {4}
|
||||
from fastmcp import Client
|
||||
|
||||
# Uses default OAuth settings
|
||||
async with Client("https://fastmcp.cloud/mcp", auth="oauth") as client:
|
||||
await client.ping()
|
||||
```
|
||||
|
||||
|
||||
### `OAuth` Helper
|
||||
|
||||
To fully configure the OAuth flow, use the `OAuth` helper and pass it to the `auth` parameter of the `Client` or transport instance. `OAuth` manages the complexities of the OAuth 2.1 Authorization Code Grant with PKCE (Proof Key for Code Exchange) for enhanced security, and implements the full `httpx.Auth` interface.
|
||||
|
||||
```python {2, 4, 6}
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.auth import OAuth
|
||||
|
||||
oauth = OAuth(mcp_url="https://fastmcp.cloud/mcp")
|
||||
|
||||
async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client:
|
||||
await client.ping()
|
||||
```
|
||||
|
||||
#### `OAuth` Parameters
|
||||
|
||||
- **`mcp_url`** (`str`): The full URL of the target MCP server endpoint. Used to discover OAuth server metadata
|
||||
- **`scopes`** (`str | list[str]`, optional): OAuth scopes to request. Can be space-separated string or list of strings
|
||||
- **`client_name`** (`str`, optional): Client name for dynamic registration. Defaults to `"FastMCP Client"`
|
||||
- **`token_storage`** (`AsyncKeyValue`, optional): Storage backend for persisting OAuth tokens. Defaults to in-memory storage (tokens lost on restart). See [Token Storage](#token-storage) for encrypted storage options
|
||||
- **`additional_client_metadata`** (`dict[str, Any]`, optional): Extra metadata for client registration
|
||||
- **`callback_port`** (`int`, optional): Fixed port for OAuth callback server. If not specified, uses a random available port
|
||||
|
||||
|
||||
## OAuth Flow
|
||||
|
||||
The OAuth flow is triggered when you use a FastMCP `Client` configured to use OAuth.
|
||||
|
||||
<Steps>
|
||||
<Step title="Token Check">
|
||||
The client first checks the configured `token_storage` backend for existing, valid tokens for the target server. If one is found, it will be used to authenticate the client.
|
||||
</Step>
|
||||
<Step title="OAuth Server Discovery">
|
||||
If no valid tokens exist, the client attempts to discover the OAuth server's endpoints using a well-known URI (e.g., `/.well-known/oauth-authorization-server`) based on the `mcp_url`.
|
||||
</Step>
|
||||
<Step title="Dynamic Client Registration">
|
||||
If the OAuth server supports it and the client isn't already registered (or credentials aren't cached), the client performs dynamic client registration according to RFC 7591.
|
||||
</Step>
|
||||
<Step title="Local Callback Server">
|
||||
A temporary local HTTP server is started on an available port (or the port specified via `callback_port`). This server's address (e.g., `http://127.0.0.1:<port>/callback`) acts as the `redirect_uri` for the OAuth flow.
|
||||
</Step>
|
||||
<Step title="Browser Interaction">
|
||||
The user's default web browser is automatically opened, directing them to the OAuth server's authorization endpoint. The user logs in and grants (or denies) the requested `scopes`.
|
||||
</Step>
|
||||
<Step title="Authorization Code & Token Exchange">
|
||||
Upon approval, the OAuth server redirects the user's browser to the local callback server with an `authorization_code`. The client captures this code and exchanges it with the OAuth server's token endpoint for an `access_token` (and often a `refresh_token`) using PKCE for security.
|
||||
</Step>
|
||||
<Step title="Token Caching">
|
||||
The obtained tokens are saved to the configured `token_storage` backend for future use, eliminating the need for repeated browser interactions.
|
||||
</Step>
|
||||
<Step title="Authenticated Requests">
|
||||
The access token is automatically included in the `Authorization` header for requests to the MCP server.
|
||||
</Step>
|
||||
<Step title="Refresh Token">
|
||||
If the access token expires, the client will automatically use the refresh token to get a new access token.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Token Storage
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
By default, tokens are stored in memory and lost when your application restarts. For persistent storage, pass an `AsyncKeyValue`-compatible storage backend to the `token_storage` parameter.
|
||||
|
||||
<Warning>
|
||||
**Security Consideration**: Use encrypted storage for production. MCP clients can accumulate OAuth credentials for many servers over time, and a compromised token store could expose access to multiple services.
|
||||
</Warning>
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.auth import OAuth
|
||||
from key_value.aio.stores.disk import DiskStore
|
||||
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
||||
from cryptography.fernet import Fernet
|
||||
import os
|
||||
|
||||
# Create encrypted disk storage
|
||||
encrypted_storage = FernetEncryptionWrapper(
|
||||
key_value=DiskStore(directory="~/.fastmcp/oauth-tokens"),
|
||||
fernet=Fernet(os.environ["OAUTH_STORAGE_ENCRYPTION_KEY"])
|
||||
)
|
||||
|
||||
oauth = OAuth(
|
||||
mcp_url="https://fastmcp.cloud/mcp",
|
||||
token_storage=encrypted_storage
|
||||
)
|
||||
|
||||
async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client:
|
||||
await client.ping()
|
||||
```
|
||||
|
||||
You can use any `AsyncKeyValue`-compatible backend from the [key-value library](https://github.com/strawgate/py-key-value) including Redis, DynamoDB, and more. Wrap your storage in `FernetEncryptionWrapper` for encryption.
|
||||
|
||||
<Note>
|
||||
When selecting a storage backend, review the [py-key-value documentation](https://github.com/strawgate/py-key-value) to understand the maturity level and limitations of your chosen backend. Some backends may be in preview or have constraints that affect production suitability.
|
||||
</Note>
|
||||
340
docs/v2/clients/client.mdx
Normal file
340
docs/v2/clients/client.mdx
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
---
|
||||
title: The FastMCP Client
|
||||
sidebarTitle: Overview
|
||||
description: Programmatic client for interacting with MCP servers through a well-typed, Pythonic interface.
|
||||
icon: user-robot
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
The central piece of MCP client applications is the `fastmcp.Client` class. This class provides a **programmatic interface** for interacting with any Model Context Protocol (MCP) server, handling protocol details and connection management automatically.
|
||||
|
||||
The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for:
|
||||
|
||||
- **Testing MCP servers** during development
|
||||
- **Building deterministic applications** that need reliable MCP interactions
|
||||
- **Creating the foundation for agentic or LLM-based clients** with structured, type-safe operations
|
||||
|
||||
All client operations require using the `async with` context manager for proper connection lifecycle management.
|
||||
|
||||
|
||||
<Note>
|
||||
This is not an agentic client - it requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems.
|
||||
</Note>
|
||||
|
||||
## Creating a Client
|
||||
|
||||
Creating a client is straightforward. You provide a server source and the client automatically infers the appropriate transport mechanism.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastmcp import Client, FastMCP
|
||||
|
||||
# In-memory server (ideal for testing)
|
||||
server = FastMCP("TestServer")
|
||||
client = Client(server)
|
||||
|
||||
# HTTP server
|
||||
client = Client("https://example.com/mcp")
|
||||
|
||||
# Local Python script
|
||||
client = Client("my_mcp_server.py")
|
||||
|
||||
async def main():
|
||||
async with client:
|
||||
# Basic server interaction
|
||||
await client.ping()
|
||||
|
||||
# List available operations
|
||||
tools = await client.list_tools()
|
||||
resources = await client.list_resources()
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
# Execute operations
|
||||
result = await client.call_tool("example_tool", {"param": "value"})
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Client-Transport Architecture
|
||||
|
||||
The FastMCP Client separates concerns between protocol and connection:
|
||||
|
||||
- **`Client`**: Handles MCP protocol operations (tools, resources, prompts) and manages callbacks
|
||||
- **`Transport`**: Establishes and maintains the connection (WebSockets, HTTP, Stdio, in-memory)
|
||||
|
||||
### Transport Inference
|
||||
|
||||
The client automatically infers the appropriate transport based on the input:
|
||||
|
||||
1. **`FastMCP` instance** → In-memory transport (perfect for testing)
|
||||
2. **File path ending in `.py`** → Python Stdio transport
|
||||
3. **File path ending in `.js`** → Node.js Stdio transport
|
||||
4. **URL starting with `http://` or `https://`** → HTTP transport
|
||||
5. **`MCPConfig` dictionary** → Multi-server client
|
||||
|
||||
```python
|
||||
from fastmcp import Client, FastMCP
|
||||
|
||||
# Examples of transport inference
|
||||
client_memory = Client(FastMCP("TestServer"))
|
||||
client_script = Client("./server.py")
|
||||
client_http = Client("https://api.example.com/mcp")
|
||||
```
|
||||
|
||||
<Tip>
|
||||
For testing and development, always prefer the in-memory transport by passing a `FastMCP` server directly to the client. This eliminates network complexity and separate processes.
|
||||
</Tip>
|
||||
|
||||
## Configuration-Based Clients
|
||||
|
||||
<VersionBadge version="2.4.0" />
|
||||
|
||||
Create clients from MCP configuration dictionaries, which can include multiple servers. While there is no official standard for MCP configuration format, FastMCP follows established conventions used by tools like Claude Desktop.
|
||||
|
||||
### Configuration Format
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"server_name": {
|
||||
# Remote HTTP/SSE server
|
||||
"transport": "http", # or "sse"
|
||||
"url": "https://api.example.com/mcp",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
"auth": "oauth" # or bearer token string
|
||||
},
|
||||
"local_server": {
|
||||
# Local stdio server
|
||||
"transport": "stdio",
|
||||
"command": "python",
|
||||
"args": ["./server.py", "--verbose"],
|
||||
"env": {"DEBUG": "true"},
|
||||
"cwd": "/path/to/server",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Server Example
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {"url": "https://weather-api.example.com/mcp"},
|
||||
"assistant": {"command": "python", "args": ["./assistant_server.py"]}
|
||||
}
|
||||
}
|
||||
|
||||
client = Client(config)
|
||||
|
||||
async with client:
|
||||
# Tools are prefixed with server names
|
||||
weather_data = await client.call_tool("weather_get_forecast", {"city": "London"})
|
||||
response = await client.call_tool("assistant_answer_question", {"question": "What's the capital of France?"})
|
||||
|
||||
# Resources use prefixed URIs
|
||||
icons = await client.read_resource("weather://weather/icons/sunny")
|
||||
templates = await client.read_resource("resource://assistant/templates/list")
|
||||
```
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
The client operates asynchronously and uses context managers for connection management:
|
||||
|
||||
```python
|
||||
async def example():
|
||||
client = Client("my_mcp_server.py")
|
||||
|
||||
# Connection established here
|
||||
async with client:
|
||||
print(f"Connected: {client.is_connected()}")
|
||||
|
||||
# Make multiple calls within the same session
|
||||
tools = await client.list_tools()
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
|
||||
# Connection closed automatically here
|
||||
print(f"Connected: {client.is_connected()}")
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
FastMCP clients can interact with several types of server components:
|
||||
|
||||
### Tools
|
||||
|
||||
Tools are server-side functions that the client can execute with arguments.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# List available tools
|
||||
tools = await client.list_tools()
|
||||
|
||||
# Execute a tool
|
||||
result = await client.call_tool("multiply", {"a": 5, "b": 3})
|
||||
print(result.data) # 15
|
||||
```
|
||||
|
||||
See [Tools](/clients/tools) for detailed documentation.
|
||||
|
||||
### Resources
|
||||
|
||||
Resources are data sources that the client can read, either static or templated.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# List available resources
|
||||
resources = await client.list_resources()
|
||||
|
||||
# Read a resource
|
||||
content = await client.read_resource("file:///config/settings.json")
|
||||
print(content[0].text)
|
||||
```
|
||||
|
||||
See [Resources](/clients/resources) for detailed documentation.
|
||||
|
||||
### Prompts
|
||||
|
||||
Prompts are reusable message templates that can accept arguments.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# List available prompts
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
# Get a rendered prompt
|
||||
messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]})
|
||||
print(messages.messages)
|
||||
```
|
||||
|
||||
See [Prompts](/clients/prompts) for detailed documentation.
|
||||
|
||||
### Server Connectivity
|
||||
|
||||
Use `ping()` to verify the server is reachable:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
await client.ping()
|
||||
print("Server is reachable")
|
||||
```
|
||||
|
||||
### Initialization and Server Information
|
||||
|
||||
When you enter the client context manager, the client automatically performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions. The result is available through the `initialize_result` property.
|
||||
|
||||
```python
|
||||
from fastmcp import Client, FastMCP
|
||||
|
||||
mcp = FastMCP(name="MyServer", instructions="Use the greet tool to say hello!")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
"""Greet a user by name."""
|
||||
return f"Hello, {name}!"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# Initialization already happened automatically
|
||||
print(f"Server: {client.initialize_result.serverInfo.name}")
|
||||
print(f"Version: {client.initialize_result.serverInfo.version}")
|
||||
print(f"Instructions: {client.initialize_result.instructions}")
|
||||
print(f"Capabilities: {client.initialize_result.capabilities.tools}")
|
||||
```
|
||||
|
||||
#### Manual Initialization Control
|
||||
|
||||
In advanced scenarios, you might want precise control over when initialization happens. For example, you may need custom error handling, want to defer initialization until after other setup, or need to measure initialization timing separately.
|
||||
|
||||
Disable automatic initialization and call `initialize()` manually:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# Disable automatic initialization
|
||||
client = Client("my_mcp_server.py", auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
# Connection established, but not initialized yet
|
||||
print(f"Connected: {client.is_connected()}")
|
||||
print(f"Initialized: {client.initialize_result is not None}") # False
|
||||
|
||||
# Initialize manually with custom timeout
|
||||
result = await client.initialize(timeout=10.0)
|
||||
print(f"Server: {result.serverInfo.name}")
|
||||
|
||||
# Now ready for operations
|
||||
tools = await client.list_tools()
|
||||
```
|
||||
|
||||
The `initialize()` method is idempotent - calling it multiple times returns the cached result from the first successful call.
|
||||
|
||||
## Client Configuration
|
||||
|
||||
Clients can be configured with additional handlers and settings for specialized use cases.
|
||||
|
||||
### Callback Handlers
|
||||
|
||||
The client supports several callback handlers for advanced server interactions:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.logging import LogMessage
|
||||
|
||||
async def log_handler(message: LogMessage):
|
||||
print(f"Server log: {message.data}")
|
||||
|
||||
async def progress_handler(progress: float, total: float | None, message: str | None):
|
||||
print(f"Progress: {progress}/{total} - {message}")
|
||||
|
||||
async def sampling_handler(messages, params, context):
|
||||
# Integrate with your LLM service here
|
||||
return "Generated response"
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
log_handler=log_handler,
|
||||
progress_handler=progress_handler,
|
||||
sampling_handler=sampling_handler,
|
||||
timeout=30.0
|
||||
)
|
||||
```
|
||||
|
||||
The `Client` constructor accepts several configuration options:
|
||||
|
||||
- `transport`: Transport instance or source for automatic inference
|
||||
- `log_handler`: Handle server log messages
|
||||
- `progress_handler`: Monitor long-running operations
|
||||
- `sampling_handler`: Respond to server LLM requests
|
||||
- `roots`: Provide local context to servers
|
||||
- `timeout`: Default timeout for requests (in seconds)
|
||||
|
||||
### Transport Configuration
|
||||
|
||||
For detailed transport configuration (headers, authentication, environment variables), see the [Transports](/clients/transports) documentation.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Explore the detailed documentation for each operation type:
|
||||
|
||||
### Core Operations
|
||||
- **[Tools](/clients/tools)** - Execute server-side functions and handle results
|
||||
- **[Resources](/clients/resources)** - Access static and templated resources
|
||||
- **[Prompts](/clients/prompts)** - Work with message templates and argument serialization
|
||||
|
||||
### Advanced Features
|
||||
- **[Logging](/clients/logging)** - Handle server log messages
|
||||
- **[Progress](/clients/progress)** - Monitor long-running operations
|
||||
- **[Sampling](/clients/sampling)** - Respond to server LLM requests
|
||||
- **[Roots](/clients/roots)** - Provide local context to servers
|
||||
|
||||
### Connection Details
|
||||
- **[Transports](/clients/transports)** - Configure connection methods and parameters
|
||||
- **[Authentication](/clients/auth/oauth)** - Set up OAuth and bearer token authentication
|
||||
|
||||
<Tip>
|
||||
The FastMCP Client is designed as a foundational tool. Use it directly for deterministic operations, or build higher-level agentic systems on top of its reliable, type-safe interface.
|
||||
</Tip>
|
||||
125
docs/v2/clients/elicitation.mdx
Normal file
125
docs/v2/clients/elicitation.mdx
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
title: User Elicitation
|
||||
sidebarTitle: Elicitation
|
||||
description: Handle server-initiated user input requests with structured schemas.
|
||||
icon: message-question
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
## What is Elicitation?
|
||||
|
||||
Elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, servers can interactively ask users for information as needed - like prompting for missing parameters, requesting clarification, or gathering additional context.
|
||||
|
||||
For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?"
|
||||
|
||||
## How FastMCP Makes Elicitation Easy
|
||||
|
||||
FastMCP's client provides a helpful abstraction layer that:
|
||||
|
||||
- **Converts JSON schemas to Python types**: The raw MCP protocol uses JSON schemas, but FastMCP automatically converts these to Python dataclasses
|
||||
- **Provides structured constructors**: Instead of manually building dictionaries that match the schema, you get dataclass constructors that ensure correct structure
|
||||
- **Handles type conversion**: FastMCP takes care of converting between JSON representations and Python objects
|
||||
- **Runtime introspection**: You can inspect the generated dataclass fields to understand the expected structure
|
||||
|
||||
When you implement an elicitation handler, FastMCP gives you a dataclass type that matches the server's schema, making it easy to create properly structured responses without having to manually parse JSON schemas.
|
||||
|
||||
## Elicitation Handler
|
||||
|
||||
Provide an `elicitation_handler` function when creating the client. FastMCP automatically converts the server's JSON schema into a Python dataclass type, making it easy to construct the response:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.elicitation import ElicitResult
|
||||
|
||||
async def elicitation_handler(message: str, response_type: type, params, context):
|
||||
# Present the message to the user and collect input
|
||||
user_input = input(f"{message}: ")
|
||||
|
||||
# Create response using the provided dataclass type
|
||||
# FastMCP converted the JSON schema to this Python type for you
|
||||
response_data = response_type(value=user_input)
|
||||
|
||||
# You can return data directly - FastMCP will implicitly accept the elicitation
|
||||
return response_data
|
||||
|
||||
# Or explicitly return an ElicitResult for more control
|
||||
# return ElicitResult(action="accept", content=response_data)
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
elicitation_handler=elicitation_handler,
|
||||
)
|
||||
```
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The elicitation handler receives four parameters:
|
||||
|
||||
<Card icon="code" title="Elicitation Handler Parameters">
|
||||
<ResponseField name="message" type="str">
|
||||
The prompt message to display to the user
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="response_type" type="type">
|
||||
A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing and IDE support. If the server requests an empty object (indicating no response), this will be `None`.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="params" type="ElicitRequestParams">
|
||||
The original MCP elicitation request parameters, including the raw JSON schema in `params.requestedSchema` if you need it
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="context" type="RequestContext">
|
||||
Request context containing metadata about the elicitation request
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
### Response Actions
|
||||
|
||||
The handler can return data directly (which implicitly accepts the elicitation) or an `ElicitResult` object for more control over the response action:
|
||||
|
||||
<Card icon="code" title="ElicitResult Structure">
|
||||
<ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
|
||||
How the user responded to the elicitation request
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="content" type="dataclass instance | dict | None">
|
||||
The user's input data (required for "accept", omitted for "decline"/"cancel")
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
**Action Types:**
|
||||
- **`accept`**: User provided valid input - include their data in the `content` field
|
||||
- **`decline`**: User chose not to provide the requested information - omit `content`
|
||||
- **`cancel`**: User cancelled the entire operation - omit `content`
|
||||
|
||||
## Basic Example
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.elicitation import ElicitResult
|
||||
|
||||
async def basic_elicitation_handler(message: str, response_type: type, params, context):
|
||||
print(f"Server asks: {message}")
|
||||
|
||||
# Simple text input for demonstration
|
||||
user_response = input("Your response: ")
|
||||
|
||||
if not user_response:
|
||||
# For non-acceptance, use ElicitResult explicitly
|
||||
return ElicitResult(action="decline")
|
||||
|
||||
# Use the response_type dataclass to create a properly structured response
|
||||
# FastMCP handles the conversion from JSON schema to Python type
|
||||
# Return data directly - FastMCP will implicitly accept the elicitation
|
||||
return response_type(value=user_response)
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
elicitation_handler=basic_elicitation_handler
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
111
docs/v2/clients/logging.mdx
Normal file
111
docs/v2/clients/logging.mdx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
title: Server Logging
|
||||
sidebarTitle: Logging
|
||||
description: Receive and handle log messages from MCP servers.
|
||||
icon: receipt
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
MCP servers can emit log messages to clients. The client can handle these logs through a log handler callback.
|
||||
|
||||
## Log Handler
|
||||
|
||||
Provide a `log_handler` function when creating the client. For robust logging, the log messages can be integrated with Python's standard `logging` module.
|
||||
|
||||
```python
|
||||
import logging
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.logging import LogMessage
|
||||
|
||||
# In a real app, you might configure this in your main entry point
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Get a logger for the module where the client is used
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# This mapping is useful for converting MCP level strings to Python's levels
|
||||
LOGGING_LEVEL_MAP = logging.getLevelNamesMapping()
|
||||
|
||||
async def log_handler(message: LogMessage):
|
||||
"""
|
||||
Handles incoming logs from the MCP server and forwards them
|
||||
to the standard Python logging system.
|
||||
"""
|
||||
msg = message.data.get('msg')
|
||||
extra = message.data.get('extra')
|
||||
|
||||
# Convert the MCP log level to a Python log level
|
||||
level = LOGGING_LEVEL_MAP.get(message.level.upper(), logging.INFO)
|
||||
|
||||
# Log the message using the standard logging library
|
||||
logger.log(level, msg, extra=extra)
|
||||
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
log_handler=log_handler,
|
||||
)
|
||||
```
|
||||
|
||||
## Handling Structured Logs
|
||||
|
||||
The `message.data` attribute is a dictionary that contains the log payload from the server. This enables structured logging, allowing you to receive rich, contextual information.
|
||||
|
||||
The dictionary contains two keys:
|
||||
- `msg`: The string log message.
|
||||
- `extra`: A dictionary containing any extra data sent from the server.
|
||||
|
||||
This structure is preserved even when logs are forwarded through a FastMCP proxy, making it a powerful tool for debugging complex, multi-server applications.
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The `log_handler` is called every time a log message is received. It receives a `LogMessage` object:
|
||||
|
||||
<Card icon="code" title="Log Handler Parameters">
|
||||
<ResponseField name="LogMessage" type="Log Message Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
|
||||
The log level
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="logger" type="str | None">
|
||||
The logger name (optional, may be None)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="data" type="dict">
|
||||
The log payload, containing `msg` and `extra` keys.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
```python
|
||||
async def detailed_log_handler(message: LogMessage):
|
||||
msg = message.data.get('msg')
|
||||
extra = message.data.get('extra')
|
||||
|
||||
if message.level == "error":
|
||||
print(f"ERROR: {msg} | Details: {extra}")
|
||||
elif message.level == "warning":
|
||||
print(f"WARNING: {msg} | Details: {extra}")
|
||||
else:
|
||||
print(f"{message.level.upper()}: {msg}")
|
||||
```
|
||||
|
||||
## Default Log Handling
|
||||
|
||||
If you don't provide a custom `log_handler`, FastMCP's default handler routes server logs to the appropriate Python logging levels. The MCP levels are mapped as follows: `notice` → INFO; `alert` and `emergency` → CRITICAL. If the server includes a logger name, it is prefixed in the message, and any `extra` data is forwarded via the logging `extra` parameter.
|
||||
|
||||
```python
|
||||
client = Client("my_mcp_server.py")
|
||||
|
||||
async with client:
|
||||
# Server logs are forwarded at their proper severity (DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
||||
await client.call_tool("some_tool")
|
||||
```
|
||||
70
docs/v2/clients/progress.mdx
Normal file
70
docs/v2/clients/progress.mdx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: Progress Monitoring
|
||||
sidebarTitle: Progress
|
||||
description: Handle progress notifications from long-running server operations.
|
||||
icon: bars-progress
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.3.5" />
|
||||
|
||||
MCP servers can report progress during long-running operations. The client can receive these updates through a progress handler.
|
||||
|
||||
## Progress Handler
|
||||
|
||||
Set a progress handler when creating the client:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async def my_progress_handler(
|
||||
progress: float,
|
||||
total: float | None,
|
||||
message: str | None
|
||||
) -> None:
|
||||
if total is not None:
|
||||
percentage = (progress / total) * 100
|
||||
print(f"Progress: {percentage:.1f}% - {message or ''}")
|
||||
else:
|
||||
print(f"Progress: {progress} - {message or ''}")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
progress_handler=my_progress_handler
|
||||
)
|
||||
```
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The progress handler receives three parameters:
|
||||
|
||||
|
||||
<Card icon="code" title="Progress Handler Parameters">
|
||||
<ResponseField name="progress" type="float">
|
||||
Current progress value
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="total" type="float | None">
|
||||
Expected total value (may be None)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="message" type="str | None">
|
||||
Optional status message (may be None)
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
|
||||
## Per-Call Progress Handler
|
||||
|
||||
Override the progress handler for specific tool calls:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Override with specific progress handler for this call
|
||||
result = await client.call_tool(
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
progress_handler=my_progress_handler
|
||||
)
|
||||
```
|
||||
216
docs/v2/clients/prompts.mdx
Normal file
216
docs/v2/clients/prompts.mdx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Prompts
|
||||
sidebarTitle: Prompts
|
||||
description: Use server-side prompt templates with automatic argument serialization.
|
||||
icon: message-lines
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Prompts are reusable message templates exposed by MCP servers. They can accept arguments to generate personalized message sequences for LLM interactions.
|
||||
|
||||
## Listing Prompts
|
||||
|
||||
Use `list_prompts()` to retrieve all available prompt templates:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
# prompts -> list[mcp.types.Prompt]
|
||||
|
||||
for prompt in prompts:
|
||||
print(f"Prompt: {prompt.name}")
|
||||
print(f"Description: {prompt.description}")
|
||||
if prompt.arguments:
|
||||
print(f"Arguments: {[arg.name for arg in prompt.arguments]}")
|
||||
# Access tags and other metadata
|
||||
if hasattr(prompt, '_meta') and prompt._meta:
|
||||
fastmcp_meta = prompt._meta.get('_fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
### Filtering by Tags
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
You can use the `meta` field to filter prompts based on their tags:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
|
||||
# Filter prompts by tag
|
||||
analysis_prompts = [
|
||||
prompt for prompt in prompts
|
||||
if hasattr(prompt, '_meta') and prompt._meta and
|
||||
prompt._meta.get('_fastmcp', {}) and
|
||||
'analysis' in prompt._meta.get('_fastmcp', {}).get('tags', [])
|
||||
]
|
||||
|
||||
print(f"Found {len(analysis_prompts)} analysis prompts")
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `_meta` field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a `_fastmcp` namespace (e.g., `_meta._fastmcp.tags`) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server's `include_fastmcp_meta` setting - when disabled, the `_fastmcp` namespace won't be included. Other MCP server implementations may not provide this metadata structure.
|
||||
</Note>
|
||||
|
||||
## Using Prompts
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Request a rendered prompt using `get_prompt()` with the prompt name and arguments:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Simple prompt without arguments
|
||||
result = await client.get_prompt("welcome_message")
|
||||
# result -> mcp.types.GetPromptResult
|
||||
|
||||
# Access the generated messages
|
||||
for message in result.messages:
|
||||
print(f"Role: {message.role}")
|
||||
print(f"Content: {message.content}")
|
||||
```
|
||||
|
||||
### Prompts with Arguments
|
||||
|
||||
Pass arguments as a dictionary to customize the prompt:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Prompt with simple arguments
|
||||
result = await client.get_prompt("user_greeting", {
|
||||
"name": "Alice",
|
||||
"role": "administrator"
|
||||
})
|
||||
|
||||
# Access the personalized messages
|
||||
for message in result.messages:
|
||||
print(f"Generated message: {message.content}")
|
||||
```
|
||||
|
||||
## Automatic Argument Serialization
|
||||
|
||||
<VersionBadge version="2.9.0" />
|
||||
|
||||
FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. This allows you to pass typed objects directly:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class UserData:
|
||||
name: str
|
||||
age: int
|
||||
|
||||
async with client:
|
||||
# Complex arguments are automatically serialized
|
||||
result = await client.get_prompt("analyze_user", {
|
||||
"user": UserData(name="Alice", age=30), # Automatically serialized to JSON
|
||||
"preferences": {"theme": "dark"}, # Dict serialized to JSON string
|
||||
"scores": [85, 92, 78], # List serialized to JSON string
|
||||
"simple_name": "Bob" # Strings passed through unchanged
|
||||
})
|
||||
```
|
||||
|
||||
The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers can automatically deserialize these JSON strings back to the expected types.
|
||||
|
||||
### Serialization Examples
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt("data_analysis", {
|
||||
# These will be automatically serialized to JSON strings:
|
||||
"config": {
|
||||
"format": "csv",
|
||||
"include_headers": True,
|
||||
"delimiter": ","
|
||||
},
|
||||
"filters": [
|
||||
{"field": "age", "operator": ">", "value": 18},
|
||||
{"field": "status", "operator": "==", "value": "active"}
|
||||
],
|
||||
# This remains a string:
|
||||
"report_title": "Monthly Analytics Report"
|
||||
})
|
||||
```
|
||||
|
||||
## Working with Prompt Results
|
||||
|
||||
The `get_prompt()` method returns a `GetPromptResult` object containing a list of messages:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt("conversation_starter", {"topic": "climate"})
|
||||
|
||||
# Access individual messages
|
||||
for i, message in enumerate(result.messages):
|
||||
print(f"Message {i + 1}:")
|
||||
print(f" Role: {message.role}")
|
||||
print(f" Content: {message.content.text if hasattr(message.content, 'text') else message.content}")
|
||||
```
|
||||
|
||||
## Raw MCP Protocol Access
|
||||
|
||||
For access to the complete MCP protocol objects, use the `*_mcp` methods:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Raw MCP method returns full protocol object
|
||||
prompts_result = await client.list_prompts_mcp()
|
||||
# prompts_result -> mcp.types.ListPromptsResult
|
||||
|
||||
prompt_result = await client.get_prompt_mcp("example_prompt", {"arg": "value"})
|
||||
# prompt_result -> mcp.types.GetPromptResult
|
||||
```
|
||||
|
||||
## Multi-Server Clients
|
||||
|
||||
When using multi-server clients, prompts are accessible without prefixing (unlike tools):
|
||||
|
||||
```python
|
||||
async with client: # Multi-server client
|
||||
# Prompts from any server are directly accessible
|
||||
result1 = await client.get_prompt("weather_prompt", {"city": "London"})
|
||||
result2 = await client.get_prompt("assistant_prompt", {"query": "help"})
|
||||
```
|
||||
|
||||
## Common Prompt Patterns
|
||||
|
||||
### System Messages
|
||||
|
||||
Many prompts generate system messages for LLM configuration:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt("system_configuration", {
|
||||
"role": "helpful assistant",
|
||||
"expertise": "python programming"
|
||||
})
|
||||
|
||||
# Typically returns messages with role="system"
|
||||
system_message = result.messages[0]
|
||||
print(f"System prompt: {system_message.content}")
|
||||
```
|
||||
|
||||
### Conversation Templates
|
||||
|
||||
Prompts can generate multi-turn conversation templates:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.get_prompt("interview_template", {
|
||||
"candidate_name": "Alice",
|
||||
"position": "Senior Developer"
|
||||
})
|
||||
|
||||
# Multiple messages for a conversation flow
|
||||
for message in result.messages:
|
||||
print(f"{message.role}: {message.content}")
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Prompt arguments and their expected types depend on the specific prompt implementation. Check the server's documentation or use `list_prompts()` to see available arguments for each prompt.
|
||||
</Tip>
|
||||
204
docs/v2/clients/resources.mdx
Normal file
204
docs/v2/clients/resources.mdx
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
---
|
||||
title: Resource Operations
|
||||
sidebarTitle: Resources
|
||||
description: Access static and templated resources from MCP servers.
|
||||
icon: folder-open
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Resources are data sources exposed by MCP servers. They can be static files or dynamic templates that generate content based on parameters.
|
||||
|
||||
## Types of Resources
|
||||
|
||||
MCP servers expose two types of resources:
|
||||
|
||||
- **Static Resources**: Fixed content accessible via URI (e.g., configuration files, documentation)
|
||||
- **Resource Templates**: Dynamic resources that accept parameters to generate content (e.g., API endpoints, database queries)
|
||||
|
||||
## Listing Resources
|
||||
|
||||
### Static Resources
|
||||
|
||||
Use `list_resources()` to retrieve all static resources available on the server:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
# resources -> list[mcp.types.Resource]
|
||||
|
||||
for resource in resources:
|
||||
print(f"Resource URI: {resource.uri}")
|
||||
print(f"Name: {resource.name}")
|
||||
print(f"Description: {resource.description}")
|
||||
print(f"MIME Type: {resource.mimeType}")
|
||||
# Access tags and other metadata
|
||||
if hasattr(resource, '_meta') and resource._meta:
|
||||
fastmcp_meta = resource._meta.get('_fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
### Resource Templates
|
||||
|
||||
Use `list_resource_templates()` to retrieve available resource templates:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
templates = await client.list_resource_templates()
|
||||
# templates -> list[mcp.types.ResourceTemplate]
|
||||
|
||||
for template in templates:
|
||||
print(f"Template URI: {template.uriTemplate}")
|
||||
print(f"Name: {template.name}")
|
||||
print(f"Description: {template.description}")
|
||||
# Access tags and other metadata
|
||||
if hasattr(template, '_meta') and template._meta:
|
||||
fastmcp_meta = template._meta.get('_fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
### Filtering by Tags
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
You can use the `meta` field to filter resources based on their tags:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
|
||||
# Filter resources by tag
|
||||
config_resources = [
|
||||
resource for resource in resources
|
||||
if hasattr(resource, '_meta') and resource._meta and
|
||||
resource._meta.get('_fastmcp', {}) and
|
||||
'config' in resource._meta.get('_fastmcp', {}).get('tags', [])
|
||||
]
|
||||
|
||||
print(f"Found {len(config_resources)} config resources")
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `_meta` field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a `_fastmcp` namespace (e.g., `_meta._fastmcp.tags`) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server's `include_fastmcp_meta` setting - when disabled, the `_fastmcp` namespace won't be included. Other MCP server implementations may not provide this metadata structure.
|
||||
</Note>
|
||||
|
||||
## Reading Resources
|
||||
|
||||
### Static Resources
|
||||
|
||||
Read a static resource using its URI:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Read a static resource
|
||||
content = await client.read_resource("file:///path/to/README.md")
|
||||
# content -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
|
||||
|
||||
# Access text content
|
||||
if hasattr(content[0], 'text'):
|
||||
print(content[0].text)
|
||||
|
||||
# Access binary content
|
||||
if hasattr(content[0], 'blob'):
|
||||
print(f"Binary data: {len(content[0].blob)} bytes")
|
||||
```
|
||||
|
||||
### Resource Templates
|
||||
|
||||
Read from a resource template by providing the URI with parameters:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Read a resource generated from a template
|
||||
# For example, a template like "weather://{{city}}/current"
|
||||
weather_content = await client.read_resource("weather://london/current")
|
||||
|
||||
# Access the generated content
|
||||
print(weather_content[0].text) # Assuming text JSON response
|
||||
```
|
||||
|
||||
## Content Types
|
||||
|
||||
Resources can return different content types:
|
||||
|
||||
### Text Resources
|
||||
|
||||
```python
|
||||
async with client:
|
||||
content = await client.read_resource("resource://config/settings.json")
|
||||
|
||||
for item in content:
|
||||
if hasattr(item, 'text'):
|
||||
print(f"Text content: {item.text}")
|
||||
print(f"MIME type: {item.mimeType}")
|
||||
```
|
||||
|
||||
### Binary Resources
|
||||
|
||||
```python
|
||||
async with client:
|
||||
content = await client.read_resource("resource://images/logo.png")
|
||||
|
||||
for item in content:
|
||||
if hasattr(item, 'blob'):
|
||||
print(f"Binary content: {len(item.blob)} bytes")
|
||||
print(f"MIME type: {item.mimeType}")
|
||||
|
||||
# Save to file
|
||||
with open("downloaded_logo.png", "wb") as f:
|
||||
f.write(item.blob)
|
||||
```
|
||||
|
||||
## Working with Multi-Server Clients
|
||||
|
||||
When using multi-server clients, resource URIs are automatically prefixed with the server name:
|
||||
|
||||
```python
|
||||
async with client: # Multi-server client
|
||||
# Access resources from different servers
|
||||
weather_icons = await client.read_resource("weather://weather/icons/sunny")
|
||||
templates = await client.read_resource("resource://assistant/templates/list")
|
||||
|
||||
print(f"Weather icon: {weather_icons[0].blob}")
|
||||
print(f"Templates: {templates[0].text}")
|
||||
```
|
||||
|
||||
## Raw MCP Protocol Access
|
||||
|
||||
For access to the complete MCP protocol objects, use the `*_mcp` methods:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Raw MCP methods return full protocol objects
|
||||
resources_result = await client.list_resources_mcp()
|
||||
# resources_result -> mcp.types.ListResourcesResult
|
||||
|
||||
templates_result = await client.list_resource_templates_mcp()
|
||||
# templates_result -> mcp.types.ListResourceTemplatesResult
|
||||
|
||||
content_result = await client.read_resource_mcp("resource://example")
|
||||
# content_result -> mcp.types.ReadResourceResult
|
||||
```
|
||||
|
||||
## Common Resource URI Patterns
|
||||
|
||||
Different MCP servers may use various URI schemes:
|
||||
|
||||
```python
|
||||
# File system resources
|
||||
"file:///path/to/file.txt"
|
||||
|
||||
# Custom protocol resources
|
||||
"weather://london/current"
|
||||
"database://users/123"
|
||||
|
||||
# Generic resource protocol
|
||||
"resource://config/settings"
|
||||
"resource://templates/email"
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Resource URIs and their formats depend on the specific MCP server implementation. Check the server's documentation for available resources and their URI patterns.
|
||||
</Tip>
|
||||
42
docs/v2/clients/roots.mdx
Normal file
42
docs/v2/clients/roots.mdx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: Client Roots
|
||||
sidebarTitle: Roots
|
||||
description: Provide local context and resource boundaries to MCP servers.
|
||||
icon: folder-tree
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Roots are a way for clients to inform servers about the resources they have access to. Servers can use this information to adjust behavior or provide more relevant responses.
|
||||
|
||||
## Setting Static Roots
|
||||
|
||||
Provide a list of roots when creating the client:
|
||||
|
||||
<CodeGroup>
|
||||
```python Static Roots
|
||||
from fastmcp import Client
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
roots=["/path/to/root1", "/path/to/root2"]
|
||||
)
|
||||
```
|
||||
|
||||
```python Dynamic Roots Callback
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.roots import RequestContext
|
||||
|
||||
async def roots_callback(context: RequestContext) -> list[str]:
|
||||
print(f"Server requested roots (Request ID: {context.request_id})")
|
||||
return ["/path/to/root1", "/path/to/root2"]
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
roots=roots_callback
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
258
docs/v2/clients/sampling.mdx
Normal file
258
docs/v2/clients/sampling.mdx
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
---
|
||||
title: LLM Sampling
|
||||
sidebarTitle: Sampling
|
||||
description: Handle server-initiated LLM sampling requests.
|
||||
icon: robot
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
MCP servers can request LLM completions from clients. The client handles these requests through a sampling handler callback.
|
||||
|
||||
## Sampling Handler
|
||||
|
||||
Provide a `sampling_handler` function when creating the client:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling import (
|
||||
SamplingMessage,
|
||||
SamplingParams,
|
||||
RequestContext,
|
||||
)
|
||||
|
||||
async def sampling_handler(
|
||||
messages: list[SamplingMessage],
|
||||
params: SamplingParams,
|
||||
context: RequestContext
|
||||
) -> str:
|
||||
# Your LLM integration logic here
|
||||
# Extract text from messages and generate a response
|
||||
return "Generated response based on the messages"
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=sampling_handler,
|
||||
)
|
||||
```
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The sampling handler receives three parameters:
|
||||
|
||||
<Card icon="code" title="Sampling Handler Parameters">
|
||||
<ResponseField name="SamplingMessage" type="Sampling Message Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="role" type='Literal["user", "assistant"]'>
|
||||
The role of the message.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="content" type="TextContent | ImageContent | AudioContent">
|
||||
The content of the message.
|
||||
|
||||
TextContent is most common, and has a `.text` attribute.
|
||||
</ResponseField>
|
||||
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
<ResponseField name="SamplingParams" type="Sampling Parameters Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="messages" type="list[SamplingMessage]">
|
||||
The messages to sample from
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="modelPreferences" type="ModelPreferences | None">
|
||||
The server's preferences for which model to select. The client MAY ignore
|
||||
these preferences.
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="hints" type="list[ModelHint] | None">
|
||||
The hints to use for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="costPriority" type="float | None">
|
||||
The cost priority for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="speedPriority" type="float | None">
|
||||
The speed priority for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="intelligencePriority" type="float | None">
|
||||
The intelligence priority for model selection.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="systemPrompt" type="str | None">
|
||||
An optional system prompt the server wants to use for sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="includeContext" type="IncludeContext | None">
|
||||
A request to include context from one or more MCP servers (including the caller), to
|
||||
be attached to the prompt.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="temperature" type="float | None">
|
||||
The sampling temperature.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="maxTokens" type="int">
|
||||
The maximum number of tokens to sample.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="stopSequences" type="list[str] | None">
|
||||
The stop sequences to use for sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="metadata" type="dict[str, Any] | None">
|
||||
Optional metadata to pass through to the LLM provider.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="tools" type="list[Tool] | None">
|
||||
Optional list of tools the LLM can use during sampling. See [Using the OpenAI Handler](#using-the-openai-handler).
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="toolChoice" type="ToolChoice | None">
|
||||
Optional control over tool usage behavior (`auto`, `required`, or `none`).
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
|
||||
</ResponseField>
|
||||
<ResponseField name="RequestContext" type="Request Context Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="request_id" type="RequestId">
|
||||
Unique identifier for the MCP request
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Basic Example
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling import SamplingMessage, SamplingParams, RequestContext
|
||||
|
||||
async def basic_sampling_handler(
|
||||
messages: list[SamplingMessage],
|
||||
params: SamplingParams,
|
||||
context: RequestContext
|
||||
) -> str:
|
||||
# Extract message content
|
||||
conversation = []
|
||||
for message in messages:
|
||||
content = message.content.text if hasattr(message.content, 'text') else str(message.content)
|
||||
conversation.append(f"{message.role}: {content}")
|
||||
|
||||
# Use the system prompt if provided
|
||||
system_prompt = params.systemPrompt or "You are a helpful assistant."
|
||||
|
||||
# Here you would integrate with your preferred LLM service
|
||||
# This is just a placeholder response
|
||||
return f"Response based on conversation: {' | '.join(conversation)}"
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=basic_sampling_handler
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
If the client doesn't provide a sampling handler, servers can optionally configure a fallback handler. See [Server Sampling](/servers/sampling#sampling-fallback-handler) for details.
|
||||
</Note>
|
||||
|
||||
## Sampling Capabilities
|
||||
|
||||
When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support (for simpler handlers that don't support tools), pass `sampling_capabilities` explicitly:
|
||||
|
||||
```python
|
||||
from mcp.types import SamplingCapability
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=basic_handler,
|
||||
sampling_capabilities=SamplingCapability(), # No tool support
|
||||
)
|
||||
```
|
||||
|
||||
## Built-in Handlers
|
||||
|
||||
FastMCP provides built-in sampling handlers for OpenAI and Anthropic APIs. These handlers support the full sampling API including tool use, handling message conversion and response formatting automatically.
|
||||
|
||||
### OpenAI Handler
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
The OpenAI handler works with OpenAI's API and any OpenAI-compatible provider:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=OpenAISamplingHandler(default_model="gpt-4o"),
|
||||
)
|
||||
```
|
||||
|
||||
For OpenAI-compatible APIs (like local models), pass a custom client:
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=OpenAISamplingHandler(
|
||||
default_model="llama-3.1-70b",
|
||||
client=AsyncOpenAI(base_url="http://localhost:8000/v1"),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Install the OpenAI handler with `pip install fastmcp[openai]`.
|
||||
</Note>
|
||||
|
||||
### Anthropic Handler
|
||||
|
||||
<VersionBadge version="2.14.1" />
|
||||
|
||||
The Anthropic handler uses Claude models via the Anthropic API:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=AnthropicSamplingHandler(default_model="claude-sonnet-4-5"),
|
||||
)
|
||||
```
|
||||
|
||||
You can pass a custom client for advanced configuration:
|
||||
|
||||
```python
|
||||
from anthropic import AsyncAnthropic
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
sampling_handler=AnthropicSamplingHandler(
|
||||
default_model="claude-sonnet-4-5",
|
||||
client=AsyncAnthropic(), # Uses ANTHROPIC_API_KEY env var
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Install the Anthropic handler with `pip install fastmcp[anthropic]`.
|
||||
</Note>
|
||||
|
||||
### Tool Execution
|
||||
|
||||
Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results.
|
||||
|
||||
<Tip>
|
||||
To implement a custom sampling handler, see the [handler source code](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/client/sampling/handlers) as a reference.
|
||||
</Tip>
|
||||
138
docs/v2/clients/tasks.mdx
Normal file
138
docs/v2/clients/tasks.mdx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
title: Background Tasks
|
||||
sidebarTitle: Background Tasks
|
||||
description: Execute operations asynchronously and track their progress
|
||||
icon: clock
|
||||
tag: "NEW"
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.14.0" />
|
||||
|
||||
The [MCP task protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) lets you request operations to run asynchronously. This returns a Task object immediately, letting you track progress, cancel operations, or await results.
|
||||
|
||||
See [Server Background Tasks](/servers/tasks) for how to enable this on the server side.
|
||||
|
||||
## Requesting Background Execution
|
||||
|
||||
Pass `task=True` to run an operation as a background task. The call returns immediately with a Task object while the work executes on the server.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async with Client(server) as client:
|
||||
# Start a background task
|
||||
task = await client.call_tool("slow_computation", {"duration": 10}, task=True)
|
||||
|
||||
print(f"Task started: {task.task_id}")
|
||||
|
||||
# Do other work while it runs...
|
||||
|
||||
# Get the result when ready
|
||||
result = await task.result()
|
||||
```
|
||||
|
||||
This works with tools, resources, and prompts:
|
||||
|
||||
```python
|
||||
tool_task = await client.call_tool("my_tool", args, task=True)
|
||||
resource_task = await client.read_resource("file://large.txt", task=True)
|
||||
prompt_task = await client.get_prompt("my_prompt", args, task=True)
|
||||
```
|
||||
|
||||
## Working with Task Objects
|
||||
|
||||
All task types share a common interface for retrieving results, checking status, and receiving updates.
|
||||
|
||||
To get the result, call `await task.result()` or simply `await task`. This blocks until the task completes and returns the result. You can also check status without blocking using `await task.status()`, which returns the current state (`"working"`, `"completed"`, `"failed"`, or `"cancelled"`) along with any progress message from the server.
|
||||
|
||||
```python
|
||||
task = await client.call_tool("analyze", {"text": "hello"}, task=True)
|
||||
|
||||
# Check current status (non-blocking)
|
||||
status = await task.status()
|
||||
print(f"{status.status}: {status.statusMessage}")
|
||||
|
||||
# Wait for result (blocking)
|
||||
result = await task.result()
|
||||
```
|
||||
|
||||
For more control over waiting, use `task.wait()` with an optional timeout or target state:
|
||||
|
||||
```python
|
||||
# Wait up to 30 seconds for completion
|
||||
status = await task.wait(timeout=30.0)
|
||||
|
||||
# Wait for a specific state
|
||||
status = await task.wait(state="completed", timeout=30.0)
|
||||
```
|
||||
|
||||
To cancel a running task, call `await task.cancel()`.
|
||||
|
||||
### Real-Time Status Updates
|
||||
|
||||
Register callbacks to receive status updates as the server reports progress. Both sync and async callbacks are supported.
|
||||
|
||||
```python
|
||||
def on_status_change(status):
|
||||
print(f"Task {status.taskId}: {status.status} - {status.statusMessage}")
|
||||
|
||||
task.on_status_change(on_status_change)
|
||||
|
||||
# Async callbacks work too
|
||||
async def on_status_async(status):
|
||||
await log_status(status)
|
||||
|
||||
task.on_status_change(on_status_async)
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline. The Task API provides a consistent interface either way.
|
||||
|
||||
```python
|
||||
task = await client.call_tool("my_tool", args, task=True)
|
||||
|
||||
if task.returned_immediately:
|
||||
print("Server executed immediately (no background support)")
|
||||
else:
|
||||
print("Running in background")
|
||||
|
||||
# Either way, this works
|
||||
result = await task.result()
|
||||
```
|
||||
|
||||
This means you can write task-aware client code without worrying about server capabilities.
|
||||
|
||||
## Complete Example
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
async with Client(server) as client:
|
||||
# Start background task
|
||||
task = await client.call_tool(
|
||||
"slow_computation",
|
||||
{"duration": 10},
|
||||
task=True,
|
||||
)
|
||||
|
||||
# Subscribe to updates
|
||||
def on_update(status):
|
||||
print(f"Progress: {status.statusMessage}")
|
||||
|
||||
task.on_status_change(on_update)
|
||||
|
||||
# Do other work while task runs
|
||||
print("Doing other work...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Wait for completion and get result
|
||||
result = await task.result()
|
||||
print(f"Result: {result.content}")
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
295
docs/v2/clients/tools.mdx
Normal file
295
docs/v2/clients/tools.mdx
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
---
|
||||
title: Tool Operations
|
||||
sidebarTitle: Tools
|
||||
description: Discover and execute server-side tools with the FastMCP client.
|
||||
icon: wrench
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
Tools are executable functions exposed by MCP servers. The FastMCP client provides methods to discover available tools and execute them with arguments.
|
||||
|
||||
## Discovering Tools
|
||||
|
||||
Use `list_tools()` to retrieve all tools available on the server:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
# tools -> list[mcp.types.Tool]
|
||||
|
||||
for tool in tools:
|
||||
print(f"Tool: {tool.name}")
|
||||
print(f"Description: {tool.description}")
|
||||
if tool.inputSchema:
|
||||
print(f"Parameters: {tool.inputSchema}")
|
||||
# Access tags and other metadata
|
||||
if hasattr(tool, 'meta') and tool.meta:
|
||||
fastmcp_meta = tool.meta.get('_fastmcp', {})
|
||||
print(f"Tags: {fastmcp_meta.get('tags', [])}")
|
||||
```
|
||||
|
||||
### Filtering by Tags
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
You can use the `meta` field to filter tools based on their tags:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
|
||||
# Filter tools by tag
|
||||
analysis_tools = [
|
||||
tool for tool in tools
|
||||
if hasattr(tool, 'meta') and tool.meta and
|
||||
tool.meta.get('_fastmcp', {}) and
|
||||
'analysis' in tool.meta.get('_fastmcp', {}).get('tags', [])
|
||||
]
|
||||
|
||||
print(f"Found {len(analysis_tools)} analysis tools")
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `meta` field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a `_fastmcp` namespace (e.g., `meta._fastmcp.tags`) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server's `include_fastmcp_meta` setting - when disabled, the `_fastmcp` namespace won't be included. Other MCP server implementations may not provide this metadata structure.
|
||||
</Note>
|
||||
|
||||
## Executing Tools
|
||||
|
||||
### Basic Execution
|
||||
|
||||
Execute a tool using `call_tool()` with the tool name and arguments:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Simple tool call
|
||||
result = await client.call_tool("add", {"a": 5, "b": 3})
|
||||
# result -> CallToolResult with structured and unstructured data
|
||||
|
||||
# Access structured data (automatically deserialized)
|
||||
print(result.data) # 8 (int) or {"result": 8} for primitive types
|
||||
|
||||
# Access traditional content blocks
|
||||
print(result.content[0].text) # "8" (TextContent)
|
||||
```
|
||||
|
||||
### Advanced Execution Options
|
||||
|
||||
The `call_tool()` method supports additional parameters for timeout control and progress monitoring:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# With timeout (aborts if execution takes longer than 2 seconds)
|
||||
result = await client.call_tool(
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
timeout=2.0
|
||||
)
|
||||
|
||||
# With progress handler (to track execution progress)
|
||||
result = await client.call_tool(
|
||||
"long_running_task",
|
||||
{"param": "value"},
|
||||
progress_handler=my_progress_handler
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name`: The tool name (string)
|
||||
- `arguments`: Dictionary of arguments to pass to the tool (optional)
|
||||
- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout)
|
||||
- `progress_handler`: Progress callback function (optional, overrides client-level handler)
|
||||
- `meta`: Dictionary of metadata to send with the request (optional, see below)
|
||||
|
||||
## Sending Metadata
|
||||
|
||||
<VersionBadge version="2.13.1" />
|
||||
|
||||
The `meta` parameter sends ancillary information alongside tool calls. This can be used for various purposes like observability, debugging, client identification, or any context the server may need beyond the tool's primary arguments.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
name="send_email",
|
||||
arguments={
|
||||
"to": "user@example.com",
|
||||
"subject": "Hello",
|
||||
"body": "Welcome!"
|
||||
},
|
||||
meta={
|
||||
"trace_id": "abc-123",
|
||||
"request_source": "mobile_app"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The structure and usage of `meta` is determined by your application. See [Client Metadata](/servers/context#client-metadata) in the server documentation to learn how to access this data in your tool implementations.
|
||||
|
||||
## Handling Results
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes.
|
||||
|
||||
### CallToolResult Properties
|
||||
|
||||
<Card icon="code" title="CallToolResult Properties">
|
||||
<ResponseField name=".data" type="Any">
|
||||
**FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".content" type="list[mcp.types.ContentBlock]">
|
||||
Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.) available from all MCP servers.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".structured_content" type="dict[str, Any] | None">
|
||||
Standard MCP structured JSON data as sent by the server, available from all MCP servers that support structured outputs.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".is_error" type="bool">
|
||||
Boolean indicating if the tool execution failed.
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
### Structured Data Access
|
||||
|
||||
FastMCP's `.data` property provides fully hydrated Python objects, not just JSON dictionaries. This includes complex type reconstruction:
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
async with client:
|
||||
result = await client.call_tool("get_weather", {"city": "London"})
|
||||
|
||||
# FastMCP reconstructs complete Python objects from the server's output schema
|
||||
weather = result.data # Server-defined WeatherReport object
|
||||
print(f"Temperature: {weather.temperature}°C at {weather.timestamp}")
|
||||
print(f"Station: {weather.station_id}")
|
||||
print(f"Humidity: {weather.humidity}%")
|
||||
|
||||
# The timestamp is a real datetime object, not a string!
|
||||
assert isinstance(weather.timestamp, datetime)
|
||||
assert isinstance(weather.station_id, UUID)
|
||||
|
||||
# Compare with raw structured JSON (standard MCP)
|
||||
print(f"Raw JSON: {result.structured_content}")
|
||||
# {"temperature": 20, "timestamp": "2024-01-15T14:30:00Z", "station_id": "123e4567-..."}
|
||||
|
||||
# Traditional content blocks (standard MCP)
|
||||
print(f"Text content: {result.content[0].text}")
|
||||
```
|
||||
|
||||
### Fallback Behavior
|
||||
|
||||
For tools without output schemas or when deserialization fails, `.data` will be `None`:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("legacy_tool", {"param": "value"})
|
||||
|
||||
if result.data is not None:
|
||||
# Structured output available and successfully deserialized
|
||||
print(f"Structured: {result.data}")
|
||||
else:
|
||||
# No structured output or deserialization failed - use content blocks
|
||||
for content in result.content:
|
||||
if hasattr(content, 'text'):
|
||||
print(f"Text result: {content.text}")
|
||||
elif hasattr(content, 'data'):
|
||||
print(f"Binary data: {len(content.data)} bytes")
|
||||
```
|
||||
|
||||
### Primitive Type Unwrapping
|
||||
|
||||
<Tip>
|
||||
FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object.
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("calculate_sum", {"a": 5, "b": 3})
|
||||
|
||||
# FastMCP client automatically unwraps for convenience
|
||||
print(result.data) # 8 (int) - the original value
|
||||
|
||||
# Raw structured content shows the server-side wrapping
|
||||
print(result.structured_content) # {"result": 8}
|
||||
|
||||
# Other MCP clients would need to manually access ["result"]
|
||||
# value = result.structured_content["result"] # Not needed with FastMCP!
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exception-Based Error Handling
|
||||
|
||||
By default, `call_tool()` raises a `ToolError` if the tool execution fails:
|
||||
|
||||
```python
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
async with client:
|
||||
try:
|
||||
result = await client.call_tool("potentially_failing_tool", {"param": "value"})
|
||||
print("Tool succeeded:", result.data)
|
||||
except ToolError as e:
|
||||
print(f"Tool failed: {e}")
|
||||
```
|
||||
|
||||
### Manual Error Checking
|
||||
|
||||
You can disable automatic error raising and manually check the result:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
"potentially_failing_tool",
|
||||
{"param": "value"},
|
||||
raise_on_error=False
|
||||
)
|
||||
|
||||
if result.is_error:
|
||||
print(f"Tool failed: {result.content[0].text}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.data}")
|
||||
```
|
||||
|
||||
### Raw MCP Protocol Access
|
||||
|
||||
For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool_mcp("potentially_failing_tool", {"param": "value"})
|
||||
# result -> mcp.types.CallToolResult
|
||||
|
||||
if result.isError:
|
||||
print(f"Tool failed: {result.content}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.content}")
|
||||
# Note: No automatic deserialization with call_tool_mcp()
|
||||
```
|
||||
|
||||
## Argument Handling
|
||||
|
||||
Arguments are passed as a dictionary to the tool:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
# Simple arguments
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
|
||||
# Complex arguments
|
||||
result = await client.call_tool("process_data", {
|
||||
"config": {"format": "json", "validate": True},
|
||||
"items": [1, 2, 3, 4, 5],
|
||||
"metadata": {"source": "api", "version": "1.0"}
|
||||
})
|
||||
```
|
||||
|
||||
<Tip>
|
||||
For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server).
|
||||
</Tip>
|
||||
383
docs/v2/clients/transports.mdx
Normal file
383
docs/v2/clients/transports.mdx
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
---
|
||||
title: Client Transports
|
||||
sidebarTitle: Transports
|
||||
description: Configure how FastMCP Clients connect to and communicate with servers.
|
||||
icon: link
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
The FastMCP `Client` communicates with MCP servers through transport objects that handle the underlying connection mechanics. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration—environment variables, authentication, session management, and more.
|
||||
|
||||
Think of transports as configurable adapters between your client code and MCP servers. Each transport type handles a different communication pattern: subprocesses with pipes, HTTP connections, or direct in-memory calls.
|
||||
|
||||
## Choosing the Right Transport
|
||||
|
||||
- **Use [STDIO Transport](#stdio-transport)** when you need to run local MCP servers with full control over their environment and lifecycle
|
||||
- **Use [Remote Transports](#remote-transports)** when connecting to production services or shared MCP servers running independently
|
||||
- **Use [In-Memory Transport](#in-memory-transport)** for testing FastMCP servers without subprocess or network overhead
|
||||
- **Use [MCP JSON Configuration](#mcp-json-configuration-transport)** when you need to connect to multiple servers defined in configuration files
|
||||
|
||||
## STDIO Transport
|
||||
|
||||
STDIO (Standard Input/Output) transport communicates with MCP servers through subprocess pipes. This is the standard mechanism used by desktop clients like Claude Desktop and is the primary way to run local MCP servers.
|
||||
|
||||
### The Client Runs the Server
|
||||
|
||||
<Warning>
|
||||
**Critical Concept**: When using STDIO transport, your client actually launches and manages the server process. This is fundamentally different from network transports where you connect to an already-running server. Understanding this relationship is key to using STDIO effectively.
|
||||
</Warning>
|
||||
|
||||
With STDIO transport, your client:
|
||||
- Starts the server as a subprocess when you connect
|
||||
- Manages the server's lifecycle (start, stop, restart)
|
||||
- Controls the server's environment and configuration
|
||||
- Communicates through stdin/stdout pipes
|
||||
|
||||
This architecture enables powerful local integrations but requires understanding environment isolation and process management.
|
||||
|
||||
### Environment Isolation
|
||||
|
||||
STDIO servers run in isolated environments by default. This is a security feature enforced by the MCP protocol to prevent accidental exposure of sensitive data.
|
||||
|
||||
When your client launches an MCP server:
|
||||
- The server does NOT inherit your shell's environment variables
|
||||
- API keys, paths, and other configuration must be explicitly passed
|
||||
- The working directory and system paths may differ from your shell
|
||||
|
||||
To pass environment variables to your server, use the `env` parameter:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# If your server needs environment variables (like API keys),
|
||||
# you must explicitly pass them:
|
||||
client = Client(
|
||||
"my_server.py",
|
||||
env={"API_KEY": "secret", "DEBUG": "true"}
|
||||
)
|
||||
|
||||
# This won't work - the server runs in isolation:
|
||||
# export API_KEY="secret" # in your shell
|
||||
# client = Client("my_server.py") # server can't see API_KEY
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
To use STDIO transport, you create a transport instance with the command and arguments needed to run your server:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["my_server.py"]
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
You can configure additional settings like environment variables, working directory, or command arguments:
|
||||
|
||||
```python
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["my_server.py", "--verbose"],
|
||||
env={"LOG_LEVEL": "DEBUG"},
|
||||
cwd="/path/to/server"
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
For convenience, the client can also infer STDIO transport from file paths, but this doesn't allow configuration:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
client = Client("my_server.py") # Limited - no configuration options
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Since STDIO servers don't inherit your environment, you need strategies for passing configuration. Here are two common approaches:
|
||||
|
||||
**Selective forwarding** passes only the variables your server actually needs:
|
||||
|
||||
```python
|
||||
import os
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
required_vars = ["API_KEY", "DATABASE_URL", "REDIS_HOST"]
|
||||
env = {
|
||||
var: os.environ[var]
|
||||
for var in required_vars
|
||||
if var in os.environ
|
||||
}
|
||||
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
env=env
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
**Loading from .env files** keeps configuration separate from code:
|
||||
|
||||
```python
|
||||
from dotenv import dotenv_values
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
env = dotenv_values(".env")
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
env=env
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
### Session Persistence
|
||||
|
||||
STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This improves performance by reusing the same subprocess for multiple connections, but can be controlled when you need isolation.
|
||||
|
||||
By default, the subprocess persists between connections:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StdioTransport
|
||||
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"]
|
||||
)
|
||||
client = Client(transport)
|
||||
|
||||
async def efficient_multiple_operations():
|
||||
async with client:
|
||||
await client.ping()
|
||||
|
||||
async with client: # Reuses the same subprocess
|
||||
await client.call_tool("process_data", {"file": "data.csv"})
|
||||
```
|
||||
|
||||
For complete isolation between connections, disable session persistence:
|
||||
|
||||
```python
|
||||
transport = StdioTransport(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
keep_alive=False
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
Use `keep_alive=False` when you need complete isolation (e.g., in test suites) or when server state could cause issues between connections.
|
||||
|
||||
### Specialized STDIO Transports
|
||||
|
||||
FastMCP provides convenience transports that are thin wrappers around `StdioTransport` with pre-configured commands:
|
||||
|
||||
- **`PythonStdioTransport`** - Uses `python` command for `.py` files
|
||||
- **`NodeStdioTransport`** - Uses `node` command for `.js` files
|
||||
- **`UvStdioTransport`** - Uses `uv` for Python packages (uses `env_vars` parameter)
|
||||
- **`UvxStdioTransport`** - Uses `uvx` for Python packages (uses `env_vars` parameter)
|
||||
- **`NpxStdioTransport`** - Uses `npx` for Node packages (uses `env_vars` parameter)
|
||||
|
||||
For most use cases, instantiate `StdioTransport` directly with your desired command. These specialized transports are primarily useful for client inference shortcuts.
|
||||
|
||||
## Remote Transports
|
||||
|
||||
Remote transports connect to MCP servers running as web services. This is a fundamentally different model from STDIO transports—instead of your client launching and managing a server process, you connect to an already-running service that manages its own environment and lifecycle.
|
||||
|
||||
### Streamable HTTP Transport
|
||||
|
||||
<VersionBadge version="2.3.0" />
|
||||
|
||||
Streamable HTTP is the recommended transport for production deployments, providing efficient bidirectional streaming over HTTP connections.
|
||||
|
||||
- **Class:** `StreamableHttpTransport`
|
||||
- **Server compatibility:** FastMCP servers running with `mcp run --transport http`
|
||||
|
||||
The transport requires a URL and optionally supports custom headers for authentication and configuration:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
# Basic connection
|
||||
transport = StreamableHttpTransport(url="https://api.example.com/mcp")
|
||||
client = Client(transport)
|
||||
|
||||
# With custom headers for authentication
|
||||
transport = StreamableHttpTransport(
|
||||
url="https://api.example.com/mcp",
|
||||
headers={
|
||||
"Authorization": "Bearer your-token-here",
|
||||
"X-Custom-Header": "value"
|
||||
}
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
For convenience, FastMCP also provides authentication helpers:
|
||||
|
||||
```python
|
||||
from fastmcp.client.auth import BearerAuth
|
||||
|
||||
client = Client(
|
||||
"https://api.example.com/mcp",
|
||||
auth=BearerAuth("your-token-here")
|
||||
)
|
||||
```
|
||||
|
||||
### SSE Transport (Legacy)
|
||||
|
||||
Server-Sent Events transport is maintained for backward compatibility but is superseded by Streamable HTTP for new deployments.
|
||||
|
||||
- **Class:** `SSETransport`
|
||||
- **Server compatibility:** FastMCP servers running with `mcp run --transport sse`
|
||||
|
||||
SSE transport supports the same configuration options as Streamable HTTP:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import SSETransport
|
||||
|
||||
transport = SSETransport(
|
||||
url="https://api.example.com/sse",
|
||||
headers={"Authorization": "Bearer token"}
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
Use Streamable HTTP for new deployments unless you have specific infrastructure requirements for SSE.
|
||||
|
||||
## In-Memory Transport
|
||||
|
||||
In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing and development.
|
||||
|
||||
- **Class:** `FastMCPTransport`
|
||||
|
||||
<Note>
|
||||
Unlike STDIO transports, in-memory servers have full access to your Python process's environment. They share the same memory space and environment variables as your client code—no isolation or explicit environment passing required.
|
||||
</Note>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Client
|
||||
import os
|
||||
|
||||
mcp = FastMCP("TestServer")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
prefix = os.environ.get("GREETING_PREFIX", "Hello")
|
||||
return f"{prefix}, {name}!"
|
||||
|
||||
client = Client(mcp)
|
||||
|
||||
async with client:
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
```
|
||||
|
||||
## MCP JSON Configuration Transport
|
||||
|
||||
<VersionBadge version="2.4.0" />
|
||||
|
||||
This transport supports the emerging MCP JSON configuration standard for defining multiple servers:
|
||||
|
||||
- **Class:** `MCPConfigTransport`
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http"
|
||||
},
|
||||
"assistant": {
|
||||
"command": "python",
|
||||
"args": ["./assistant.py"],
|
||||
"env": {"LOG_LEVEL": "INFO"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client = Client(config)
|
||||
|
||||
async with client:
|
||||
# Tools are namespaced by server
|
||||
weather = await client.call_tool("weather_get_forecast", {"city": "NYC"})
|
||||
answer = await client.call_tool("assistant_ask", {"question": "What?"})
|
||||
```
|
||||
|
||||
### Tool Transformation with FastMCP and MCPConfig
|
||||
|
||||
FastMCP supports basic tool transformations to be defined alongside the MCP Servers in the MCPConfig file.
|
||||
|
||||
```python
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http",
|
||||
"tools": { } # <--- This is the tool transformation section
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With these transformations, you can transform (change) the name, title, description, tags, enablement, and arguments of a tool.
|
||||
|
||||
For each argument the tool takes, you can transform (change) the name, description, default, visibility, whether it's required, and you can provide example values.
|
||||
|
||||
In the following example, we're transforming the `weather_get_forecast` tool to only retrieve the weather for `Miami` and hiding the `city` argument from the client.
|
||||
|
||||
```python
|
||||
tool_transformations = {
|
||||
"weather_get_forecast": {
|
||||
"name": "miami_weather",
|
||||
"description": "Get the weather for Miami",
|
||||
"arguments": {
|
||||
"city": {
|
||||
"name": "city",
|
||||
"default": "Miami",
|
||||
"hide": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http",
|
||||
"tools": tool_transformations
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Allowlisting and Blocklisting Tools
|
||||
|
||||
Tools can be allowlisted or blocklisted from the client by applying `tags` to the tools on the server. In the following example, we're allowlisting only tools marked with the `forecast` tag, all other tools will be unavailable to the client.
|
||||
|
||||
```python
|
||||
tool_transformations = {
|
||||
"weather_get_forecast": {
|
||||
"enabled": True,
|
||||
"tags": ["forecast"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather.example.com/mcp",
|
||||
"transport": "http",
|
||||
"tools": tool_transformations,
|
||||
"include_tags": ["forecast"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
65
docs/v2/community/showcase.mdx
Normal file
65
docs/v2/community/showcase.mdx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
title: 'Community Showcase'
|
||||
description: 'High-quality projects and examples from the FastMCP community'
|
||||
icon: 'users'
|
||||
---
|
||||
|
||||
import { YouTubeEmbed } from '/snippets/youtube-embed.mdx'
|
||||
|
||||
## Join the Community
|
||||
|
||||
<Card title="FastMCP Discord" icon="discord" href="https://discord.gg/uu8dJCgttd">
|
||||
Connect with other FastMCP developers, share your projects, and discuss ideas.
|
||||
</Card>
|
||||
|
||||
## Featured Projects
|
||||
|
||||
Discover exemplary MCP servers and implementations created by our community. These projects demonstrate best practices and innovative uses of FastMCP.
|
||||
|
||||
### Learning Resources
|
||||
|
||||
<Card title="MCP Dummy Server" icon="graduation-cap" href="https://github.com/WaiYanNyeinNaing/mcp-dummy-server">
|
||||
A comprehensive educational example demonstrating FastMCP best practices with professional dual-transport server implementation, interactive test client, and detailed documentation.
|
||||
</Card>
|
||||
|
||||
#### Video Tutorials
|
||||
|
||||
**Build Remote MCP Servers w/ Python & FastMCP** - Claude Integrations Tutorial by Greg + Code
|
||||
|
||||
<YouTubeEmbed
|
||||
videoId="bOYkbXP-GGo"
|
||||
title="Build Remote MCP Servers w/ Python & FastMCP"
|
||||
/>
|
||||
|
||||
**FastMCP — the best way to build an MCP server with Python** - Tutorial by ZazenCodes
|
||||
|
||||
<YouTubeEmbed
|
||||
videoId="rnljvmHorQw"
|
||||
title="FastMCP — the best way to build an MCP server with Python"
|
||||
/>
|
||||
|
||||
**Speedrun a MCP server for Claude Desktop (fastmcp)** - Tutorial by Nate from Prefect
|
||||
|
||||
<YouTubeEmbed
|
||||
videoId="67ZwpkUEtSI"
|
||||
title="Speedrun a MCP server for Claude Desktop (fastmcp)"
|
||||
/>
|
||||
|
||||
### Community Examples
|
||||
|
||||
Have you built something interesting with FastMCP? We'd love to feature high-quality examples here! Start a [discussion on GitHub](https://github.com/jlowin/fastmcp/discussions) to share your project.
|
||||
|
||||
## Contributing
|
||||
|
||||
To get your project featured:
|
||||
|
||||
1. Ensure your project demonstrates best practices
|
||||
2. Include comprehensive documentation
|
||||
3. Add clear usage examples
|
||||
4. Open a discussion in our [GitHub Discussions](https://github.com/jlowin/fastmcp/discussions)
|
||||
|
||||
We review submissions regularly and feature projects that provide value to the FastMCP community.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Contrib Modules](/patterns/contrib) - Community-contributed modules that are distributed with FastMCP itself
|
||||
89
docs/v2/deployment/fastmcp-cloud.mdx
Normal file
89
docs/v2/deployment/fastmcp-cloud.mdx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
title: FastMCP Cloud
|
||||
sidebarTitle: FastMCP Cloud
|
||||
description: The fastest way to deploy your MCP server
|
||||
icon: cloud
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
[FastMCP Cloud](https://fastmcp.cloud) is a managed platform for hosting MCP servers, built by the FastMCP team. While the FastMCP framework will always be fully open-source, we created FastMCP Cloud to solve the deployment challenges we've seen developers face. Our goal is to provide the absolute fastest way to make your MCP server available to LLM clients like Claude and Cursor.
|
||||
|
||||
FastMCP Cloud is a young product and we welcome your feedback. Please join our [Discord](https://discord.com/invite/aGsSC3yDF4) to share your thoughts and ideas, and you can expect to see new features and improvements every week.
|
||||
|
||||
|
||||
<Note>
|
||||
FastMCP Cloud supports both **FastMCP 2.0** servers and also **FastMCP 1.0** servers that were created with the official MCP Python SDK.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
FastMCP Cloud is completely free while in beta!
|
||||
</Tip>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use FastMCP Cloud, you'll need a [GitHub](https://github.com) account. In addition, you'll need a GitHub repo that contains a FastMCP server instance. If you don't want to create one yet, you can proceed to [step 1](#step-1-create-a-project) and use the FastMCP Cloud quickstart repo.
|
||||
|
||||
Your repo can be public or private, but must include at least a Python file that contains a FastMCP server instance.
|
||||
<Tip>
|
||||
To ensure your file is compatible with FastMCP Cloud, you can run `fastmcp inspect <file.py:server_object>` to see what FastMCP Cloud will see when it runs your server.
|
||||
</Tip>
|
||||
|
||||
If you have a `requirements.txt` or `pyproject.toml` in the repo, FastMCP Cloud will automatically detect your server's dependencies and install them for you. Note that your file *can* have an `if __name__ == "__main__"` block, but it will be ignored by FastMCP Cloud.
|
||||
|
||||
For example, a minimal server file might look like:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def hello(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
There are just three steps to deploying a server to FastMCP Cloud:
|
||||
|
||||
### Step 1: Create a Project
|
||||
|
||||
Visit [fastmcp.cloud](https://fastmcp.cloud) and sign in with your GitHub account. Then, create a project. Each project corresponds to a GitHub repo, and you can create one from either your own repo or using the FastMCP Cloud quickstart repo.
|
||||
|
||||
<img src="/assets/images/fastmcp_cloud/quickstart.png" alt="FastMCP Cloud Quickstart Screen" />
|
||||
|
||||
Next, you'll be prompted to configure your project.
|
||||
|
||||
<img src="/assets/images/fastmcp_cloud/create_project.png" alt="FastMCP Cloud Configuration Screen" />
|
||||
|
||||
The configuration screen lets you specify:
|
||||
- **Name**: The name of your project. This will be used to generate a unique URL for your server.
|
||||
- **Entrypoint**: The Python file containing your FastMCP server (e.g., `echo.py`). This field has the same syntax as the `fastmcp run` command, for example `echo.py:my_server` to specify a specific object in the file.
|
||||
- **Authentication**: If disabled, your server is open to the public. If enabled, only other members of your FastMCP Cloud organization will be able to connect.
|
||||
|
||||
Note that FastMCP Cloud will automatically detect yours server's Python dependencies from either a `requirements.txt` or `pyproject.toml` file.
|
||||
|
||||
### Step 2: Deploy Your Server
|
||||
|
||||
Once you configure your project, FastMCP Cloud will:
|
||||
1. Clone the repository
|
||||
2. Build your FastMCP server
|
||||
3. Deploy it to a unique URL
|
||||
4. Make it immediately available for connections
|
||||
|
||||
<img src="/assets/images/fastmcp_cloud/deployment.png" alt="FastMCP Cloud Deployment Screen" />
|
||||
|
||||
FastMCP Cloud will monitor your repo and redeploy your server whenever you push a change to the `main` branch. In addition, FastMCP Cloud will build and deploy servers for every PR your open, hosting them on unique URLs, so you can test changes before updating your production server.
|
||||
|
||||
### Step 3: Connect to Your Server
|
||||
|
||||
Once your server is deployed, it will be accessible at a URL like:
|
||||
|
||||
```
|
||||
https://your-project-name.fastmcp.app/mcp
|
||||
```
|
||||
|
||||
You should be able to connect to it as soon as you see the deployment succeed! FastMCP Cloud provides instant connection options for popular LLM clients:
|
||||
|
||||
<img src="/assets/images/fastmcp_cloud/connect.png" alt="FastMCP Cloud Connection Screen" />
|
||||
|
||||
736
docs/v2/deployment/http.mdx
Normal file
736
docs/v2/deployment/http.mdx
Normal file
|
|
@ -0,0 +1,736 @@
|
|||
---
|
||||
title: HTTP Deployment
|
||||
sidebarTitle: HTTP Deployment
|
||||
description: Deploy your FastMCP server over HTTP for remote access
|
||||
icon: server
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<Tip>
|
||||
STDIO transport is perfect for local development and desktop applications. But to unlock the full potential of MCP—centralized services, multi-client access, and network availability—you need remote HTTP deployment.
|
||||
</Tip>
|
||||
|
||||
This guide walks you through deploying your FastMCP server as a remote MCP service that's accessible via a URL. Once deployed, your MCP server will be available over the network, allowing multiple clients to connect simultaneously and enabling integration with cloud-based LLM applications. This guide focuses specifically on remote MCP deployment, not local STDIO servers.
|
||||
|
||||
## Choosing Your Approach
|
||||
|
||||
FastMCP provides two ways to deploy your server as an HTTP service. Understanding the trade-offs helps you choose the right approach for your needs.
|
||||
|
||||
The **direct HTTP server** approach is simpler and perfect for getting started quickly. You modify your server's `run()` method to use HTTP transport, and FastMCP handles all the web server configuration. This approach works well for standalone deployments where you want your MCP server to be the only service running on a port.
|
||||
|
||||
The **ASGI application** approach gives you more control and flexibility. Instead of running the server directly, you create an ASGI application that can be served by Uvicorn. This approach is better when you need advanced server features like multiple workers, custom middleware, or when you're integrating with existing web applications.
|
||||
|
||||
### Direct HTTP Server
|
||||
|
||||
The simplest way to get your MCP server online is to use the built-in `run()` method with HTTP transport. This approach handles all the server configuration for you and is ideal when you want a standalone MCP server without additional complexity.
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
|
||||
@mcp.tool
|
||||
def process_data(input: str) -> str:
|
||||
"""Process data on the server"""
|
||||
return f"Processed: {input}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
Run your server with a simple Python command:
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
Your server is now accessible at `http://localhost:8000/mcp` (or use your server's actual IP address for remote access).
|
||||
|
||||
This approach is ideal when you want to get online quickly with minimal configuration. It's perfect for internal tools, development environments, or simple deployments where you don't need advanced server features. The built-in server handles all the HTTP details, letting you focus on your MCP implementation.
|
||||
|
||||
### ASGI Application
|
||||
|
||||
For production deployments, you'll often want more control over how your server runs. FastMCP can create a standard ASGI application that works with any ASGI server like Uvicorn, Gunicorn, or Hypercorn. This approach is particularly useful when you need to configure advanced server options, run multiple workers, or integrate with existing infrastructure.
|
||||
|
||||
```python app.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
|
||||
@mcp.tool
|
||||
def process_data(input: str) -> str:
|
||||
"""Process data on the server"""
|
||||
return f"Processed: {input}"
|
||||
|
||||
# Create ASGI application
|
||||
app = mcp.http_app()
|
||||
```
|
||||
|
||||
Run with any ASGI server - here's an example with Uvicorn:
|
||||
```bash
|
||||
uvicorn app:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Your server is accessible at the same URL: `http://localhost:8000/mcp` (or use your server's actual IP address for remote access).
|
||||
|
||||
The ASGI approach shines in production environments where you need reliability and performance. You can run multiple worker processes to handle concurrent requests, add custom middleware for logging or monitoring, integrate with existing deployment pipelines, or mount your MCP server as part of a larger application.
|
||||
|
||||
## Configuring Your Server
|
||||
|
||||
### Custom Path
|
||||
|
||||
By default, your MCP server is accessible at `/mcp/` on your domain. You can customize this path to fit your URL structure or avoid conflicts with existing endpoints. This is particularly useful when integrating MCP into an existing application or following specific API conventions.
|
||||
|
||||
```python
|
||||
# Option 1: With mcp.run()
|
||||
mcp.run(transport="http", host="0.0.0.0", port=8000, path="/api/mcp/")
|
||||
|
||||
# Option 2: With ASGI app
|
||||
app = mcp.http_app(path="/api/mcp/")
|
||||
```
|
||||
|
||||
Now your server is accessible at `http://localhost:8000/api/mcp/`.
|
||||
|
||||
### Authentication
|
||||
|
||||
<Warning>
|
||||
Authentication is **highly recommended** for remote MCP servers. Some LLM clients require authentication for remote servers and will refuse to connect without it.
|
||||
</Warning>
|
||||
|
||||
FastMCP supports multiple authentication methods to secure your remote server. See the [Authentication Overview](/servers/auth/authentication) for complete configuration options including Bearer tokens, JWT, and OAuth.
|
||||
|
||||
If you're mounting an authenticated server under a path prefix, see [Mounting Authenticated Servers](#mounting-authenticated-servers) below for important routing considerations.
|
||||
|
||||
### Health Checks
|
||||
|
||||
Health check endpoints are essential for monitoring your deployed server and ensuring it's responding correctly. FastMCP allows you to add custom routes alongside your MCP endpoints, making it easy to implement health checks that work with both deployment approaches.
|
||||
|
||||
```python
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
@mcp.custom_route("/health", methods=["GET"])
|
||||
async def health_check(request):
|
||||
return JSONResponse({"status": "healthy", "service": "mcp-server"})
|
||||
```
|
||||
|
||||
This health endpoint will be available at `http://localhost:8000/health` and can be used by load balancers, monitoring systems, or deployment platforms to verify your server is running.
|
||||
|
||||
### Custom Middleware
|
||||
|
||||
|
||||
<VersionBadge version="2.3.2" />
|
||||
|
||||
Add custom Starlette middleware to your FastMCP ASGI apps:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
|
||||
# Create your FastMCP server
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
# Define middleware
|
||||
middleware = [
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
]
|
||||
|
||||
# Create ASGI app with middleware
|
||||
http_app = mcp.http_app(middleware=middleware)
|
||||
```
|
||||
|
||||
### CORS for Browser-Based Clients
|
||||
|
||||
<Tip>
|
||||
Most MCP clients, including those that you access through a browser like ChatGPT or Claude, don't need CORS configuration. Only enable CORS if you're working with an MCP client that connects directly from a browser, such as debugging tools or inspectors.
|
||||
</Tip>
|
||||
|
||||
CORS (Cross-Origin Resource Sharing) is needed when JavaScript running in a web browser connects directly to your MCP server. This is different from using an LLM through a browser—in that case, the browser connects to the LLM service, and the LLM service connects to your MCP server (no CORS needed).
|
||||
|
||||
Browser-based MCP clients that need CORS include:
|
||||
|
||||
- **MCP Inspector** - Browser-based debugging tool for testing MCP servers
|
||||
- **Custom browser-based MCP clients** - If you're building a web app that directly connects to MCP servers
|
||||
|
||||
For these scenarios, add CORS middleware with the specific headers required for MCP protocol:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
# Configure CORS for browser-based clients
|
||||
middleware = [
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # Allow all origins; use specific origins for security
|
||||
allow_methods=["GET", "POST", "DELETE", "OPTIONS"],
|
||||
allow_headers=[
|
||||
"mcp-protocol-version",
|
||||
"mcp-session-id",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
],
|
||||
expose_headers=["mcp-session-id"],
|
||||
)
|
||||
]
|
||||
|
||||
app = mcp.http_app(middleware=middleware)
|
||||
```
|
||||
|
||||
**Key configuration details:**
|
||||
|
||||
- **`allow_origins`**: Specify exact origins (e.g., `["http://localhost:3000"]`) rather than `["*"]` for production deployments
|
||||
- **`allow_headers`**: Must include `mcp-protocol-version`, `mcp-session-id`, and `Authorization` (for authenticated servers)
|
||||
- **`expose_headers`**: Must include `mcp-session-id` so JavaScript can read the session ID from responses and send it in subsequent requests
|
||||
|
||||
Without `expose_headers=["mcp-session-id"]`, browsers will receive the session ID but JavaScript won't be able to access it, causing session management to fail.
|
||||
|
||||
<Warning>
|
||||
**Production Security**: Never use `allow_origins=["*"]` in production. Specify the exact origins of your browser-based clients. Using wildcards exposes your server to unauthorized access from any website.
|
||||
</Warning>
|
||||
|
||||
### SSE Polling for Long-Running Operations
|
||||
|
||||
<VersionBadge version="2.14.0" />
|
||||
|
||||
<Note>
|
||||
This feature only applies to the **StreamableHTTP transport** (the default for `http_app()`). It does not apply to the legacy SSE transport (`transport="sse"`).
|
||||
</Note>
|
||||
|
||||
When running tools that take a long time to complete, you may encounter issues with load balancers or proxies terminating connections that stay idle too long. [SEP-1699](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699) introduces SSE polling to solve this by allowing the server to gracefully close connections and have clients automatically reconnect.
|
||||
|
||||
To enable SSE polling, configure an `EventStore` when creating your HTTP application:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Context
|
||||
from fastmcp.server.event_store import EventStore
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
|
||||
@mcp.tool
|
||||
async def long_running_task(ctx: Context) -> str:
|
||||
"""A task that takes several minutes to complete."""
|
||||
for i in range(100):
|
||||
await ctx.report_progress(i, 100)
|
||||
|
||||
# Periodically close the connection to avoid load balancer timeouts
|
||||
# Client will automatically reconnect and resume receiving progress
|
||||
if i % 30 == 0 and i > 0:
|
||||
await ctx.close_sse_stream()
|
||||
|
||||
await do_expensive_work()
|
||||
|
||||
return "Done!"
|
||||
|
||||
# Configure with EventStore for resumability
|
||||
event_store = EventStore()
|
||||
app = mcp.http_app(
|
||||
event_store=event_store,
|
||||
retry_interval=2000, # Client reconnects after 2 seconds
|
||||
)
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. When `event_store` is configured, the server stores all events (progress updates, results) with unique IDs
|
||||
2. Calling `ctx.close_sse_stream()` gracefully closes the HTTP connection
|
||||
3. The client automatically reconnects with a `Last-Event-ID` header
|
||||
4. The server replays any events the client missed during the disconnection
|
||||
|
||||
The `retry_interval` parameter (in milliseconds) controls how long clients wait before reconnecting. Choose a value that balances responsiveness with server load.
|
||||
|
||||
<Note>
|
||||
`close_sse_stream()` is a no-op if called without an `EventStore` configured, so you can safely include it in tools that may run in different deployment configurations.
|
||||
</Note>
|
||||
|
||||
#### Custom Storage Backends
|
||||
|
||||
By default, `EventStore` uses in-memory storage. For production deployments with multiple server instances, you can provide a custom storage backend using the `key_value` package:
|
||||
|
||||
```python
|
||||
from fastmcp.server.event_store import EventStore
|
||||
from key_value.aio.stores.redis import RedisStore
|
||||
|
||||
# Use Redis for distributed deployments
|
||||
redis_store = RedisStore(url="redis://localhost:6379")
|
||||
event_store = EventStore(
|
||||
storage=redis_store,
|
||||
max_events_per_stream=100, # Keep last 100 events per stream
|
||||
ttl=3600, # Events expire after 1 hour
|
||||
)
|
||||
|
||||
app = mcp.http_app(event_store=event_store)
|
||||
```
|
||||
|
||||
## Integration with Web Frameworks
|
||||
|
||||
If you already have a web application running, you can add MCP capabilities by mounting a FastMCP server as a sub-application. This allows you to expose MCP tools alongside your existing API endpoints, sharing the same domain and infrastructure. The MCP server becomes just another route in your application, making it easy to manage and deploy.
|
||||
|
||||
### Mounting in Starlette
|
||||
|
||||
Mount your FastMCP server in a Starlette application:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount
|
||||
|
||||
# Create your FastMCP server
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def analyze(data: str) -> dict:
|
||||
return {"result": f"Analyzed: {data}"}
|
||||
|
||||
# Create the ASGI app
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
|
||||
# Create a Starlette app and mount the MCP server
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Mount("/mcp-server", app=mcp_app),
|
||||
# Add other routes as needed
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
```
|
||||
|
||||
The MCP endpoint will be available at `/mcp-server/mcp/` of the resulting Starlette app.
|
||||
|
||||
<Warning>
|
||||
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the resulting Starlette app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.
|
||||
</Warning>
|
||||
|
||||
#### Nested Mounts
|
||||
|
||||
You can create complex routing structures by nesting mounts:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount
|
||||
|
||||
# Create your FastMCP server
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
# Create the ASGI app
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
|
||||
# Create nested application structure
|
||||
inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)])
|
||||
app = Starlette(
|
||||
routes=[Mount("/outer", app=inner_app)],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
```
|
||||
|
||||
In this setup, the MCP server is accessible at the `/outer/inner/mcp/` path.
|
||||
|
||||
### FastAPI Integration
|
||||
|
||||
For FastAPI-specific integration patterns including both mounting MCP servers into FastAPI apps and generating MCP servers from FastAPI apps, see the [FastAPI Integration guide](/integrations/fastapi).
|
||||
|
||||
Here's a quick example showing how to add MCP to an existing FastAPI application:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Your existing API
|
||||
api = FastAPI()
|
||||
|
||||
@api.get("/api/status")
|
||||
def status():
|
||||
return {"status": "ok"}
|
||||
|
||||
# Create your MCP server
|
||||
mcp = FastMCP("API Tools")
|
||||
|
||||
@mcp.tool
|
||||
def query_database(query: str) -> dict:
|
||||
"""Run a database query"""
|
||||
return {"result": "data"}
|
||||
|
||||
# Mount MCP at /mcp
|
||||
api.mount("/mcp", mcp.http_app())
|
||||
|
||||
# Run with: uvicorn app:api --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Your existing API remains at `http://localhost:8000/api` while MCP is available at `http://localhost:8000/mcp`.
|
||||
|
||||
## Mounting Authenticated Servers
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
<Tip>
|
||||
This section only applies if you're **mounting an OAuth-protected FastMCP server under a path prefix** (like `/api`) inside another application using `Mount()`.
|
||||
|
||||
If you're deploying your FastMCP server at root level without any `Mount()` prefix, the well-known routes are automatically included in `mcp.http_app()` and you don't need to do anything special.
|
||||
</Tip>
|
||||
|
||||
OAuth specifications (RFC 8414 and RFC 9728) require discovery metadata to be accessible at well-known paths under the root level of your domain. When you mount an OAuth-protected FastMCP server under a path prefix like `/api`, this creates a routing challenge: your operational OAuth endpoints move under the prefix, but discovery endpoints must remain at the root.
|
||||
|
||||
<Warning>
|
||||
**Common Mistakes to Avoid:**
|
||||
|
||||
1. **Forgetting to mount `.well-known` routes at root** - FastMCP cannot do this automatically when your server is mounted under a path prefix. You must explicitly mount well-known routes at the root level.
|
||||
|
||||
2. **Including mount prefix in both base_url AND mcp_path** - The mount prefix (like `/api`) should only be in `base_url`, not in `mcp_path`. Otherwise you'll get double paths.
|
||||
|
||||
✅ **Correct:**
|
||||
```python
|
||||
base_url = "http://localhost:8000/api"
|
||||
mcp_path = "/mcp"
|
||||
# Result: /api/mcp
|
||||
```
|
||||
|
||||
❌ **Wrong:**
|
||||
```python
|
||||
base_url = "http://localhost:8000/api"
|
||||
mcp_path = "/api/mcp"
|
||||
# Result: /api/api/mcp (double prefix!)
|
||||
```
|
||||
|
||||
Follow the configuration instructions below to set up mounting correctly.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
**CORS Middleware Conflicts:**
|
||||
|
||||
If you're integrating FastMCP into an existing application with its own CORS middleware, be aware that layering CORS middleware can cause conflicts (such as 404 errors on `.well-known` routes or OPTIONS requests).
|
||||
|
||||
FastMCP and the MCP SDK already handle CORS for OAuth routes. If you need CORS on your own application routes, consider using the sub-app pattern: mount FastMCP and your routes as separate apps, each with their own middleware, rather than adding application-wide CORS middleware.
|
||||
</Warning>
|
||||
|
||||
### Route Types
|
||||
|
||||
OAuth-protected MCP servers expose two categories of routes:
|
||||
|
||||
**Operational routes** handle the OAuth flow and MCP protocol:
|
||||
- `/authorize` - OAuth authorization endpoint
|
||||
- `/token` - Token exchange endpoint
|
||||
- `/auth/callback` - OAuth callback handler
|
||||
- `/mcp` - MCP protocol endpoint
|
||||
|
||||
**Discovery routes** provide metadata for OAuth clients:
|
||||
- `/.well-known/oauth-authorization-server` - Authorization server metadata
|
||||
- `/.well-known/oauth-protected-resource/*` - Protected resource metadata
|
||||
|
||||
When you mount your MCP app under a prefix, operational routes move with it, but discovery routes must stay at root level for RFC compliance.
|
||||
|
||||
### Configuration Parameters
|
||||
|
||||
Three parameters control where routes are located and how they combine:
|
||||
|
||||
**`base_url`** tells clients where to find operational endpoints. This includes any Starlette `Mount()` path prefix (e.g., `/api`):
|
||||
|
||||
```python
|
||||
base_url="http://localhost:8000/api" # Includes mount prefix
|
||||
```
|
||||
|
||||
**`mcp_path`** is the internal FastMCP endpoint path, which gets appended to `base_url`:
|
||||
|
||||
```python
|
||||
mcp_path="/mcp" # Internal MCP path, NOT the mount prefix
|
||||
```
|
||||
|
||||
**`issuer_url`** (optional) controls the authorization server identity for OAuth discovery. Defaults to `base_url`.
|
||||
|
||||
```python
|
||||
# Usually not needed - just set base_url and it works
|
||||
issuer_url="http://localhost:8000" # Only if you want root-level discovery
|
||||
```
|
||||
|
||||
When `issuer_url` has a path (either explicitly or by defaulting from `base_url`), FastMCP creates path-aware discovery routes per RFC 8414. For example, if `base_url` is `http://localhost:8000/api`, the authorization server metadata will be at `/.well-known/oauth-authorization-server/api`.
|
||||
|
||||
**Key Invariant:** `base_url + mcp_path = actual externally-accessible MCP URL`
|
||||
|
||||
Example:
|
||||
- `base_url`: `http://localhost:8000/api` (mount prefix `/api`)
|
||||
- `mcp_path`: `/mcp` (internal path)
|
||||
- Result: `http://localhost:8000/api/mcp` (final MCP endpoint)
|
||||
|
||||
Note that the mount prefix (`/api` from `Mount("/api", ...)`) goes in `base_url`, while `mcp_path` is just the internal MCP route. Don't include the mount prefix in both places or you'll get `/api/api/mcp`.
|
||||
|
||||
### Mounting Strategy
|
||||
|
||||
When mounting an OAuth-protected server under a path prefix, declare your URLs upfront to make the relationships clear:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.github import GitHubProvider
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount
|
||||
|
||||
# Define the routing structure
|
||||
ROOT_URL = "http://localhost:8000"
|
||||
MOUNT_PREFIX = "/api"
|
||||
MCP_PATH = "/mcp"
|
||||
```
|
||||
|
||||
Create the auth provider with `base_url`:
|
||||
|
||||
```python
|
||||
auth = GitHubProvider(
|
||||
client_id="your-client-id",
|
||||
client_secret="your-client-secret",
|
||||
base_url=f"{ROOT_URL}{MOUNT_PREFIX}", # Operational endpoints under prefix
|
||||
# issuer_url defaults to base_url - path-aware discovery works automatically
|
||||
)
|
||||
```
|
||||
|
||||
Create the MCP app, which generates operational routes at the specified path:
|
||||
|
||||
```python
|
||||
mcp = FastMCP("Protected Server", auth=auth)
|
||||
mcp_app = mcp.http_app(path=MCP_PATH)
|
||||
```
|
||||
|
||||
Retrieve the discovery routes from the auth provider. The `mcp_path` argument should match the path used when creating the MCP app:
|
||||
|
||||
```python
|
||||
well_known_routes = auth.get_well_known_routes(mcp_path=MCP_PATH)
|
||||
```
|
||||
|
||||
Finally, mount everything in the Starlette app with discovery routes at root and the MCP app under the prefix:
|
||||
|
||||
```python
|
||||
app = Starlette(
|
||||
routes=[
|
||||
*well_known_routes, # Discovery routes at root level
|
||||
Mount(MOUNT_PREFIX, app=mcp_app), # Operational routes under prefix
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
```
|
||||
|
||||
This configuration produces the following URL structure:
|
||||
|
||||
- MCP endpoint: `http://localhost:8000/api/mcp`
|
||||
- OAuth authorization: `http://localhost:8000/api/authorize`
|
||||
- OAuth callback: `http://localhost:8000/api/auth/callback`
|
||||
- Authorization server metadata: `http://localhost:8000/.well-known/oauth-authorization-server/api`
|
||||
- Protected resource metadata: `http://localhost:8000/.well-known/oauth-protected-resource/api/mcp`
|
||||
|
||||
Both discovery endpoints use path-aware URLs per RFC 8414 and RFC 9728, matching the `base_url` path.
|
||||
|
||||
### Complete Example
|
||||
|
||||
Here's a complete working example showing all the pieces together:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.github import GitHubProvider
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount
|
||||
import uvicorn
|
||||
|
||||
# Define routing structure
|
||||
ROOT_URL = "http://localhost:8000"
|
||||
MOUNT_PREFIX = "/api"
|
||||
MCP_PATH = "/mcp"
|
||||
|
||||
# Create OAuth provider
|
||||
auth = GitHubProvider(
|
||||
client_id="your-client-id",
|
||||
client_secret="your-client-secret",
|
||||
base_url=f"{ROOT_URL}{MOUNT_PREFIX}",
|
||||
# issuer_url defaults to base_url - path-aware discovery works automatically
|
||||
)
|
||||
|
||||
# Create MCP server
|
||||
mcp = FastMCP("Protected Server", auth=auth)
|
||||
|
||||
@mcp.tool
|
||||
def analyze(data: str) -> dict:
|
||||
return {"result": f"Analyzed: {data}"}
|
||||
|
||||
# Create MCP app
|
||||
mcp_app = mcp.http_app(path=MCP_PATH)
|
||||
|
||||
# Get discovery routes for root level
|
||||
well_known_routes = auth.get_well_known_routes(mcp_path=MCP_PATH)
|
||||
|
||||
# Assemble the application
|
||||
app = Starlette(
|
||||
routes=[
|
||||
*well_known_routes,
|
||||
Mount(MOUNT_PREFIX, app=mcp_app),
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
For more details on OAuth authentication, see the [Authentication guide](/servers/auth).
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Running with Uvicorn
|
||||
|
||||
When deploying to production, you'll want to optimize your server for performance and reliability. Uvicorn provides several options to improve your server's capabilities:
|
||||
|
||||
```bash
|
||||
# Run with basic configuration
|
||||
uvicorn app:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# Run with multiple workers for production (requires stateless mode - see below)
|
||||
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
```
|
||||
|
||||
### Horizontal Scaling
|
||||
|
||||
<VersionBadge version="2.10.2" />
|
||||
|
||||
When deploying FastMCP behind a load balancer or running multiple server instances, you need to understand how the HTTP transport handles sessions and configure your server appropriately.
|
||||
|
||||
#### Understanding Sessions
|
||||
|
||||
By default, FastMCP's Streamable HTTP transport maintains server-side sessions. Sessions enable stateful MCP features like [elicitation](/servers/elicitation) and [sampling](/servers/sampling), where the server needs to maintain context across multiple requests from the same client.
|
||||
|
||||
This works perfectly for single-instance deployments. However, sessions are stored in memory on each server instance, which creates challenges when scaling horizontally.
|
||||
|
||||
#### Without Stateless Mode
|
||||
|
||||
When running multiple server instances behind a load balancer (Traefik, nginx, HAProxy, Kubernetes, etc.), requests from the same client may be routed to different instances:
|
||||
|
||||
1. Client connects to Instance A → session created on Instance A
|
||||
2. Next request routes to Instance B → session doesn't exist → **request fails**
|
||||
|
||||
You might expect sticky sessions (session affinity) to solve this, but they don't work reliably with MCP clients.
|
||||
|
||||
<Warning>
|
||||
**Why sticky sessions don't work:** Most MCP clients—including Cursor and Claude Code—use `fetch()` internally and don't properly forward `Set-Cookie` headers. Without cookies, load balancers can't identify which instance should handle subsequent requests. This is a limitation in how these clients implement HTTP, not something you can fix with load balancer configuration.
|
||||
</Warning>
|
||||
|
||||
#### Enabling Stateless Mode
|
||||
|
||||
For horizontally scaled deployments, enable stateless HTTP mode. In stateless mode, each request creates a fresh transport context, eliminating the need for session affinity entirely.
|
||||
|
||||
**Option 1: Via constructor**
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My Server", stateless_http=True)
|
||||
|
||||
@mcp.tool
|
||||
def process(data: str) -> str:
|
||||
return f"Processed: {data}"
|
||||
|
||||
app = mcp.http_app()
|
||||
```
|
||||
|
||||
**Option 2: Via `run()`**
|
||||
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", stateless_http=True)
|
||||
```
|
||||
|
||||
**Option 3: Via environment variable**
|
||||
|
||||
```bash
|
||||
FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations.
|
||||
|
||||
Here's an example using bearer token authentication (though OAuth is recommended for production):
|
||||
|
||||
```python
|
||||
import os
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth import BearerTokenAuth
|
||||
|
||||
# Read configuration from environment
|
||||
auth_token = os.environ.get("MCP_AUTH_TOKEN")
|
||||
if auth_token:
|
||||
auth = BearerTokenAuth(token=auth_token)
|
||||
mcp = FastMCP("Production Server", auth=auth)
|
||||
else:
|
||||
mcp = FastMCP("Production Server")
|
||||
|
||||
app = mcp.http_app()
|
||||
```
|
||||
|
||||
Deploy with your secrets safely stored in environment variables:
|
||||
```bash
|
||||
MCP_AUTH_TOKEN=secret uvicorn app:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### OAuth Token Security
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
If you're using the [OAuth Proxy](/servers/auth/oauth-proxy), FastMCP issues its own JWT tokens to clients instead of forwarding upstream provider tokens. This maintains proper OAuth 2.0 token boundaries.
|
||||
|
||||
**Default Behavior (Development Only):**
|
||||
|
||||
By default, FastMCP automatically manages cryptographic keys:
|
||||
- **Mac/Windows**: Keys are generated and stored in your system keyring, surviving server restarts. Suitable **only** for development and local testing.
|
||||
- **Linux**: Keys are ephemeral (random salt at startup), so tokens are invalidated on restart.
|
||||
|
||||
This automatic approach is convenient for development but not suitable for production deployments.
|
||||
|
||||
**For Production:**
|
||||
|
||||
Production requires explicit key management to ensure tokens survive restarts and can be shared across multiple server instances. This requires the following two things working together:
|
||||
|
||||
1. **Explicit JWT signing key** for signing tokens issued to clients
|
||||
3. **Persistent network-accessible storage** for upstream tokens (wrapped in `FernetEncryptionWrapper` to encrypt sensitive data at rest)
|
||||
|
||||
**Configuration:**
|
||||
|
||||
Add two parameters to your auth provider:
|
||||
|
||||
```python {8-12}
|
||||
from key_value.aio.stores.redis import RedisStore
|
||||
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
auth = GitHubProvider(
|
||||
client_id=os.environ["GITHUB_CLIENT_ID"],
|
||||
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
client_storage=FernetEncryptionWrapper(
|
||||
key_value=RedisStore(host="redis.example.com", port=6379),
|
||||
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
|
||||
),
|
||||
base_url="https://your-server.com" # use HTTPS
|
||||
)
|
||||
```
|
||||
|
||||
Both parameters are required for production. Without an explicit signing key, keys are signed using a key derived from the client_secret, which will cause invalidation upon rotation of the client secret. Without persistent storage, tokens are local to the server and won't be trusted across hosts. **Wrap your storage backend in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without encryption, tokens are stored in plaintext.
|
||||
|
||||
For more details on the token architecture and key management, see [OAuth Proxy Key and Storage Management](/servers/auth/oauth-proxy#key-and-storage-management).
|
||||
|
||||
## Testing Your Deployment
|
||||
|
||||
Once your server is deployed, you'll need to verify it's accessible and functioning correctly. For comprehensive testing strategies including connectivity tests, client testing, and authentication testing, see the [Testing Your Server](/development/tests) guide.
|
||||
|
||||
## Hosting Your Server
|
||||
|
||||
This guide has shown you how to create an HTTP-accessible MCP server, but you'll still need a hosting provider to make it available on the internet. Your FastMCP server can run anywhere that supports Python web applications:
|
||||
|
||||
- **Cloud VMs** (AWS EC2, Google Compute Engine, Azure VMs)
|
||||
- **Container platforms** (Cloud Run, Container Instances, ECS)
|
||||
- **Platform-as-a-Service** (Railway, Render, Vercel)
|
||||
- **Edge platforms** (Cloudflare Workers)
|
||||
- **Kubernetes clusters** (self-managed or managed)
|
||||
|
||||
The key requirements are Python 3.10+ support and the ability to expose an HTTP port. Most providers will require you to package your server (requirements.txt, Dockerfile, etc.) according to their deployment format. For managed, zero-configuration deployment, see [FastMCP Cloud](/deployment/fastmcp-cloud).
|
||||
258
docs/v2/deployment/running-server.mdx
Normal file
258
docs/v2/deployment/running-server.mdx
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
---
|
||||
title: Running Your Server
|
||||
sidebarTitle: Running Your Server
|
||||
description: Learn how to run your FastMCP server locally for development and testing
|
||||
icon: circle-play
|
||||
---
|
||||
|
||||
FastMCP servers can be run in different ways depending on your needs. This guide focuses on running servers locally for development and testing. For production deployment to a URL, see the [HTTP Deployment](/deployment/http) guide.
|
||||
|
||||
## The `run()` Method
|
||||
|
||||
Every FastMCP server needs to be started to accept connections. The simplest way to run a server is by calling the `run()` method on your FastMCP instance. This method starts the server and blocks until it's stopped, handling all the connection management for you.
|
||||
|
||||
<Tip>
|
||||
For maximum compatibility, it's best practice to place the `run()` call within an `if __name__ == "__main__":` block. This ensures the server starts only when the script is executed directly, not when imported as a module.
|
||||
</Tip>
|
||||
|
||||
```python {9-10} my_server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def hello(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
You can now run this MCP server by executing `python my_server.py`.
|
||||
|
||||
## Transport Protocols
|
||||
|
||||
MCP servers communicate with clients through different transport protocols. Think of transports as the "language" your server speaks to communicate with clients. FastMCP supports three main transport protocols, each designed for specific use cases and deployment scenarios.
|
||||
|
||||
The choice of transport determines how clients connect to your server, what network capabilities are available, and how many clients can connect simultaneously. Understanding these transports helps you choose the right approach for your application.
|
||||
|
||||
### STDIO Transport (Default)
|
||||
|
||||
STDIO (Standard Input/Output) is the default transport for FastMCP servers. When you call `run()` without arguments, your server uses STDIO transport. This transport communicates through standard input and output streams, making it perfect for command-line tools and desktop applications like Claude Desktop.
|
||||
|
||||
With STDIO transport, the client spawns a new server process for each session and manages its lifecycle. The server reads MCP messages from stdin and writes responses to stdout. This is why STDIO servers don't stay running - they're started on-demand by the client.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def hello(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run() # Uses STDIO transport by default
|
||||
```
|
||||
|
||||
STDIO is ideal for:
|
||||
- Local development and testing
|
||||
- Claude Desktop integration
|
||||
- Command-line tools
|
||||
- Single-user applications
|
||||
|
||||
### HTTP Transport (Streamable)
|
||||
|
||||
HTTP transport turns your MCP server into a web service accessible via a URL. This transport uses the Streamable HTTP protocol, which allows clients to connect over the network. Unlike STDIO where each client gets its own process, an HTTP server can handle multiple clients simultaneously.
|
||||
|
||||
The Streamable HTTP protocol provides full bidirectional communication between client and server, supporting all MCP operations including streaming responses. This makes it the recommended choice for network-based deployments.
|
||||
|
||||
To use HTTP transport, specify it in the `run()` method along with networking options:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def hello(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Start an HTTP server on port 8000
|
||||
mcp.run(transport="http", host="127.0.0.1", port=8000)
|
||||
```
|
||||
|
||||
Your server is now accessible at `http://localhost:8000/mcp`. This URL is the MCP endpoint that clients will connect to. HTTP transport enables:
|
||||
- Network accessibility
|
||||
- Multiple concurrent clients
|
||||
- Integration with web infrastructure
|
||||
- Remote deployment capabilities
|
||||
|
||||
For production HTTP deployment with authentication and advanced configuration, see the [HTTP Deployment](/deployment/http) guide.
|
||||
|
||||
### SSE Transport (Legacy)
|
||||
|
||||
Server-Sent Events (SSE) transport was the original HTTP-based transport for MCP. While still supported for backward compatibility, it has limitations compared to the newer Streamable HTTP transport. SSE only supports server-to-client streaming, making it less efficient for bidirectional communication.
|
||||
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
# SSE transport - use HTTP instead for new projects
|
||||
mcp.run(transport="sse", host="127.0.0.1", port=8000)
|
||||
```
|
||||
|
||||
We recommend using HTTP transport instead of SSE for all new projects. SSE remains available only for compatibility with older clients that haven't upgraded to Streamable HTTP.
|
||||
|
||||
### Choosing the Right Transport
|
||||
|
||||
Each transport serves different needs. STDIO is perfect when you need simple, local execution - it's what Claude Desktop and most command-line tools expect. HTTP transport is essential when you need network access, want to serve multiple clients, or plan to deploy your server remotely. SSE exists only for backward compatibility and shouldn't be used in new projects.
|
||||
|
||||
Consider your deployment scenario: Are you building a tool for local use? STDIO is your best choice. Need a centralized service that multiple clients can access? HTTP transport is the way to go.
|
||||
|
||||
## The FastMCP CLI
|
||||
|
||||
FastMCP provides a powerful command-line interface for running servers without modifying the source code. The CLI can automatically find and run your server with different transports, manage dependencies, and handle development workflows:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py
|
||||
```
|
||||
|
||||
The CLI automatically finds a FastMCP instance in your file (named `mcp`, `server`, or `app`) and runs it with the specified options. This is particularly useful for testing different transports or configurations without changing your code.
|
||||
|
||||
### Dependency Management
|
||||
|
||||
The CLI integrates with `uv` to manage Python environments and dependencies:
|
||||
|
||||
```bash
|
||||
# Run with a specific Python version
|
||||
fastmcp run server.py --python 3.11
|
||||
|
||||
# Run with additional packages
|
||||
fastmcp run server.py --with pandas --with numpy
|
||||
|
||||
# Run with dependencies from a requirements file
|
||||
fastmcp run server.py --with-requirements requirements.txt
|
||||
|
||||
# Combine multiple options
|
||||
fastmcp run server.py --python 3.10 --with httpx --transport http
|
||||
|
||||
# Run within a specific project directory
|
||||
fastmcp run server.py --project /path/to/project
|
||||
```
|
||||
|
||||
<Note>
|
||||
When using `--python`, `--with`, `--project`, or `--with-requirements`, the server runs via `uv run` subprocess instead of using your local environment.
|
||||
</Note>
|
||||
|
||||
### Passing Arguments to Servers
|
||||
|
||||
When servers accept command line arguments (using argparse, click, or other libraries), you can pass them after `--`:
|
||||
|
||||
```bash
|
||||
fastmcp run config_server.py -- --config config.json
|
||||
fastmcp run database_server.py -- --database-path /tmp/db.sqlite --debug
|
||||
```
|
||||
|
||||
This is useful for servers that need configuration files, database paths, API keys, or other runtime options.
|
||||
|
||||
For more CLI features including development mode with the MCP Inspector, see the [CLI documentation](/patterns/cli).
|
||||
|
||||
### Async Usage
|
||||
|
||||
FastMCP servers are built on async Python, but the framework provides both synchronous and asynchronous APIs to fit your application's needs. The `run()` method we've been using is actually a synchronous wrapper around the async server implementation.
|
||||
|
||||
For applications that are already running in an async context, FastMCP provides the `run_async()` method:
|
||||
|
||||
```python {10-12}
|
||||
from fastmcp import FastMCP
|
||||
import asyncio
|
||||
|
||||
mcp = FastMCP(name="MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def hello(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
async def main():
|
||||
# Use run_async() in async contexts
|
||||
await mcp.run_async(transport="http", port=8000)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
<Warning>
|
||||
The `run()` method cannot be called from inside an async function because it creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running.
|
||||
|
||||
Always use `run_async()` inside async functions and `run()` in synchronous contexts.
|
||||
</Warning>
|
||||
|
||||
Both `run()` and `run_async()` accept the same transport arguments, so all the examples above apply to both methods.
|
||||
|
||||
## Custom Routes
|
||||
|
||||
When using HTTP transport, you might want to add custom web endpoints alongside your MCP server. This is useful for health checks, status pages, or simple APIs. FastMCP lets you add custom routes using the `@custom_route` decorator:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import PlainTextResponse
|
||||
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
@mcp.custom_route("/health", methods=["GET"])
|
||||
async def health_check(request: Request) -> PlainTextResponse:
|
||||
return PlainTextResponse("OK")
|
||||
|
||||
@mcp.tool
|
||||
def process(data: str) -> str:
|
||||
return f"Processed: {data}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http") # Health check at http://localhost:8000/health
|
||||
```
|
||||
|
||||
Custom routes are served by the same web server as your MCP endpoint. They're available at the root of your domain while the MCP endpoint is at `/mcp/`. For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/http#integration-with-web-frameworks).
|
||||
|
||||
## Alternative Initialization Patterns
|
||||
|
||||
The `if __name__ == "__main__"` pattern works well for standalone scripts, but some deployment scenarios require different approaches. FastMCP handles these cases automatically.
|
||||
|
||||
### CLI-Only Servers
|
||||
|
||||
When using the FastMCP CLI, you don't need the `if __name__` block at all. The CLI will find your FastMCP instance and run it:
|
||||
|
||||
```python
|
||||
# server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("MyServer") # CLI looks for 'mcp', 'server', or 'app'
|
||||
|
||||
@mcp.tool
|
||||
def process(data: str) -> str:
|
||||
return f"Processed: {data}"
|
||||
|
||||
# No if __name__ block needed - CLI will find and run 'mcp'
|
||||
```
|
||||
|
||||
### ASGI Applications
|
||||
|
||||
For ASGI deployment (running with Uvicorn or similar), you'll want to create an ASGI application object. This approach is common in production deployments where you need more control over the server configuration:
|
||||
|
||||
```python
|
||||
# app.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
def create_app():
|
||||
mcp = FastMCP("MyServer")
|
||||
|
||||
@mcp.tool
|
||||
def process(data: str) -> str:
|
||||
return f"Processed: {data}"
|
||||
|
||||
return mcp.http_app()
|
||||
|
||||
app = create_app() # Uvicorn will use this
|
||||
```
|
||||
|
||||
See the [HTTP Deployment](/deployment/http) guide for more ASGI deployment patterns.
|
||||
640
docs/v2/deployment/server-configuration.mdx
Normal file
640
docs/v2/deployment/server-configuration.mdx
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
---
|
||||
title: "Project Configuration"
|
||||
sidebarTitle: "Project Configuration"
|
||||
description: Use fastmcp.json for portable, declarative project configuration
|
||||
icon: file-code
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.12.0" />
|
||||
|
||||
FastMCP supports declarative configuration through `fastmcp.json` files. This is the canonical and preferred way to configure FastMCP projects, providing a single source of truth for server settings, dependencies, and deployment options that replaces complex command-line arguments.
|
||||
|
||||
The `fastmcp.json` file is designed to be a portable description of your server configuration that can be shared across environments and teams. When running from a `fastmcp.json` file, you can override any configuration values using CLI arguments.
|
||||
|
||||
## Overview
|
||||
|
||||
The `fastmcp.json` configuration file allows you to define all aspects of your FastMCP server in a structured, shareable format. Instead of remembering command-line arguments or writing shell scripts, you declare your server's configuration once and use it everywhere.
|
||||
|
||||
When you have a `fastmcp.json` file, running your server becomes as simple as:
|
||||
|
||||
```bash
|
||||
# Run the server using the configuration
|
||||
fastmcp run fastmcp.json
|
||||
|
||||
# Or if fastmcp.json exists in the current directory
|
||||
fastmcp run
|
||||
```
|
||||
|
||||
This configuration approach ensures reproducible deployments across different environments, from local development to production servers. It works seamlessly with Claude Desktop, VS Code extensions, and any MCP-compatible client.
|
||||
|
||||
## File Structure
|
||||
|
||||
The `fastmcp.json` configuration answers three fundamental questions about your server:
|
||||
|
||||
- **Source** = WHERE does your server code live?
|
||||
- **Environment** = WHAT environment setup does it require?
|
||||
- **Deployment** = HOW should the server run?
|
||||
|
||||
This conceptual model helps you understand the purpose of each configuration section and organize your settings effectively. The configuration file maps directly to these three concerns:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
// WHERE: Location of your server code
|
||||
"type": "filesystem", // Optional, defaults to "filesystem"
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
// WHAT: Environment setup and dependencies
|
||||
"type": "uv", // Optional, defaults to "uv"
|
||||
"python": ">=3.10",
|
||||
"dependencies": ["pandas", "numpy"]
|
||||
},
|
||||
"deployment": {
|
||||
// HOW: Runtime configuration
|
||||
"transport": "stdio",
|
||||
"log_level": "INFO"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Only the `source` field is required. The `environment` and `deployment` sections are optional and provide additional configuration when needed.
|
||||
|
||||
### JSON Schema Support
|
||||
|
||||
FastMCP provides JSON schemas for IDE autocomplete and validation. Add the schema reference to your `fastmcp.json` for enhanced developer experience:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Two schema URLs are available:
|
||||
- **Version-specific**: `https://gofastmcp.com/public/schemas/fastmcp.json/v1.json`
|
||||
- **Latest version**: `https://gofastmcp.com/public/schemas/fastmcp.json/latest.json`
|
||||
|
||||
Modern IDEs like VS Code will automatically provide autocomplete suggestions, validation, and inline documentation when the schema is specified.
|
||||
|
||||
### Source Configuration
|
||||
|
||||
The source configuration determines **WHERE** your server code lives. It tells FastMCP how to find and load your server, whether it's a local Python file, a remote repository, or hosted in the cloud. This section is required and forms the foundation of your configuration.
|
||||
|
||||
<Card icon="code" title="Source">
|
||||
<ParamField body="source" type="object" required>
|
||||
The server source configuration that determines where your server code lives.
|
||||
|
||||
<ParamField body="type" type="string" default="filesystem">
|
||||
The source type identifier that determines which implementation to use. Currently supports `"filesystem"` for local files. Future releases will add support for `"git"` and `"cloud"` source types.
|
||||
</ParamField>
|
||||
|
||||
<Expandable title="FileSystemSource">
|
||||
When `type` is `"filesystem"` (or omitted), the source points to a local Python file containing your FastMCP server:
|
||||
|
||||
<ParamField body="path" type="string" required>
|
||||
Path to the Python file containing your FastMCP server.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="entrypoint" type="string">
|
||||
Name of the server instance or factory function within the module:
|
||||
- Can be a FastMCP server instance (e.g., `mcp = FastMCP("MyServer")`)
|
||||
- Can be a function with no arguments that returns a FastMCP server
|
||||
- If not specified, FastMCP searches for common names: `mcp`, `server`, or `app`
|
||||
</ParamField>
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"source": {
|
||||
"type": "filesystem",
|
||||
"path": "src/server.py",
|
||||
"entrypoint": "mcp"
|
||||
}
|
||||
```
|
||||
|
||||
Note: File paths are resolved relative to the configuration file's location.
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
<Note>
|
||||
**Future Source Types**
|
||||
|
||||
Future releases will support additional source types:
|
||||
- **Git repositories** (`type: "git"`) for loading server code directly from version control
|
||||
- **FastMCP Cloud** (`type: "cloud"`) for hosted servers with automatic scaling and management
|
||||
</Note>
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
The environment configuration determines **WHAT** environment setup your server requires. It controls the build-time setup of your Python environment, ensuring your server runs with the exact Python version and dependencies it requires. This section creates isolated, reproducible environments across different systems.
|
||||
|
||||
FastMCP uses an extensible environment system with a base `Environment` class that can be implemented by different environment providers. Currently, FastMCP supports the `UVEnvironment` for Python environment management using `uv`'s powerful dependency resolver.
|
||||
|
||||
<Card icon="code" title="Environment">
|
||||
<ParamField body="environment" type="object">
|
||||
Optional environment configuration. When specified, FastMCP uses the appropriate environment implementation to set up your server's runtime.
|
||||
|
||||
<ParamField body="type" type="string" default="uv">
|
||||
The environment type identifier that determines which implementation to use. Currently supports `"uv"` for Python environments managed by uv. If omitted, defaults to `"uv"`.
|
||||
</ParamField>
|
||||
|
||||
<Expandable title="UVEnvironment">
|
||||
When `type` is `"uv"` (or omitted), the environment uses uv to manage Python dependencies:
|
||||
|
||||
<ParamField body="python" type="string">
|
||||
Python version constraint. Examples:
|
||||
- Exact version: `"3.12"`
|
||||
- Minimum version: `">=3.10"`
|
||||
- Version range: `">=3.10,<3.13"`
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="dependencies" type="list[str]">
|
||||
List of pip packages with optional version specifiers (PEP 508 format).
|
||||
```json
|
||||
"dependencies": ["pandas>=2.0", "requests", "httpx"]
|
||||
```
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="requirements" type="string">
|
||||
Path to a requirements.txt file, resolved relative to the config file location.
|
||||
```json
|
||||
"requirements": "requirements.txt"
|
||||
```
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="project" type="string">
|
||||
Path to a project directory containing pyproject.toml for uv project management.
|
||||
```json
|
||||
"project": "."
|
||||
```
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="editable" type="list[string]">
|
||||
List of paths to packages to install in editable/development mode. Useful for local development when you want changes to be reflected immediately. Supports multiple packages for monorepo setups or shared libraries.
|
||||
```json
|
||||
"editable": ["."]
|
||||
```
|
||||
Or with multiple packages:
|
||||
```json
|
||||
"editable": [".", "../shared-lib", "/path/to/another-package"]
|
||||
```
|
||||
</ParamField>
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"environment": {
|
||||
"type": "uv",
|
||||
"python": ">=3.10",
|
||||
"dependencies": ["pandas", "numpy"],
|
||||
"editable": ["."]
|
||||
}
|
||||
```
|
||||
|
||||
Note: When any UVEnvironment field is specified, FastMCP automatically creates an isolated environment using `uv` before running your server.
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
When environment configuration is provided, FastMCP:
|
||||
1. Detects the environment type (defaults to `"uv"` if not specified)
|
||||
2. Creates an isolated environment using the appropriate provider
|
||||
3. Installs the specified dependencies
|
||||
4. Runs your server in this clean environment
|
||||
|
||||
This build-time setup ensures your server always has the dependencies it needs, without polluting your system Python or conflicting with other projects.
|
||||
|
||||
<Note>
|
||||
**Future Environment Types**
|
||||
|
||||
Similar to source types, future releases may support additional environment types for different runtime requirements, such as Docker containers or language-specific environments beyond Python.
|
||||
</Note>
|
||||
|
||||
### Deployment Configuration
|
||||
|
||||
The deployment configuration controls **HOW** your server runs. It defines the runtime behavior including network settings, environment variables, and execution context. These settings determine how your server operates when it executes, from transport protocols to logging levels.
|
||||
|
||||
Environment variables are included in this section because they're runtime configuration that affects how your server behaves when it executes, not how its environment is built. The deployment configuration is applied every time your server starts, controlling its operational characteristics.
|
||||
|
||||
<Card icon="code" title="Deployment Fields">
|
||||
<ParamField body="deployment" type="object">
|
||||
Optional runtime configuration for the server.
|
||||
|
||||
<Expandable title="Deployment Fields">
|
||||
<ParamField body="transport" type="string" default="stdio">
|
||||
Protocol for client communication:
|
||||
- `"stdio"`: Standard input/output for desktop clients
|
||||
- `"http"`: Network-accessible HTTP server
|
||||
- `"sse"`: Server-sent events
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="host" type="string" default="127.0.0.1">
|
||||
Network interface to bind (HTTP transport only):
|
||||
- `"127.0.0.1"`: Local connections only
|
||||
- `"0.0.0.0"`: All network interfaces
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="port" type="integer" default="3000">
|
||||
Port number for HTTP transport.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="path" type="string" default="/mcp/">
|
||||
URL path for the MCP endpoint when using HTTP transport.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="log_level" type="string" default="INFO">
|
||||
Server logging verbosity. Options:
|
||||
- `"DEBUG"`: Detailed debugging information
|
||||
- `"INFO"`: General informational messages
|
||||
- `"WARNING"`: Warning messages
|
||||
- `"ERROR"`: Error messages only
|
||||
- `"CRITICAL"`: Critical errors only
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="env" type="object">
|
||||
Environment variables to set when running the server. Supports `${VAR_NAME}` syntax for runtime interpolation.
|
||||
```json
|
||||
"env": {
|
||||
"API_KEY": "secret-key",
|
||||
"DATABASE_URL": "postgres://${DB_USER}@${DB_HOST}/mydb"
|
||||
}
|
||||
```
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="cwd" type="string">
|
||||
Working directory for the server process. Relative paths are resolved from the config file location.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="args" type="list[str]">
|
||||
Command-line arguments to pass to the server, passed after `--` to the server's argument parser.
|
||||
```json
|
||||
"args": ["--config", "server-config.json"]
|
||||
```
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
#### Environment Variable Interpolation
|
||||
|
||||
The `env` field in deployment configuration supports runtime interpolation of environment variables using `${VAR_NAME}` syntax. This enables dynamic configuration based on your deployment environment:
|
||||
|
||||
```json
|
||||
{
|
||||
"deployment": {
|
||||
"env": {
|
||||
"API_URL": "https://api.${ENVIRONMENT}.example.com",
|
||||
"DATABASE_URL": "postgres://${DB_USER}:${DB_PASS}@${DB_HOST}/myapp",
|
||||
"CACHE_KEY": "myapp_${ENVIRONMENT}_${VERSION}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the server starts, FastMCP replaces `${ENVIRONMENT}`, `${DB_USER}`, etc. with values from your system's environment variables. If a variable doesn't exist, the placeholder is preserved as-is.
|
||||
|
||||
**Example**: If your system has `ENVIRONMENT=production` and `DB_HOST=db.example.com`:
|
||||
```json
|
||||
// Configuration
|
||||
{
|
||||
"deployment": {
|
||||
"env": {
|
||||
"API_URL": "https://api.${ENVIRONMENT}.example.com",
|
||||
"DB_HOST": "${DB_HOST}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Result at runtime
|
||||
{
|
||||
"API_URL": "https://api.production.example.com",
|
||||
"DB_HOST": "db.example.com"
|
||||
}
|
||||
```
|
||||
|
||||
This feature is particularly useful for:
|
||||
- Deploying the same configuration across development, staging, and production
|
||||
- Keeping sensitive values out of configuration files
|
||||
- Building dynamic URLs and connection strings
|
||||
- Creating environment-specific prefixes or suffixes
|
||||
|
||||
## Usage with CLI Commands
|
||||
|
||||
FastMCP automatically detects and uses a file specifically named `fastmcp.json` in the current directory, making server execution simple and consistent. Files with FastMCP configuration format but different names are not auto-detected and must be specified explicitly:
|
||||
|
||||
```bash
|
||||
# Auto-detect fastmcp.json in current directory
|
||||
cd my-project
|
||||
fastmcp run # No arguments needed!
|
||||
|
||||
# Or specify a configuration file explicitly
|
||||
fastmcp run prod.fastmcp.json
|
||||
|
||||
# Skip environment setup when already in a uv environment
|
||||
fastmcp run fastmcp.json --skip-env
|
||||
|
||||
# Skip source preparation when source is already prepared
|
||||
fastmcp run fastmcp.json --skip-source
|
||||
|
||||
# Skip both environment and source preparation
|
||||
fastmcp run fastmcp.json --skip-env --skip-source
|
||||
```
|
||||
|
||||
### Pre-building Environments
|
||||
|
||||
You can use `fastmcp project prepare` to create a persistent uv project with all dependencies pre-installed:
|
||||
|
||||
```bash
|
||||
# Create a persistent environment
|
||||
fastmcp project prepare fastmcp.json --output-dir ./env
|
||||
|
||||
# Use the pre-built environment to run the server
|
||||
fastmcp run fastmcp.json --project ./env
|
||||
```
|
||||
|
||||
This pattern separates environment setup (slow) from server execution (fast), useful for deployment scenarios.
|
||||
|
||||
### Using an Existing Environment
|
||||
|
||||
By default, FastMCP creates an isolated environment with `uv` based on your configuration. When you already have a suitable Python environment, use the `--skip-env` flag to skip environment creation:
|
||||
|
||||
```bash
|
||||
fastmcp run fastmcp.json --skip-env
|
||||
```
|
||||
|
||||
**When you already have an environment:**
|
||||
- You're in an activated virtual environment with all dependencies installed
|
||||
- You're inside a Docker container with pre-installed dependencies
|
||||
- You're in a CI/CD pipeline that pre-builds the environment
|
||||
- You're using a system-wide installation with all required packages
|
||||
- You're in a uv-managed environment (prevents infinite recursion)
|
||||
|
||||
This flag tells FastMCP: "I already have everything installed, just run the server."
|
||||
|
||||
### Using an Existing Source
|
||||
|
||||
When working with source types that require preparation (future support for git repositories or cloud sources), use the `--skip-source` flag when you already have the source code available:
|
||||
|
||||
```bash
|
||||
fastmcp run fastmcp.json --skip-source
|
||||
```
|
||||
|
||||
**When you already have the source:**
|
||||
- You've previously cloned a git repository and don't need to re-fetch
|
||||
- You have a cached copy of a cloud-hosted server
|
||||
- You're in a CI/CD pipeline where source checkout is a separate step
|
||||
- You're iterating locally on already-downloaded code
|
||||
|
||||
This flag tells FastMCP: "I already have the source code, skip any download/clone steps."
|
||||
|
||||
Note: For filesystem sources (local Python files), this flag has no effect since they don't require preparation.
|
||||
|
||||
The configuration file works with all FastMCP commands:
|
||||
- **`run`** - Start the server in production mode
|
||||
- **`dev`** - Launch with the Inspector UI for development
|
||||
- **`inspect`** - View server capabilities and configuration
|
||||
- **`install`** - Install to Claude Desktop, Cursor, or other MCP clients
|
||||
|
||||
When no file argument is provided, FastMCP searches the current directory for `fastmcp.json`. This means you can simply navigate to your project directory and run `fastmcp run` to start your server with all its configured settings.
|
||||
|
||||
### CLI Override Behavior
|
||||
|
||||
Command-line arguments take precedence over configuration file values, allowing ad-hoc adjustments without modifying the file:
|
||||
|
||||
```bash
|
||||
# Config specifies port 3000, CLI overrides to 8080
|
||||
fastmcp run fastmcp.json --port 8080
|
||||
|
||||
# Config specifies stdio, CLI overrides to HTTP
|
||||
fastmcp run fastmcp.json --transport http
|
||||
|
||||
# Add extra dependencies not in config
|
||||
fastmcp run fastmcp.json --with requests --with httpx
|
||||
```
|
||||
|
||||
This precedence order enables:
|
||||
- Quick testing of different settings
|
||||
- Environment-specific overrides in deployment scripts
|
||||
- Debugging with increased log levels
|
||||
- Temporary configuration changes
|
||||
|
||||
### Custom Naming Patterns
|
||||
|
||||
You can use different configuration files for different environments:
|
||||
|
||||
- `fastmcp.json` - Default configuration
|
||||
- `dev.fastmcp.json` - Development settings
|
||||
- `prod.fastmcp.json` - Production settings
|
||||
- `test_fastmcp.json` - Test configuration
|
||||
|
||||
Any file with "fastmcp.json" in the name is recognized as a configuration file.
|
||||
|
||||
## Examples
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Basic Configuration">
|
||||
|
||||
A minimal configuration for a simple server:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
This configuration explicitly specifies the server entrypoint (`mcp`), making it clear which server instance or factory function to use. Uses all defaults: STDIO transport, no special dependencies, standard logging.
|
||||
</Tab>
|
||||
<Tab title="Development Configuration">
|
||||
|
||||
A configuration optimized for local development:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
// WHERE does the server live?
|
||||
"source": {
|
||||
"path": "src/server.py",
|
||||
"entrypoint": "app"
|
||||
},
|
||||
// WHAT dependencies does it need?
|
||||
"environment": {
|
||||
"type": "uv",
|
||||
"python": "3.12",
|
||||
"dependencies": ["fastmcp[dev]"],
|
||||
"editable": "."
|
||||
},
|
||||
// HOW should it run?
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "127.0.0.1",
|
||||
"port": 8000,
|
||||
"log_level": "DEBUG",
|
||||
"env": {
|
||||
"DEBUG": "true",
|
||||
"ENV": "development"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Production Configuration">
|
||||
|
||||
A production-ready configuration with full dependency management:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
// WHERE does the server live?
|
||||
"source": {
|
||||
"path": "app/main.py",
|
||||
"entrypoint": "mcp_server"
|
||||
},
|
||||
// WHAT dependencies does it need?
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"requirements": "requirements/production.txt",
|
||||
"project": "."
|
||||
},
|
||||
// HOW should it run?
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
"port": 3000,
|
||||
"path": "/api/mcp/",
|
||||
"log_level": "INFO",
|
||||
"env": {
|
||||
"ENV": "production",
|
||||
"API_BASE_URL": "https://api.example.com",
|
||||
"DATABASE_URL": "postgresql://user:pass@db.example.com/prod"
|
||||
},
|
||||
"cwd": "/app",
|
||||
"args": ["--workers", "4"]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Data Science Server">
|
||||
|
||||
Configuration for a data analysis server with scientific packages:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "analysis_server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"dependencies": [
|
||||
"pandas>=2.0",
|
||||
"numpy",
|
||||
"scikit-learn",
|
||||
"matplotlib",
|
||||
"jupyterlab"
|
||||
]
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "stdio",
|
||||
"env": {
|
||||
"MATPLOTLIB_BACKEND": "Agg",
|
||||
"DATA_PATH": "./datasets"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Multi-Environment Setup">
|
||||
|
||||
You can maintain multiple configuration files for different environments:
|
||||
|
||||
**dev.fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"log_level": "DEBUG"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**prod.fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"requirements": "requirements/production.txt"
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
"log_level": "WARNING"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run different configurations:
|
||||
```bash
|
||||
fastmcp run dev.fastmcp.json # Development
|
||||
fastmcp run prod.fastmcp.json # Production
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Migrating from CLI Arguments
|
||||
|
||||
If you're currently using command-line arguments or shell scripts, migrating to `fastmcp.json` simplifies your workflow. Here's how common CLI patterns map to configuration:
|
||||
|
||||
**CLI Command**:
|
||||
```bash
|
||||
uv run --with pandas --with requests \
|
||||
fastmcp run server.py \
|
||||
--transport http \
|
||||
--port 8000 \
|
||||
--log-level INFO
|
||||
```
|
||||
|
||||
**Equivalent fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"port": 8000,
|
||||
"log_level": "INFO"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now simply run:
|
||||
```bash
|
||||
fastmcp run # Automatically finds and uses fastmcp.json
|
||||
```
|
||||
|
||||
The configuration file approach provides better documentation, easier sharing, and consistent execution across different environments while maintaining the flexibility to override settings when needed.
|
||||
187
docs/v2/development/contributing.mdx
Normal file
187
docs/v2/development/contributing.mdx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
title: "Contributing"
|
||||
description: "Development workflow for FastMCP contributors"
|
||||
icon: code-pull-request
|
||||
---
|
||||
|
||||
Contributing to FastMCP means joining a community that values clean, maintainable code and thoughtful API design. All contributions are valued - from fixing typos in documentation to implementing major features.
|
||||
|
||||
## Issues
|
||||
|
||||
### Issue First, Code Second
|
||||
|
||||
**Every pull request requires a corresponding issue - no exceptions.** This requirement creates a collaborative space where approach, scope, and alignment are established before code is written. Issues serve as design documents where maintainers and contributors discuss implementation strategy, identify potential conflicts with existing patterns, and ensure proposed changes advance FastMCP's vision.
|
||||
|
||||
**FastMCP is an opinionated framework, not a kitchen sink.** The maintainers have strong beliefs about what FastMCP should and shouldn't do. Just because something takes N lines of code and you want it in fewer lines doesn't mean FastMCP should take on the maintenance burden or endorse that pattern. This is judged at the maintainers' discretion.
|
||||
|
||||
Use issues to understand scope BEFORE opening PRs. The issue discussion determines whether a feature belongs in core, contrib, or not at all.
|
||||
|
||||
### Writing Good Issues
|
||||
|
||||
FastMCP is an extremely highly-trafficked repository maintained by a very small team. Issues that appear to transfer burden to maintainers without any effort to validate the problem will be closed. Please help the maintainers help you by always providing a minimal reproducible example and clearly describing the problem.
|
||||
|
||||
**LLM-generated issues will be closed immediately.** Issues that contain paragraphs of unnecessary explanation, verbose problem descriptions, or obvious LLM authorship patterns obfuscate the actual problem and transfer burden to maintainers.
|
||||
|
||||
Write clear, concise issues that:
|
||||
- State the problem directly
|
||||
- Provide a minimal reproducible example
|
||||
- Skip unnecessary background or context
|
||||
- Take responsibility for clear communication
|
||||
|
||||
Issues may be labeled "Invalid" simply due to confusion caused by verbosity or not adhering to the guidelines outlined here.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
PRs that deviate from FastMCP's core principles will be rejected regardless of implementation quality. **PRs are NOT for iterating on ideas** - they should only be opened for ideas that already have a bias toward acceptance based on issue discussion.
|
||||
|
||||
|
||||
### Development Environment
|
||||
|
||||
#### Installation
|
||||
|
||||
To contribute to FastMCP, you'll need to set up a development environment with all necessary tools and dependencies.
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/jlowin/fastmcp.git
|
||||
cd fastmcp
|
||||
|
||||
# Install all dependencies including dev tools
|
||||
uv sync
|
||||
|
||||
# Install prek hooks
|
||||
uv run prek install
|
||||
```
|
||||
|
||||
In addition, some development commands require [just](https://github.com/casey/just) to be installed.
|
||||
|
||||
Prek hooks will run automatically on every commit to catch issues before they reach CI. If you see failures, fix them before committing - never commit broken code expecting to fix it later.
|
||||
|
||||
### Development Standards
|
||||
|
||||
#### Scope
|
||||
|
||||
Large pull requests create review bottlenecks and quality risks. Unless you're fixing a discrete bug or making an incredibly well-scoped change, keep PRs small and focused.
|
||||
|
||||
A PR that changes 50 lines across 3 files can be thoroughly reviewed in minutes. A PR that changes 500 lines across 20 files requires hours of careful analysis and often hides subtle issues.
|
||||
|
||||
Breaking large features into smaller PRs:
|
||||
- Creates better review experiences
|
||||
- Makes git history clear
|
||||
- Simplifies debugging with bisect
|
||||
- Reduces merge conflicts
|
||||
- Gets your code merged faster
|
||||
|
||||
#### Code Quality
|
||||
|
||||
FastMCP values clarity over cleverness. Every line you write will be maintained by someone else - possibly years from now, possibly without context about your decisions.
|
||||
|
||||
**PRs can be rejected for two opposing reasons:**
|
||||
1. **Insufficient quality** - Code that doesn't meet our standards for clarity, maintainability, or idiomaticity
|
||||
2. **Overengineering** - Code that is overbearing, unnecessarily complex, or tries to be too clever
|
||||
|
||||
The focus is on idiomatic, high-quality Python. FastMCP uses patterns like `NotSet` type as an alternative to `None` in certain situations - follow existing patterns.
|
||||
|
||||
#### Required Practices
|
||||
|
||||
**Full type annotations** on all functions and methods. They catch bugs before runtime and serve as inline documentation.
|
||||
|
||||
**Async/await patterns** for all I/O operations. Even if your specific use case doesn't need concurrency, consistency means users can compose features without worrying about blocking operations.
|
||||
|
||||
**Descriptive names** make code self-documenting. `auth_token` is clear; `tok` requires mental translation.
|
||||
|
||||
**Specific exception types** make error handling predictable. Catching `ValueError` tells readers exactly what error you expect. Never use bare `except` clauses.
|
||||
|
||||
#### Anti-Patterns to Avoid
|
||||
|
||||
**Complex one-liners** are hard to debug and modify. Break operations into clear steps.
|
||||
|
||||
**Mutable default arguments** cause subtle bugs. Use `None` as the default and create the mutable object inside the function.
|
||||
|
||||
**Breaking established patterns** confuses readers. If you must deviate, discuss in the issue first.
|
||||
|
||||
### Prek Checks
|
||||
|
||||
```bash
|
||||
# Runs automatically on commit, or manually:
|
||||
uv run prek run --all-files
|
||||
```
|
||||
|
||||
This runs three critical tools:
|
||||
- **Ruff**: Linting and formatting
|
||||
- **Prettier**: Code formatting
|
||||
- **ty**: Static type checking
|
||||
|
||||
Pytest runs separately as a distinct workflow step after prek checks pass. CI will reject PRs that fail these checks. Always run them locally first.
|
||||
|
||||
### Testing
|
||||
|
||||
Tests are documentation that shows how features work. Good tests give reviewers confidence and help future maintainers understand intent.
|
||||
|
||||
```bash
|
||||
# Run specific test directory
|
||||
uv run pytest tests/server/ -v
|
||||
|
||||
# Run all tests before submitting PR
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
Every new feature needs tests. See the [Testing Guide](/development/tests) for patterns and requirements.
|
||||
|
||||
### Documentation
|
||||
|
||||
A feature doesn't exist unless it's documented. Note that FastMCP's hosted documentation always tracks the main branch - users who want historical documentation can clone the repo, checkout a specific tag, and host it themselves.
|
||||
|
||||
```bash
|
||||
# Preview documentation locally
|
||||
just docs
|
||||
```
|
||||
|
||||
Documentation requirements:
|
||||
- **Explain concepts in prose first** - Code without context is just syntax
|
||||
- **Complete, runnable examples** - Every code block should be copy-pasteable
|
||||
- **Register in docs.json** - Makes pages appear in navigation
|
||||
- **Version badges** - Mark when features were added using `<VersionBadge />`
|
||||
|
||||
#### SDK Documentation
|
||||
|
||||
FastMCP's SDK documentation is auto-generated from the source code docstrings and type annotations. It is automatically updated on every merge to main by a GitHub Actions workflow, so users are *not* responsible for keeping the documentation up to date. However, to generate it proactively, you can use the following command:
|
||||
|
||||
```bash
|
||||
just api-ref-all
|
||||
```
|
||||
|
||||
### Submitting Your PR
|
||||
|
||||
#### Before Submitting
|
||||
|
||||
1. **Run all checks**: `uv run prek run --all-files && uv run pytest`
|
||||
2. **Keep scope small**: One feature or fix per PR
|
||||
3. **Write clear description**: Your PR description becomes permanent documentation
|
||||
4. **Update docs**: Include documentation for API changes
|
||||
|
||||
#### PR Description
|
||||
|
||||
Write PR descriptions that explain:
|
||||
- What problem you're solving
|
||||
- Why you chose this approach
|
||||
- Any trade-offs or alternatives considered
|
||||
- Migration path for breaking changes
|
||||
|
||||
Focus on the "why" - the code shows the "what". Keep it concise but complete.
|
||||
|
||||
#### What We Look For
|
||||
|
||||
**Framework Philosophy**: FastMCP is NOT trying to do all things or provide all shortcuts. Features are rejected when they don't align with the framework's vision, even if perfectly implemented. The burden of proof is on the PR to demonstrate value.
|
||||
|
||||
**Code Quality**: We verify code follows existing patterns. Consistency reduces cognitive load. When every module works similarly, developers understand new code quickly.
|
||||
|
||||
**Test Coverage**: Not every line needs testing, but every behavior does. Tests document intent and protect against regressions.
|
||||
|
||||
**Breaking Changes**: May be acceptable in minor versions but must be clearly documented. See the [versioning policy](/development/releases#versioning-policy).
|
||||
|
||||
## Special Modules
|
||||
|
||||
**`contrib`**: Community-maintained patterns and utilities. Original authors maintain their contributions. Not representative of the core framework.
|
||||
|
||||
**`experimental`**: Maintainer-developed features that may preview future functionality. Can break or be deleted at any time without notice. Pin your FastMCP version when using these features.
|
||||
79
docs/v2/development/releases.mdx
Normal file
79
docs/v2/development/releases.mdx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: "Releases"
|
||||
description: "FastMCP versioning and release process"
|
||||
icon: "truck-fast"
|
||||
---
|
||||
|
||||
FastMCP releases frequently to deliver features quickly in the rapidly evolving MCP ecosystem. We use semantic versioning pragmatically - the Model Context Protocol is young, patterns are still emerging, and waiting for perfect stability would mean missing opportunities to empower developers with better tools.
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
**Major (x.0.0)**: Complete API redesigns
|
||||
|
||||
Major versions represent fundamental shifts. FastMCP 2.x is entirely different from 1.x in both implementation and design philosophy.
|
||||
|
||||
**Minor (2.x.0)**: New features and evolution
|
||||
|
||||
<Warning>
|
||||
Unlike traditional semantic versioning, minor versions **may** include [breaking changes](#breaking-changes) when necessary for the ecosystem's evolution. This flexibility is essential in a young ecosystem where perfect backwards compatibility would prevent important improvements.
|
||||
</Warning>
|
||||
|
||||
FastMCP always targets the most current MCP Protocol version. Breaking changes in the MCP spec or MCP SDK automatically flow through to FastMCP - we prioritize staying current with the latest features and conventions over maintaining compatibility with older protocol versions.
|
||||
|
||||
**Patch (2.0.x)**: Bug fixes and refinements
|
||||
|
||||
Patch versions contain only bug fixes without breaking changes. These are safe updates you can apply with confidence.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
We permit breaking changes in minor versions because the MCP ecosystem is rapidly evolving. Refusing to break problematic APIs would accumulate design debt that eventually makes the framework unusable. Each breaking change represents a deliberate decision to keep FastMCP aligned with the ecosystem's evolution.
|
||||
|
||||
When breaking changes occur:
|
||||
- They only happen in minor versions (e.g., 2.3.x to 2.4.0)
|
||||
- Release notes explain what changed and how to migrate
|
||||
- We provide deprecation warnings at least 1 minor version in advance when possible
|
||||
- Changes must substantially benefit users to justify disruption
|
||||
|
||||
The public API is what's covered by our compatibility guarantees - these are the parts of FastMCP you can rely on to remain stable within a minor version. The public API consists of:
|
||||
- `FastMCP` server class, `Client` class, and FastMCP `Context`
|
||||
- Core MCP components: `Tool`, `Prompt`, `Resource`, `ResourceTemplate`, and transports
|
||||
- Their public methods and documented behaviors
|
||||
|
||||
Everything else (utilities, private methods, internal modules) may change without notice. This boundary lets us refactor internals and improve implementation details without breaking your code. For production stability, pin to specific versions.
|
||||
|
||||
<Warning>
|
||||
The `fastmcp.server.auth` module was introduced in 2.12.0 and is exempted from this policy temporarily, meaning it is *expected* to have breaking changes even on patch versions. This is because auth is a rapidly evolving part of the MCP spec and it would be dangerous to be beholden to old decisions. Please pin your FastMCP version if using authentication in production.
|
||||
|
||||
We expect this exemption to last through at least the 2.12.x and 2.13.x release series.
|
||||
</Warning>
|
||||
|
||||
### Production Use
|
||||
|
||||
Pin to exact versions:
|
||||
```
|
||||
fastmcp==2.11.0 # Good
|
||||
fastmcp>=2.11.0 # Bad - will install breaking changes
|
||||
```
|
||||
|
||||
## Creating Releases
|
||||
|
||||
Our release process is intentionally simple:
|
||||
|
||||
1. Create GitHub release with tag `vMAJOR.MINOR.PATCH` (e.g., `v2.11.0`)
|
||||
2. Generate release notes automatically, and curate or add additional editorial information as needed
|
||||
3. GitHub releases automatically trigger PyPI deployments
|
||||
|
||||
This automation lets maintainers focus on code quality rather than release mechanics.
|
||||
|
||||
### Release Cadence
|
||||
|
||||
We follow a feature-driven release cadence rather than a fixed schedule. Minor versions ship approximately every 3-4 weeks when significant functionality is ready.
|
||||
|
||||
Patch releases ship promptly for:
|
||||
- Critical bug fixes
|
||||
- Security updates (immediate release)
|
||||
- Regression fixes
|
||||
|
||||
This approach means you get improvements as soon as they're ready rather than waiting for arbitrary release dates.
|
||||
396
docs/v2/development/tests.mdx
Normal file
396
docs/v2/development/tests.mdx
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
---
|
||||
title: "Tests"
|
||||
description: "Testing patterns and requirements for FastMCP"
|
||||
icon: vial
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
Good tests are the foundation of reliable software. In FastMCP, we treat tests as first-class documentation that demonstrates how features work while protecting against regressions. Every new capability needs comprehensive tests that demonstrate correctness.
|
||||
|
||||
## FastMCP Tests
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
uv run pytest
|
||||
|
||||
# Run specific test file
|
||||
uv run pytest tests/server/test_auth.py
|
||||
|
||||
# Run with coverage
|
||||
uv run pytest --cov=fastmcp
|
||||
|
||||
# Skip integration tests for faster runs
|
||||
uv run pytest -m "not integration"
|
||||
|
||||
# Skip tests that spawn processes
|
||||
uv run pytest -m "not integration and not client_process"
|
||||
```
|
||||
|
||||
Tests should complete in under 1 second unless marked as integration tests. This speed encourages running them frequently, catching issues early.
|
||||
|
||||
### Test Organization
|
||||
|
||||
Our test organization mirrors the `src/` directory structure, creating a predictable mapping between code and tests. When you're working on `src/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files.
|
||||
|
||||
### Test Markers
|
||||
|
||||
We use pytest markers to categorize tests that require special resources or take longer to run:
|
||||
|
||||
```python
|
||||
@pytest.mark.integration
|
||||
async def test_github_api_integration():
|
||||
"""Test GitHub API integration with real service."""
|
||||
token = os.getenv("FASTMCP_GITHUB_TOKEN")
|
||||
if not token:
|
||||
pytest.skip("FASTMCP_GITHUB_TOKEN not available")
|
||||
|
||||
# Test against real GitHub API
|
||||
client = GitHubClient(token)
|
||||
repos = await client.list_repos("jlowin")
|
||||
assert "fastmcp" in [repo.name for repo in repos]
|
||||
|
||||
@pytest.mark.client_process
|
||||
async def test_stdio_transport():
|
||||
"""Test STDIO transport with separate process."""
|
||||
# This spawns a subprocess
|
||||
async with Client("python examples/simple_echo.py") as client:
|
||||
result = await client.call_tool("echo", {"message": "test"})
|
||||
assert result.content[0].text == "test"
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
|
||||
### Test Requirements
|
||||
|
||||
Following these practices creates maintainable, debuggable test suites that serve as both documentation and regression protection.
|
||||
|
||||
#### Single Behavior Per Test
|
||||
|
||||
Each test should verify exactly one behavior. When it fails, you need to know immediately what broke. A test that checks five things gives you five potential failure points to investigate. A test that checks one thing points directly to the problem.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Good: Atomic Test
|
||||
async def test_tool_registration():
|
||||
"""Test that tools are properly registered with the server."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
tools = mcp.list_tools()
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "add"
|
||||
```
|
||||
|
||||
```python Bad: Multi-Behavior Test
|
||||
async def test_server_functionality():
|
||||
"""Test multiple server features at once."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
# Tool registration
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
# Resource creation
|
||||
@mcp.resource("config://app")
|
||||
def get_config():
|
||||
return {"version": "1.0"}
|
||||
|
||||
# Authentication setup
|
||||
mcp.auth = BearerTokenProvider({"token": "user"})
|
||||
|
||||
# What exactly are we testing? If this fails, what broke?
|
||||
assert mcp.list_tools()
|
||||
assert mcp.list_resources()
|
||||
assert mcp.auth is not None
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
#### Self-Contained Setup
|
||||
|
||||
Every test must create its own setup. Tests should be runnable in any order, in parallel, or in isolation. When a test fails, you should be able to run just that test to reproduce the issue.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Good: Self-Contained
|
||||
async def test_tool_execution_with_error():
|
||||
"""Test that tool errors are properly handled."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
@mcp.tool
|
||||
def divide(a: int, b: int) -> float:
|
||||
if b == 0:
|
||||
raise ValueError("Cannot divide by zero")
|
||||
return a / b
|
||||
|
||||
async with Client(mcp) as client:
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool("divide", {"a": 10, "b": 0})
|
||||
```
|
||||
|
||||
```python Bad: Test Dependencies
|
||||
# Global state that tests depend on
|
||||
test_server = None
|
||||
|
||||
def test_setup_server():
|
||||
"""Setup for other tests."""
|
||||
global test_server
|
||||
test_server = FastMCP("shared-server")
|
||||
|
||||
def test_server_works():
|
||||
"""Test server functionality."""
|
||||
# Depends on test_setup_server running first
|
||||
assert test_server is not None
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
#### Clear Intent
|
||||
|
||||
Test names and assertions should make the verified behavior obvious. A developer reading your test should understand what feature it validates and how that feature should behave.
|
||||
|
||||
```python
|
||||
async def test_authenticated_tool_requires_valid_token():
|
||||
"""Test that authenticated users can access protected tools."""
|
||||
mcp = FastMCP("test-server")
|
||||
mcp.auth = BearerTokenProvider({"secret-token": "test-user"})
|
||||
|
||||
@mcp.tool
|
||||
def protected_action() -> str:
|
||||
return "success"
|
||||
|
||||
async with Client(mcp, auth=BearerAuth("secret-token")) as client:
|
||||
result = await client.call_tool("protected_action", {})
|
||||
assert result.content[0].text == "success"
|
||||
```
|
||||
|
||||
#### Using Fixtures
|
||||
|
||||
Use fixtures to create reusable data, server configurations, or other resources for your tests. Note that you should **not** open FastMCP clients in your fixtures as it can create hard-to-diagnose issues with event loops.
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastmcp import FastMCP, Client
|
||||
|
||||
@pytest.fixture
|
||||
def weather_server():
|
||||
server = FastMCP("WeatherServer")
|
||||
|
||||
@server.tool
|
||||
def get_temperature(city: str) -> dict:
|
||||
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
|
||||
return {"city": city, "temp": temps.get(city, 70)}
|
||||
|
||||
return server
|
||||
|
||||
async def test_temperature_tool(weather_server):
|
||||
async with Client(weather_server) as client:
|
||||
result = await client.call_tool("get_temperature", {"city": "LA"})
|
||||
assert result.data == {"city": "LA", "temp": 85}
|
||||
```
|
||||
|
||||
#### Effective Assertions
|
||||
|
||||
Assertions should be specific and provide context on failure. When a test fails during CI, the assertion message should tell you exactly what went wrong.
|
||||
|
||||
```python
|
||||
# Basic assertion - minimal context on failure
|
||||
assert result.status == "success"
|
||||
|
||||
# Better - explains what was expected
|
||||
assert result.status == "success", f"Expected successful operation, got {result.status}: {result.error}"
|
||||
```
|
||||
|
||||
Try not to have too many assertions in a single test unless you truly need to check various aspects of the same behavior. In general, assertions of different behaviors should be in separate tests.
|
||||
|
||||
#### Inline Snapshots
|
||||
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run of `pytest --inline-snapshot=create` with an empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
|
||||
```python
|
||||
from inline_snapshot import snapshot
|
||||
|
||||
async def test_tool_schema_generation():
|
||||
"""Test that tool schemas are generated correctly."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
@mcp.tool
|
||||
def calculate_tax(amount: float, rate: float = 0.1) -> dict:
|
||||
"""Calculate tax on an amount."""
|
||||
return {"amount": amount, "tax": amount * rate, "total": amount * (1 + rate)}
|
||||
|
||||
tools = mcp.list_tools()
|
||||
schema = tools[0].inputSchema
|
||||
|
||||
# First run: snapshot() is empty, gets auto-populated
|
||||
# Subsequent runs: compares against stored snapshot
|
||||
assert schema == snapshot({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {"type": "number"},
|
||||
"rate": {"type": "number", "default": 0.1}
|
||||
},
|
||||
"required": ["amount"]
|
||||
})
|
||||
```
|
||||
|
||||
### In-Memory Testing
|
||||
|
||||
FastMCP uses in-memory transport for testing, where servers and clients communicate directly. The majority of functionality can be tested in a deterministic fashion this way. We use more complex setups only when testing transports themselves.
|
||||
|
||||
The in-memory transport runs the real MCP protocol implementation without network overhead. Instead of deploying your server or managing network connections, you pass your server instance directly to the client. Everything runs in the same Python process - you can set breakpoints anywhere and step through with your debugger.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Client
|
||||
|
||||
# Create your server
|
||||
server = FastMCP("WeatherServer")
|
||||
|
||||
@server.tool
|
||||
def get_temperature(city: str) -> dict:
|
||||
"""Get current temperature for a city"""
|
||||
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
|
||||
return {"city": city, "temp": temps.get(city, 70)}
|
||||
|
||||
async def test_weather_operations():
|
||||
# Pass server directly - no deployment needed
|
||||
async with Client(server) as client:
|
||||
result = await client.call_tool("get_temperature", {"city": "NYC"})
|
||||
assert result.data == {"city": "NYC", "temp": 72}
|
||||
```
|
||||
|
||||
This pattern makes tests deterministic and fast - typically completing in milliseconds rather than seconds.
|
||||
|
||||
### Mocking External Dependencies
|
||||
|
||||
FastMCP servers are standard Python objects, so you can mock external dependencies using your preferred approach:
|
||||
|
||||
```python
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
async def test_database_tool():
|
||||
server = FastMCP("DataServer")
|
||||
|
||||
# Mock the database
|
||||
mock_db = AsyncMock()
|
||||
mock_db.fetch_users.return_value = [
|
||||
{"id": 1, "name": "Alice"},
|
||||
{"id": 2, "name": "Bob"}
|
||||
]
|
||||
|
||||
@server.tool
|
||||
async def list_users() -> list:
|
||||
return await mock_db.fetch_users()
|
||||
|
||||
async with Client(server) as client:
|
||||
result = await client.call_tool("list_users", {})
|
||||
assert len(result.data) == 2
|
||||
assert result.data[0]["name"] == "Alice"
|
||||
mock_db.fetch_users.assert_called_once()
|
||||
```
|
||||
|
||||
### Testing Network Transports
|
||||
|
||||
While in-memory testing covers most unit testing needs, you'll occasionally need to test actual network transports like HTTP or SSE. FastMCP provides two approaches: in-process async servers (preferred), and separate subprocess servers (for special cases).
|
||||
|
||||
#### In-Process Network Testing (Preferred)
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
For most network transport tests, use `run_server_async` as an async context manager. This runs the server as a task in the same process, providing fast, deterministic tests with full debugger support:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastmcp import FastMCP, Client
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
from fastmcp.utilities.tests import run_server_async
|
||||
|
||||
def create_test_server() -> FastMCP:
|
||||
"""Create a test server instance."""
|
||||
server = FastMCP("TestServer")
|
||||
|
||||
@server.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
return server
|
||||
|
||||
@pytest.fixture
|
||||
async def http_server() -> str:
|
||||
"""Start server in-process for testing."""
|
||||
server = create_test_server()
|
||||
async with run_server_async(server) as url:
|
||||
yield url
|
||||
|
||||
async def test_http_transport(http_server: str):
|
||||
"""Test actual HTTP transport behavior."""
|
||||
async with Client(
|
||||
transport=StreamableHttpTransport(http_server)
|
||||
) as client:
|
||||
result = await client.ping()
|
||||
assert result is True
|
||||
|
||||
greeting = await client.call_tool("greet", {"name": "World"})
|
||||
assert greeting.data == "Hello, World!"
|
||||
```
|
||||
|
||||
The `run_server_async` context manager automatically handles server lifecycle and cleanup. This approach is faster than subprocess-based testing and provides better error messages.
|
||||
|
||||
#### Subprocess Testing (Special Cases)
|
||||
|
||||
For tests that require complete process isolation (like STDIO transport or testing subprocess behavior), use `run_server_in_process`:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastmcp.utilities.tests import run_server_in_process
|
||||
from fastmcp import FastMCP, Client
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
def run_server(host: str, port: int) -> None:
|
||||
"""Function to run in subprocess."""
|
||||
server = FastMCP("TestServer")
|
||||
|
||||
@server.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
server.run(host=host, port=port)
|
||||
|
||||
@pytest.fixture
|
||||
async def http_server():
|
||||
"""Fixture that runs server in subprocess."""
|
||||
with run_server_in_process(run_server, transport="http") as url:
|
||||
yield f"{url}/mcp"
|
||||
|
||||
async def test_http_transport(http_server: str):
|
||||
"""Test actual HTTP transport behavior."""
|
||||
async with Client(
|
||||
transport=StreamableHttpTransport(http_server)
|
||||
) as client:
|
||||
result = await client.ping()
|
||||
assert result is True
|
||||
```
|
||||
|
||||
The `run_server_in_process` utility handles server lifecycle, port allocation, and cleanup automatically. Use this only when subprocess isolation is truly necessary, as it's slower and harder to debug than in-process testing. FastMCP uses the `client_process` marker to isolate these tests in CI.
|
||||
|
||||
### Documentation Testing
|
||||
|
||||
Documentation requires the same validation as code. The `just docs` command launches a local Mintlify server that renders your documentation exactly as users will see it:
|
||||
|
||||
```bash
|
||||
# Start local documentation server with hot reload
|
||||
just docs
|
||||
|
||||
# Or run Mintlify directly
|
||||
mintlify dev
|
||||
```
|
||||
|
||||
The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it.
|
||||
153
docs/v2/development/upgrade-guide.mdx
Normal file
153
docs/v2/development/upgrade-guide.mdx
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: Upgrade Guide
|
||||
sidebarTitle: Upgrade Guide
|
||||
description: Migration instructions for upgrading between FastMCP versions
|
||||
icon: up
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
This guide provides migration instructions for breaking changes and major updates when upgrading between FastMCP versions.
|
||||
|
||||
## v2.14.0
|
||||
|
||||
### OpenAPI Parser Promotion
|
||||
|
||||
The experimental OpenAPI parser is now the standard implementation. The legacy parser has been removed.
|
||||
|
||||
**If you were using the legacy parser:** No code changes required. The new parser is a drop-in replacement with improved architecture.
|
||||
|
||||
**If you were using the experimental parser:** Update your imports from the experimental module to the standard location:
|
||||
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
from fastmcp.experimental.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
|
||||
```
|
||||
|
||||
```python After
|
||||
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The experimental imports will continue working temporarily but will show deprecation warnings. The `FASTMCP_EXPERIMENTAL_ENABLE_NEW_OPENAPI_PARSER` environment variable is no longer needed and can be removed.
|
||||
|
||||
### Deprecated Features Removed
|
||||
|
||||
The following deprecated features have been removed in v2.14.0:
|
||||
|
||||
**BearerAuthProvider** (deprecated in v2.11):
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
from fastmcp.server.auth.providers.bearer import BearerAuthProvider
|
||||
```
|
||||
|
||||
```python After
|
||||
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
**Context.get_http_request()** (deprecated in v2.2.11):
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
request = context.get_http_request()
|
||||
```
|
||||
|
||||
```python After
|
||||
from fastmcp.server.dependencies import get_http_request
|
||||
request = get_http_request()
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
**Top-level Image import** (deprecated in v2.8.1):
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
from fastmcp import Image
|
||||
```
|
||||
|
||||
```python After
|
||||
from fastmcp.utilities.types import Image
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
**FastMCP dependencies parameter** (deprecated in v2.11.4):
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
mcp = FastMCP("server", dependencies=["requests", "pandas"])
|
||||
```
|
||||
|
||||
```json After
|
||||
{
|
||||
"environment": {
|
||||
"dependencies": ["requests", "pandas"]
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
**Legacy resource prefix format**: The `resource_prefix_format` parameter and "protocol" format have been removed. Only the "path" format is supported (this was already the default).
|
||||
|
||||
**FastMCPProxy client parameter**:
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
proxy = FastMCPProxy(client=my_client)
|
||||
```
|
||||
|
||||
```python After
|
||||
proxy = FastMCPProxy(client_factory=lambda: my_client)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
**output_schema=False**:
|
||||
<CodeGroup>
|
||||
```python Before
|
||||
@mcp.tool(output_schema=False)
|
||||
def my_tool() -> str:
|
||||
return "result"
|
||||
```
|
||||
|
||||
```python After
|
||||
@mcp.tool(output_schema=None)
|
||||
def my_tool() -> str:
|
||||
return "result"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## v2.13.0
|
||||
|
||||
### OAuth Token Key Management
|
||||
|
||||
The OAuth proxy now issues its own JWT tokens to clients instead of forwarding upstream provider tokens. This improves security by maintaining proper token audience boundaries.
|
||||
|
||||
**What changed:**
|
||||
|
||||
The OAuth proxy now implements a token factory pattern - it receives tokens from your OAuth provider (GitHub, Google, etc.), encrypts and stores them, then issues its own FastMCP JWT tokens to clients. This requires cryptographic keys for JWT signing and token encryption.
|
||||
|
||||
**Default behavior (development):**
|
||||
|
||||
By default, FastMCP automatically manages keys based on your platform:
|
||||
- **Mac/Windows**: Keys are auto-managed via system keyring, surviving server restarts with zero configuration. Suitable **only** for development and local testing.
|
||||
- **Linux**: Keys are ephemeral (random salt at startup, regenerated on each restart).
|
||||
|
||||
This works fine for development and testing where re-authentication after restart is acceptable.
|
||||
|
||||
**For production:**
|
||||
|
||||
Production deployments must provide explicit keys and use persistent storage. Add these three things:
|
||||
|
||||
```python
|
||||
auth = GitHubProvider(
|
||||
client_id=os.environ["GITHUB_CLIENT_ID"],
|
||||
client_secret=os.environ["GITHUB_CLIENT_SECRET"],
|
||||
base_url="https://your-server.com",
|
||||
|
||||
# Explicit keys (required for production)
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
|
||||
# Persistent network storage (required for production)
|
||||
client_storage=RedisStore(host="redis.example.com", port=6379)
|
||||
)
|
||||
```
|
||||
|
||||
**More information:**
|
||||
- [OAuth Token Security](/deployment/http#oauth-token-security) - Complete production setup guide
|
||||
- [Key and Storage Management](/servers/auth/oauth-proxy#key-and-storage-management) - Detailed explanation of defaults and production requirements
|
||||
- [OAuth Proxy Parameters](/servers/auth/oauth-proxy#configuration-parameters) - Parameter documentation
|
||||
100
docs/v2/getting-started/installation.mdx
Normal file
100
docs/v2/getting-started/installation.mdx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
title: Installation
|
||||
icon: arrow-down-to-line
|
||||
---
|
||||
## Install FastMCP
|
||||
|
||||
We recommend using [uv](https://docs.astral.sh/uv/getting-started/installation/) to install and manage FastMCP.
|
||||
|
||||
If you plan to use FastMCP in your project, you can add it as a dependency with:
|
||||
|
||||
```bash
|
||||
uv add fastmcp
|
||||
```
|
||||
|
||||
Alternatively, you can install it directly with `pip` or `uv pip`:
|
||||
<CodeGroup>
|
||||
```bash uv
|
||||
uv pip install fastmcp
|
||||
```
|
||||
|
||||
```bash pip
|
||||
pip install fastmcp
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
**FastMCP 3.0** is in development and may include breaking changes. To avoid unexpected issues, pin your dependency to v2: `fastmcp<3`
|
||||
</Warning>
|
||||
|
||||
### Verify Installation
|
||||
|
||||
To verify that FastMCP is installed correctly, you can run the following command:
|
||||
|
||||
```bash
|
||||
fastmcp version
|
||||
```
|
||||
|
||||
You should see output like the following:
|
||||
|
||||
```bash
|
||||
$ fastmcp version
|
||||
|
||||
FastMCP version: 2.11.3
|
||||
MCP version: 1.12.4
|
||||
Python version: 3.12.2
|
||||
Platform: macOS-15.3.1-arm64-arm-64bit
|
||||
FastMCP root path: ~/Developer/fastmcp
|
||||
```
|
||||
|
||||
### Dependency Licensing
|
||||
|
||||
<Info>
|
||||
FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations.
|
||||
|
||||
If this is a concern, you can install Cyclopts v5 alpha which removes this dependency:
|
||||
|
||||
```bash
|
||||
pip install "cyclopts>=5.0.0a1"
|
||||
```
|
||||
|
||||
Alternatively, wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details.
|
||||
</Info>
|
||||
## Upgrading from the Official MCP SDK
|
||||
|
||||
Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
|
||||
|
||||
|
||||
```python {5}
|
||||
# Before
|
||||
# from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# After
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
|
||||
</Warning>
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
FastMCP follows semantic versioning with pragmatic adaptations for the rapidly evolving MCP ecosystem. Breaking changes may occur in minor versions (e.g., 2.3.x to 2.4.0) when necessary to stay current with the MCP Protocol.
|
||||
|
||||
For production use, always pin to exact versions:
|
||||
```
|
||||
fastmcp==2.11.0 # Good
|
||||
fastmcp>=2.11.0 # Bad - will install breaking changes
|
||||
```
|
||||
|
||||
See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy.
|
||||
|
||||
## Contributing to FastMCP
|
||||
|
||||
Interested in contributing to FastMCP? See the [Contributing Guide](/development/contributing) for details on:
|
||||
- Setting up your development environment
|
||||
- Running tests and pre-commit hooks
|
||||
- Submitting issues and pull requests
|
||||
- Code standards and review process
|
||||
136
docs/v2/getting-started/quickstart.mdx
Normal file
136
docs/v2/getting-started/quickstart.mdx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
---
|
||||
title: Quickstart
|
||||
icon: rocket-launch
|
||||
---
|
||||
|
||||
Welcome! This guide will help you quickly set up FastMCP, run your first MCP server, and deploy a server to FastMCP Cloud.
|
||||
|
||||
If you haven't already installed FastMCP, follow the [installation instructions](/getting-started/installation).
|
||||
|
||||
## Create a FastMCP Server
|
||||
|
||||
A FastMCP server is a collection of tools, resources, and other MCP components. To create a server, start by instantiating the `FastMCP` class.
|
||||
|
||||
Create a new file called `my_server.py` and add the following code:
|
||||
|
||||
```python my_server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
```
|
||||
|
||||
|
||||
That's it! You've created a FastMCP server, albeit a very boring one. Let's add a tool to make it more interesting.
|
||||
|
||||
|
||||
## Add a Tool
|
||||
|
||||
To add a tool that returns a simple greeting, write a function and decorate it with `@mcp.tool` to register it with the server:
|
||||
|
||||
```python my_server.py {5-7}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
|
||||
## Run the Server
|
||||
|
||||
The simplest way to run your FastMCP server is to call its `run()` method. You can choose between different transports, like `stdio` for local servers, or `http` for remote access:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python my_server.py (stdio) {9, 10}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
```python my_server.py (HTTP) {9, 10}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
This lets us run the server with `python my_server.py`. The stdio transport is the traditional way to connect MCP servers to clients, while the HTTP transport enables remote connections.
|
||||
|
||||
<Tip>
|
||||
Why do we need the `if __name__ == "__main__":` block?
|
||||
|
||||
The `__main__` block is recommended for consistency and compatibility, ensuring your server works with all MCP clients that execute your server file as a script. Users who will exclusively run their server with the FastMCP CLI can omit it, as the CLI imports the server object directly.
|
||||
</Tip>
|
||||
|
||||
### Using the FastMCP CLI
|
||||
|
||||
You can also use the `fastmcp run` command to start your server. Note that the FastMCP CLI **does not** execute the `__main__` block of your server file. Instead, it imports your server object and runs it with whatever transport and options you provide.
|
||||
|
||||
For example, to run this server with the default stdio transport (no matter how you called `mcp.run()`), you can use the following command:
|
||||
```bash
|
||||
fastmcp run my_server.py:mcp
|
||||
```
|
||||
|
||||
To run this server with the HTTP transport, you can use the following command:
|
||||
```bash
|
||||
fastmcp run my_server.py:mcp --transport http --port 8000
|
||||
```
|
||||
|
||||
## Call Your Server
|
||||
|
||||
Once your server is running with HTTP transport, you can connect to it with a FastMCP client or any LLM client that supports the MCP protocol:
|
||||
|
||||
```python my_client.py
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
client = Client("http://localhost:8000/mcp")
|
||||
|
||||
async def call_tool(name: str):
|
||||
async with client:
|
||||
result = await client.call_tool("greet", {"name": name})
|
||||
print(result)
|
||||
|
||||
asyncio.run(call_tool("Ford"))
|
||||
```
|
||||
|
||||
Note that:
|
||||
- FastMCP clients are asynchronous, so we need to use `asyncio.run` to run the client
|
||||
- We must enter a client context (`async with client:`) before using the client
|
||||
- You can make multiple client calls within the same context
|
||||
|
||||
## Deploy to FastMCP Cloud
|
||||
|
||||
[FastMCP Cloud](https://fastmcp.cloud) is a hosting service run by the FastMCP team at [Prefect](https://www.prefect.io/fastmcp). It is optimized to deploy authenticated FastMCP servers as quickly as possible, giving you a secure URL that you can plug into any LLM client.
|
||||
|
||||
<Info>
|
||||
FastMCP Cloud is **free for personal servers** and offers simple pay-as-you-go pricing for teams.
|
||||
</Info>
|
||||
|
||||
To deploy your server, you'll need a [GitHub account](https://github.com). Once you have one, you can deploy your server in three steps:
|
||||
|
||||
1. Push your `my_server.py` file to a GitHub repository
|
||||
2. Sign in to [FastMCP Cloud](https://fastmcp.cloud) with your GitHub account
|
||||
3. Create a new project from your repository and enter `my_server.py:mcp` as the server entrypoint
|
||||
|
||||
That's it! FastMCP Cloud will build and deploy your server, making it available at a URL like `https://your-project.fastmcp.app/mcp`. You can chat with it to test its functionality, or connect to it from any LLM client that supports the MCP protocol.
|
||||
|
||||
For more details, see the [FastMCP Cloud guide](/deployment/fastmcp-cloud).
|
||||
115
docs/v2/getting-started/welcome.mdx
Normal file
115
docs/v2/getting-started/welcome.mdx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: "Welcome to FastMCP 2.0!"
|
||||
sidebarTitle: "Welcome!"
|
||||
description: The fast, Pythonic way to build MCP servers and clients.
|
||||
icon: hand-wave
|
||||
---
|
||||
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves.png"
|
||||
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl block dark:hidden"
|
||||
/>
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves-dark.png"
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl hidden dark:block"
|
||||
/>
|
||||
|
||||
|
||||
**FastMCP is the standard framework for building MCP applications.** The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) provides a standardized way to connect LLMs to tools and data, and FastMCP makes it production-ready with clean, Pythonic code:
|
||||
|
||||
```python {1}
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Demo 🚀")
|
||||
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
## Beyond Basic MCP
|
||||
|
||||
FastMCP pioneered Python MCP development, and FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
||||
|
||||
**This is FastMCP 2.0,** the actively maintained version that extends far beyond basic protocol implementation. While the SDK provides core functionality, FastMCP 2.0 delivers everything needed for production: advanced MCP patterns (server composition, proxying, OpenAPI/FastAPI generation, tool transformation), enterprise auth (Google, GitHub, Azure, Auth0, WorkOS, and more), deployment tools, testing frameworks, and comprehensive client libraries.
|
||||
|
||||
Ready to build? Start with our [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart).
|
||||
|
||||
FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
|
||||
|
||||
<Warning>
|
||||
**FastMCP 3.0** is in development and may include breaking changes. To avoid unexpected issues, pin your dependency to v2: `fastmcp<3`
|
||||
</Warning>
|
||||
|
||||
## What is MCP?
|
||||
|
||||
The Model Context Protocol lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can:
|
||||
|
||||
- Expose data through `Resources` (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
|
||||
- Provide functionality through `Tools` (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
|
||||
- Define interaction patterns through `Prompts` (reusable templates for LLM interactions)
|
||||
- And more!
|
||||
|
||||
FastMCP provides a high-level, Pythonic interface for building, managing, and interacting with these servers.
|
||||
|
||||
## Why FastMCP?
|
||||
|
||||
FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need — FastMCP handles the rest.
|
||||
|
||||
🚀 **Fast**: High-level interface means less code and faster development
|
||||
|
||||
🍀 **Simple**: Build MCP servers with minimal boilerplate
|
||||
|
||||
🐍 **Pythonic**: Feels natural to Python developers
|
||||
|
||||
🔍 **Complete**: Everything for production — enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more
|
||||
|
||||
FastMCP provides the shortest path from idea to production. Deploy locally, to the cloud with [FastMCP Cloud](https://fastmcp.cloud) (free for personal servers), or to your own infrastructure.
|
||||
|
||||
<Tip>
|
||||
**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 2.13.1`) to indicate when they were introduced. Note that this may include features that are not yet released.
|
||||
</Tip>
|
||||
|
||||
## LLM-Friendly Docs
|
||||
|
||||
The FastMCP documentation is available in multiple LLM-friendly formats:
|
||||
|
||||
### MCP Server
|
||||
|
||||
The FastMCP docs are accessible via MCP! The server URL is `https://gofastmcp.com/mcp`.
|
||||
|
||||
In fact, you can use FastMCP to search the FastMCP docs:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
async with Client("https://gofastmcp.com/mcp") as client:
|
||||
result = await client.call_tool(
|
||||
name="SearchFastMcp",
|
||||
arguments={"query": "deploy a FastMCP server"}
|
||||
)
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Text Formats
|
||||
|
||||
The docs are also available in [llms.txt format](https://llmstxt.org/):
|
||||
- [llms.txt](https://gofastmcp.com/llms.txt) - A sitemap listing all documentation pages
|
||||
- [llms-full.txt](https://gofastmcp.com/llms-full.txt) - The entire documentation in one file (may exceed context windows)
|
||||
|
||||
Any page can be accessed as markdown by appending `.md` to the URL. For example, this page becomes `https://gofastmcp.com/getting-started/welcome.md`.
|
||||
|
||||
You can also copy any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.
|
||||
228
docs/v2/integrations/anthropic.mdx
Normal file
228
docs/v2/integrations/anthropic.mdx
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
---
|
||||
title: Anthropic API 🤝 FastMCP
|
||||
sidebarTitle: Anthropic API
|
||||
description: Connect FastMCP servers to the Anthropic API
|
||||
icon: message-code
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
|
||||
Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.
|
||||
|
||||
<Tip>
|
||||
Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
|
||||
</Tip>
|
||||
|
||||
## Create a Server
|
||||
|
||||
First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.
|
||||
|
||||
```python server.py
|
||||
import random
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Dice Roller")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
## Deploy the Server
|
||||
|
||||
Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.
|
||||
|
||||
For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.
|
||||
|
||||
Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:
|
||||
|
||||
<CodeGroup>
|
||||
```bash FastMCP server
|
||||
python server.py
|
||||
```
|
||||
|
||||
```bash ngrok
|
||||
ngrok http 8000
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
|
||||
</Warning>
|
||||
|
||||
## Call the Server
|
||||
|
||||
To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):
|
||||
|
||||
```bash
|
||||
pip install anthropic
|
||||
```
|
||||
|
||||
You'll also need to authenticate with Anthropic. You can do this by setting the `ANTHROPIC_API_KEY` environment variable. Consult the Anthropic SDK documentation for more information.
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment. **At this time you must also include the `extra_headers` parameter with the `anthropic-beta` header.**
|
||||
|
||||
```python {5, 13-22}
|
||||
import anthropic
|
||||
from rich import print
|
||||
|
||||
# Your server URL (replace with your actual URL)
|
||||
url = 'https://your-server-url.com'
|
||||
|
||||
client = anthropic.Anthropic()
|
||||
|
||||
response = client.beta.messages.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
max_tokens=1000,
|
||||
messages=[{"role": "user", "content": "Roll a few dice!"}],
|
||||
mcp_servers=[
|
||||
{
|
||||
"type": "url",
|
||||
"url": f"{url}/mcp/",
|
||||
"name": "dice-server",
|
||||
}
|
||||
],
|
||||
extra_headers={
|
||||
"anthropic-beta": "mcp-client-2025-04-04"
|
||||
}
|
||||
)
|
||||
|
||||
print(response.content)
|
||||
```
|
||||
|
||||
If you run this code, you'll see something like the following output:
|
||||
|
||||
```text
|
||||
I'll roll some dice for you! Let me use the dice rolling tool.
|
||||
|
||||
I rolled 3 dice and got: 4, 2, 6
|
||||
|
||||
The results were 4, 2, and 6. Would you like me to roll again or roll a different number of dice?
|
||||
```
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
<VersionBadge version="2.6.0" />
|
||||
|
||||
The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.
|
||||
|
||||
### Server Authentication
|
||||
|
||||
The simplest way to add authentication to the server is to use a bearer token scheme.
|
||||
|
||||
For this example, we'll quickly generate our own tokens with FastMCP's `RSAKeyPair` utility, but this may not be appropriate for production use. For more details, see the complete server-side [Token Verification](/servers/auth/token-verification) documentation.
|
||||
|
||||
We'll start by creating an RSA key pair to sign and verify tokens.
|
||||
|
||||
```python
|
||||
from fastmcp.server.auth.providers.jwt import RSAKeyPair
|
||||
|
||||
key_pair = RSAKeyPair.generate()
|
||||
access_token = key_pair.create_token(audience="dice-server")
|
||||
```
|
||||
|
||||
<Warning>
|
||||
FastMCP's `RSAKeyPair` utility is for development and testing only.
|
||||
</Warning>
|
||||
|
||||
Next, we'll create a `JWTVerifier` to authenticate the server.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth import JWTVerifier
|
||||
|
||||
auth = JWTVerifier(
|
||||
public_key=key_pair.public_key,
|
||||
audience="dice-server",
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Dice Roller", auth=auth)
|
||||
```
|
||||
|
||||
Here is a complete example that you can copy/paste. For simplicity and the purposes of this example only, it will print the token to the console. **Do NOT do this in production!**
|
||||
|
||||
```python server.py [expandable]
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth import JWTVerifier
|
||||
from fastmcp.server.auth.providers.jwt import RSAKeyPair
|
||||
import random
|
||||
|
||||
key_pair = RSAKeyPair.generate()
|
||||
access_token = key_pair.create_token(audience="dice-server")
|
||||
|
||||
auth = JWTVerifier(
|
||||
public_key=key_pair.public_key,
|
||||
audience="dice-server",
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Dice Roller", auth=auth)
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
### Client Authentication
|
||||
|
||||
If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
|
||||
|
||||
```python
|
||||
Error code: 400 - {
|
||||
"type": "error",
|
||||
"error": {
|
||||
"type": "invalid_request_error",
|
||||
"message": "MCP server 'dice-server' requires authentication. Please provide an authorization_token.",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
To authenticate the client, you can pass the token using the `authorization_token` parameter in your MCP server configuration:
|
||||
|
||||
```python {8, 21}
|
||||
import anthropic
|
||||
from rich import print
|
||||
|
||||
# Your server URL (replace with your actual URL)
|
||||
url = 'https://your-server-url.com'
|
||||
|
||||
# Your access token (replace with your actual token)
|
||||
access_token = 'your-access-token'
|
||||
|
||||
client = anthropic.Anthropic()
|
||||
|
||||
response = client.beta.messages.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
max_tokens=1000,
|
||||
messages=[{"role": "user", "content": "Roll a few dice!"}],
|
||||
mcp_servers=[
|
||||
{
|
||||
"type": "url",
|
||||
"url": f"{url}/mcp/",
|
||||
"name": "dice-server",
|
||||
"authorization_token": access_token
|
||||
}
|
||||
],
|
||||
extra_headers={
|
||||
"anthropic-beta": "mcp-client-2025-04-04"
|
||||
}
|
||||
)
|
||||
|
||||
print(response.content)
|
||||
```
|
||||
|
||||
You should now see the dice roll results in the output.
|
||||
277
docs/v2/integrations/auth0.mdx
Normal file
277
docs/v2/integrations/auth0.mdx
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
---
|
||||
title: Auth0 OAuth 🤝 FastMCP
|
||||
sidebarTitle: Auth0
|
||||
description: Secure your FastMCP server with Auth0 OAuth
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.12.4" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **Auth0 OAuth**. While Auth0 does have support for Dynamic Client Registration, it is not enabled by default so this integration uses the [**OIDC Proxy**](/servers/auth/oidc-proxy) pattern to bridge Auth0's dynamic OIDC configuration with MCP's authentication requirements.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. An **[Auth0 Account](https://auth0.com/)** with access to create Applications
|
||||
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
|
||||
|
||||
### Step 1: Create an Auth0 Application
|
||||
|
||||
Create an Application in your Auth0 settings to get the credentials needed for authentication:
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to Applications">
|
||||
Go to **Applications → Applications** in your Auth0 account.
|
||||
|
||||
Click **"+ Create Application"** to create a new application.
|
||||
</Step>
|
||||
|
||||
<Step title="Create Your Application">
|
||||
- **Name**: Choose a name users will recognize (e.g., "My FastMCP Server")
|
||||
- **Choose an application type**: Choose "Single Page Web Applications"
|
||||
- Click **Create** to create the application
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Your Application">
|
||||
Select the "Settings" tab for your application, then find the "Application URIs" section.
|
||||
|
||||
- **Allowed Callback URLs**: Your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
|
||||
- Click **Save** to save your changes
|
||||
|
||||
<Warning>
|
||||
The callback URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter.
|
||||
</Warning>
|
||||
|
||||
<Tip>
|
||||
If you want to use a custom callback path (e.g., `/auth/auth0/callback`), make sure to set the same path in both your Auth0 Application settings and the `redirect_path` parameter when configuring the Auth0Provider.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Save Your Credentials">
|
||||
After creating the app, in the "Basic Information" section you'll see:
|
||||
|
||||
- **Client ID**: A public identifier like `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`
|
||||
- **Client Secret**: A private hidden value that should always be stored securely
|
||||
|
||||
<Tip>
|
||||
Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Select Your Audience">
|
||||
Go to **Applications → APIs** in your Auth0 account.
|
||||
|
||||
- Find the API that you want to use for your application
|
||||
- **API Audience**: A URL that uniquely identifies the API
|
||||
|
||||
<Tip>
|
||||
Store this along with of the credentials above. Never commit this to version control. Use environment variables or a secrets manager in production.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server using the `Auth0Provider`.
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
||||
|
||||
# The Auth0Provider utilizes Auth0 OIDC configuration
|
||||
auth_provider = Auth0Provider(
|
||||
config_url="https://.../.well-known/openid-configuration", # Your Auth0 configuration URL
|
||||
client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB", # Your Auth0 application Client ID
|
||||
client_secret="vPYqbjemq...", # Your Auth0 application Client Secret
|
||||
audience="https://...", # Your Auth0 API audience
|
||||
base_url="http://localhost:8000", # Must match your application configuration
|
||||
# redirect_path="/auth/callback" # Default value, customize if needed
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Auth0 Secured App", auth=auth_provider)
|
||||
|
||||
# Add a protected tool to test authentication
|
||||
@mcp.tool
|
||||
async def get_token_info() -> dict:
|
||||
"""Returns information about the Auth0 token."""
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
token = get_access_token()
|
||||
|
||||
return {
|
||||
"issuer": token.claims.get("iss"),
|
||||
"audience": token.claims.get("aud"),
|
||||
"scope": token.claims.get("scope")
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Running the Server
|
||||
|
||||
Start your FastMCP server with HTTP transport to enable OAuth flows:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Your server is now running and protected by Auth0 authentication.
|
||||
|
||||
### Testing with a Client
|
||||
|
||||
Create a test client that authenticates with your Auth0-protected server:
|
||||
|
||||
```python test_client.py
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# The client will automatically handle Auth0 OAuth flows
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
# First-time connection will open Auth0 login in your browser
|
||||
print("✓ Authenticated with Auth0!")
|
||||
|
||||
# Test the protected tool
|
||||
result = await client.call_tool("get_token_info")
|
||||
print(f"Auth0 audience: {result['audience']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
When you run the client for the first time:
|
||||
1. Your browser will open to Auth0's authorization page
|
||||
2. After you authorize the app, you'll be redirected back
|
||||
3. The client receives the token and can make authenticated requests
|
||||
|
||||
## Production Configuration
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`:
|
||||
|
||||
```python server.py
|
||||
import os
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
||||
from key_value.aio.stores.redis import RedisStore
|
||||
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
# Production setup with encrypted persistent token storage
|
||||
auth_provider = Auth0Provider(
|
||||
config_url="https://.../.well-known/openid-configuration",
|
||||
client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB",
|
||||
client_secret="vPYqbjemq...",
|
||||
audience="https://...",
|
||||
base_url="https://your-production-domain.com",
|
||||
|
||||
# Production token management
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
client_storage=FernetEncryptionWrapper(
|
||||
key_value=RedisStore(
|
||||
host=os.environ["REDIS_HOST"],
|
||||
port=int(os.environ["REDIS_PORT"])
|
||||
),
|
||||
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
|
||||
)
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Production Auth0 App", auth=auth_provider)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
|
||||
|
||||
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
|
||||
</Note>
|
||||
|
||||
<Info>
|
||||
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
|
||||
</Info>
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the Auth0 provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.auth0.Auth0Provider` to use Auth0 authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Auth0-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the Auth0 provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL" required>
|
||||
Your Auth0 Application Configuration URL (e.g., `https://.../.well-known/openid-configuration`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID" required>
|
||||
Your Auth0 Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET" required>
|
||||
Your Auth0 Application Client Secret (e.g., `vPYqbjemq...`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE" required>
|
||||
Your Auth0 API Audience
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_BASE_URL" required>
|
||||
Public URL where OAuth endpoints will be accessible (includes any mount path)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_ISSUER_URL" default="Uses BASE_URL">
|
||||
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_REDIRECT_PATH" default="/auth/callback">
|
||||
Redirect path configured in your Auth0 Application
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES" default='["openid"]'>
|
||||
Comma-, space-, or JSON-separated list of required AUth0 scopes (e.g., `openid email` or `["openid","email"]`)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
# Use the Auth0 provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider
|
||||
|
||||
# Auth0 configuration and credentials
|
||||
FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration
|
||||
FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB
|
||||
FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq...
|
||||
FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://...
|
||||
FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES=openid,email
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="Auth0 Secured App")
|
||||
|
||||
@mcp.tool
|
||||
async def search_logs() -> list[str]:
|
||||
"""Search the service logs."""
|
||||
# Your tool implementation here
|
||||
pass
|
||||
```
|
||||
133
docs/v2/integrations/authkit.mdx
Normal file
133
docs/v2/integrations/authkit.mdx
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
title: AuthKit 🤝 FastMCP
|
||||
sidebarTitle: AuthKit
|
||||
description: Secure your FastMCP server with AuthKit by WorkOS
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using WorkOS's **AuthKit**, a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where AuthKit handles user login and your FastMCP server validates the tokens.
|
||||
|
||||
|
||||
## Configuration
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. A **[WorkOS Account](https://workos.com/)** and a new **Project**.
|
||||
2. An **[AuthKit](https://www.authkit.com/)** instance configured within your WorkOS project.
|
||||
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`).
|
||||
|
||||
### Step 1: AuthKit Configuration
|
||||
|
||||
In your WorkOS Dashboard, enable AuthKit and configure the following settings:
|
||||
|
||||
<Steps>
|
||||
<Step title="Enable Dynamic Client Registration">
|
||||
Go to **Applications → Configuration** and enable **Dynamic Client Registration**. This allows MCP clients register with your application automatically.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Note Your AuthKit Domain">
|
||||
Find your **AuthKit Domain** on the configuration page. It will look like `https://your-project-12345.authkit.app`. You'll need this for your FastMCP server configuration.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server file and use the `AuthKitProvider` to handle all the OAuth integration automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
||||
|
||||
# The AuthKitProvider automatically discovers WorkOS endpoints
|
||||
# and configures JWT token validation
|
||||
auth_provider = AuthKitProvider(
|
||||
authkit_domain="https://your-project-12345.authkit.app",
|
||||
base_url="http://localhost:8000" # Use your actual server URL
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="AuthKit Secured App", auth=auth_provider)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the `authkit_domain` and `base_url` with your actual values!), you can run the following command:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Now, you can use a FastMCP client to test that you can reach your server after authenticating:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
|
||||
## Environment Variables
|
||||
|
||||
<VersionBadge version="2.12.1" />
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the AuthKit provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.workos.AuthKitProvider` to use AuthKit authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### AuthKit-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the AuthKit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN" required>
|
||||
Your AuthKit domain (e.g., `https://your-project-12345.authkit.app`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL" required>
|
||||
Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_REQUIRED_SCOPES" default="[]">
|
||||
Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid", "profile", "email"]`)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
# Use the AuthKit provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider
|
||||
|
||||
# AuthKit configuration
|
||||
FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project-12345.authkit.app
|
||||
FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_REQUIRED_SCOPES=openid,profile,email
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="AuthKit Secured App")
|
||||
```
|
||||
365
docs/v2/integrations/aws-cognito.mdx
Normal file
365
docs/v2/integrations/aws-cognito.mdx
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
---
|
||||
title: AWS Cognito OAuth 🤝 FastMCP
|
||||
sidebarTitle: AWS Cognito
|
||||
description: Secure your FastMCP server with AWS Cognito user pools
|
||||
icon: aws
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.12.4" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **AWS Cognito user pools**. Since AWS Cognito doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge AWS Cognito's traditional OAuth with MCP's authentication requirements. It also includes robust JWT token validation, ensuring enterprise-grade authentication.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. An **[AWS Account](https://aws.amazon.com/)** with access to create AWS Cognito user pools
|
||||
2. Basic familiarity with AWS Cognito concepts (user pools, app clients)
|
||||
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
|
||||
|
||||
### Step 1: Create an AWS Cognito User Pool and App Client
|
||||
|
||||
Set up AWS Cognito user pool with an app client to get the credentials needed for authentication:
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to AWS Cognito">
|
||||
Go to the **[AWS Cognito Console](https://console.aws.amazon.com/cognito/)** and ensure you're in your desired AWS region.
|
||||
|
||||
Select **"User pools"** from the side navigation (click on the hamburger icon at the top left in case you don't see any), and click **"Create user pool"** to create a new user pool.
|
||||
</Step>
|
||||
|
||||
<Step title="Define Your Application">
|
||||
AWS Cognito now provides a streamlined setup experience:
|
||||
|
||||
1. **Application type**: Select **"Traditional web application"** (this is the correct choice for FastMCP server-side authentication)
|
||||
2. **Name your application**: Enter a descriptive name (e.g., `FastMCP Server`)
|
||||
|
||||
The traditional web application type automatically configures:
|
||||
- Server-side authentication with client secrets
|
||||
- Authorization code grant flow
|
||||
- Appropriate security settings for confidential clients
|
||||
|
||||
<Info>
|
||||
Choose "Traditional web application" rather than SPA, Mobile app, or Machine-to-machine options. This ensures proper OAuth 2.0 configuration for FastMCP.
|
||||
</Info>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Options">
|
||||
AWS will guide you through configuration options:
|
||||
|
||||
- **Sign-in identifiers**: Choose how users will sign in (email, username, or phone)
|
||||
- **Required attributes**: Select any additional user information you need
|
||||
- **Return URL**: Add your callback URL (e.g., `http://localhost:8000/auth/callback` for development)
|
||||
|
||||
<Tip>
|
||||
The simplified interface handles most OAuth security settings automatically based on your application type selection.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Review and Create">
|
||||
Review your configuration and click **"Create user pool"**.
|
||||
|
||||
After creation, you'll see your user pool details. Save these important values:
|
||||
- **User pool ID** (format: `eu-central-1_XXXXXXXXX`)
|
||||
- **Client ID** (found under → "Applications" → "App clients" in the side navigation → \<Your application name, e.g., `FastMCP Server`\> → "App client information")
|
||||
- **Client Secret** (found under → "Applications" → "App clients" in the side navigation → \<Your application name, e.g., `FastMCP Server`\> → "App client information")
|
||||
|
||||
<Tip>
|
||||
The user pool ID and app client credentials are all you need for FastMCP configuration.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OAuth Settings">
|
||||
Under "Login pages" in your app client's settings, you can double check and adjust the OAuth configuration:
|
||||
|
||||
- **Allowed callback URLs**: Add your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
|
||||
- **Allowed sign-out URLs**: Optional, for logout functionality
|
||||
- **OAuth 2.0 grant types**: Ensure "Authorization code grant" is selected
|
||||
- **OpenID Connect scopes**: Select scopes your application needs (e.g., `openid`, `email`, `profile`)
|
||||
|
||||
<Tip>
|
||||
For local development, you can use `http://localhost` URLs. For production, you must use HTTPS.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Resource Server">
|
||||
AWS Cognito requires a resource server entry to support OAuth with protected resources. Without this, token exchange will fail with an `invalid_grant` error.
|
||||
|
||||
Navigate to **"Branding" → "Domain"** in the side navigation, then:
|
||||
|
||||
1. Click **"Create resource server"**
|
||||
2. **Resource server name**: Enter a descriptive name (e.g., `My MCP Server`)
|
||||
3. **Resource server identifier**: Enter your MCP endpoint URL exactly as it will be accessed (e.g., `http://localhost:8000/mcp` for development, or `https://your-server.com/mcp` for production)
|
||||
4. Click **"Create resource server"**
|
||||
|
||||
<Warning>
|
||||
The resource server identifier must exactly match your `base_url + mcp_path`. For the default configuration with `base_url="http://localhost:8000"` and `path="/mcp"`, use `http://localhost:8000/mcp`.
|
||||
</Warning>
|
||||
</Step>
|
||||
|
||||
<Step title="Save Your Credentials">
|
||||
After setup, you'll have:
|
||||
|
||||
- **User Pool ID**: Format like `eu-central-1_XXXXXXXXX`
|
||||
- **Client ID**: Your application's client identifier
|
||||
- **Client Secret**: Generated client secret (keep secure)
|
||||
- **AWS Region**: Where Your AWS Cognito user pool is located
|
||||
|
||||
<Tip>
|
||||
Store these credentials securely. Never commit them to version control. Use environment variables or AWS Secrets Manager in production.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server using the `AWSCognitoProvider`, which handles AWS Cognito's JWT tokens and user claims automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.aws import AWSCognitoProvider
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
# The AWSCognitoProvider handles JWT validation and user claims
|
||||
auth_provider = AWSCognitoProvider(
|
||||
user_pool_id="eu-central-1_XXXXXXXXX", # Your AWS Cognito user pool ID
|
||||
aws_region="eu-central-1", # AWS region (defaults to eu-central-1)
|
||||
client_id="your-app-client-id", # Your app client ID
|
||||
client_secret="your-app-client-secret", # Your app client Secret
|
||||
base_url="http://localhost:8000", # Must match your callback URL
|
||||
# redirect_path="/auth/callback" # Default value, customize if needed
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="AWS Cognito Secured App", auth=auth_provider)
|
||||
|
||||
# Add a protected tool to test authentication
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"username": token.claims.get("username"),
|
||||
"cognito:groups": token.claims.get("cognito:groups", []),
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Running the Server
|
||||
|
||||
Start your FastMCP server with HTTP transport to enable OAuth flows:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Your server is now running and protected by AWS Cognito OAuth authentication.
|
||||
|
||||
### Testing with a Client
|
||||
|
||||
Create a test client that authenticates with Your AWS Cognito-protected server:
|
||||
|
||||
```python test_client.py
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# The client will automatically handle AWS Cognito OAuth
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
# First-time connection will open AWS Cognito login in your browser
|
||||
print("✓ Authenticated with AWS Cognito!")
|
||||
|
||||
# Test the protected tool
|
||||
print("Calling protected tool: get_access_token_claims")
|
||||
result = await client.call_tool("get_access_token_claims")
|
||||
user_data = result.data
|
||||
print("Available access token claims:")
|
||||
print(f"- sub: {user_data.get('sub', 'N/A')}")
|
||||
print(f"- username: {user_data.get('username', 'N/A')}")
|
||||
print(f"- cognito:groups: {user_data.get('cognito:groups', [])}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
When you run the client for the first time:
|
||||
1. Your browser will open to AWS Cognito's hosted UI login page
|
||||
2. After you sign in (or sign up), you'll be redirected back to your MCP server
|
||||
3. The client receives the JWT token and can make authenticated requests
|
||||
|
||||
<Info>
|
||||
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
|
||||
</Info>
|
||||
|
||||
## Production Configuration
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`:
|
||||
|
||||
```python server.py
|
||||
import os
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.aws import AWSCognitoProvider
|
||||
from key_value.aio.stores.redis import RedisStore
|
||||
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
# Production setup with encrypted persistent token storage
|
||||
auth_provider = AWSCognitoProvider(
|
||||
user_pool_id="eu-central-1_XXXXXXXXX",
|
||||
aws_region="eu-central-1",
|
||||
client_id="your-app-client-id",
|
||||
client_secret="your-app-client-secret",
|
||||
base_url="https://your-production-domain.com",
|
||||
|
||||
# Production token management
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
client_storage=FernetEncryptionWrapper(
|
||||
key_value=RedisStore(
|
||||
host=os.environ["REDIS_HOST"],
|
||||
port=int(os.environ["REDIS_PORT"])
|
||||
),
|
||||
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
|
||||
)
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Production AWS Cognito App", auth=auth_provider)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
|
||||
|
||||
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
|
||||
</Note>
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the AWS Cognito provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.aws.AWSCognitoProvider` to use AWS Cognito authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### AWS Cognito-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the AWS Cognito provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID" required>
|
||||
Your AWS Cognito user pool ID (e.g., `eu-central-1_XXXXXXXXX`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION" default="eu-central-1">
|
||||
AWS region where your AWS Cognito user pool is located
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID" required>
|
||||
Your AWS Cognito app client ID
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET" required>
|
||||
Your AWS Cognito app client secret
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL" default="http://localhost:8000">
|
||||
Public URL where OAuth endpoints will be accessible (includes any mount path)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_ISSUER_URL" default="Uses BASE_URL">
|
||||
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_REDIRECT_PATH" default="/auth/callback">
|
||||
One of the redirect paths configured in your AWS Cognito app client
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES" default='["openid"]'>
|
||||
Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid","email","profile"]`)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
# Use the AWS Cognito provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.aws.AWSCognitoProvider
|
||||
|
||||
# AWS Cognito credentials
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID=eu-central-1_XXXXXXXXX
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION=eu-central-1
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID=your-app-client-id
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET=your-app-client-secret
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES=openid,email,profile
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="AWS Cognito Secured App")
|
||||
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"username": token.claims.get("username"),
|
||||
"cognito:groups": token.claims.get("cognito:groups", []),
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### JWT Token Validation
|
||||
|
||||
The AWS Cognito provider includes robust JWT token validation:
|
||||
|
||||
- **Signature Verification**: Validates tokens against AWS Cognito's public keys (JWKS)
|
||||
- **Expiration Checking**: Automatically rejects expired tokens
|
||||
- **Issuer Validation**: Ensures tokens come from your specific AWS Cognito user pool
|
||||
- **Scope Enforcement**: Verifies required OAuth scopes are present
|
||||
|
||||
### User Claims and Groups
|
||||
|
||||
Access rich user information from AWS Cognito JWT tokens:
|
||||
|
||||
```python
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
@mcp.tool
|
||||
async def admin_only_tool() -> str:
|
||||
"""A tool only available to admin users."""
|
||||
token = get_access_token()
|
||||
user_groups = token.claims.get("cognito:groups", [])
|
||||
|
||||
if "admin" not in user_groups:
|
||||
raise ValueError("This tool requires admin access")
|
||||
|
||||
return "Admin access granted!"
|
||||
```
|
||||
|
||||
### Enterprise Integration
|
||||
|
||||
Perfect for enterprise environments with:
|
||||
|
||||
- **Single Sign-On (SSO)**: Integrate with corporate identity providers
|
||||
- **Multi-Factor Authentication (MFA)**: Leverage AWS Cognito's built-in MFA
|
||||
- **User Groups**: Role-based access control through AWS Cognito groups
|
||||
- **Custom Attributes**: Access custom user attributes defined in your AWS Cognito user pool
|
||||
- **Compliance**: Meet enterprise security and compliance requirements
|
||||
391
docs/v2/integrations/azure.mdx
Normal file
391
docs/v2/integrations/azure.mdx
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
---
|
||||
title: Azure (Microsoft Entra ID) OAuth 🤝 FastMCP
|
||||
sidebarTitle: Azure (Entra ID)
|
||||
description: Secure your FastMCP server with Azure/Microsoft Entra OAuth
|
||||
icon: microsoft
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **Azure OAuth** (Microsoft Entra ID). Since Azure doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Azure's traditional OAuth with MCP's authentication requirements. FastMCP validates Azure JWTs against your application's client_id.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. An **[Azure Account](https://portal.azure.com/)** with access to create App registrations
|
||||
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
|
||||
3. Your Azure tenant ID (found in Azure Portal under Microsoft Entra ID)
|
||||
|
||||
### Step 1: Create an Azure App Registration
|
||||
|
||||
Create an App registration in Azure Portal to get the credentials needed for authentication:
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App registrations">
|
||||
Go to the [Azure Portal](https://portal.azure.com) and navigate to **Microsoft Entra ID → App registrations**.
|
||||
|
||||
Click **"New registration"** to create a new application.
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Your Application">
|
||||
Fill in the application details:
|
||||
|
||||
- **Name**: Choose a name users will recognize (e.g., "My FastMCP Server")
|
||||
- **Supported account types**: Choose based on your needs:
|
||||
- **Single tenant**: Only users in your organization
|
||||
- **Multitenant**: Users in any Microsoft Entra directory
|
||||
- **Multitenant + personal accounts**: Any Microsoft account
|
||||
- **Redirect URI**: Select "Web" and enter your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
|
||||
|
||||
<Warning>
|
||||
The redirect URI must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. For local development, Azure allows `http://localhost` URLs. For production, you must use HTTPS.
|
||||
</Warning>
|
||||
|
||||
<Tip>
|
||||
If you want to use a custom callback path (e.g., `/auth/azure/callback`), make sure to set the same path in both your Azure App registration and the `redirect_path` parameter when configuring the AzureProvider.
|
||||
</Tip>
|
||||
|
||||
- **Expose an API**: Configure your Application ID URI and define scopes
|
||||
- Go to **Expose an API** in the App registration sidebar.
|
||||
- Click **Set** next to "Application ID URI" and choose one of:
|
||||
- Keep the default `api://{client_id}`
|
||||
- Set a custom value, following the supported formats (see [Identifier URI restrictions](https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions))
|
||||
- Click **Add a scope** and create a scope your app will require, for example:
|
||||
- Scope name: `read` (or `write`, etc.)
|
||||
- Admin consent display name/description: as appropriate for your org
|
||||
- Who can consent: as needed (Admins only or Admins and users)
|
||||
|
||||
- **Configure Access Token Version**: Ensure your app uses access token v2
|
||||
- Go to **Manifest** in the App registration sidebar.
|
||||
- Find the `requestedAccessTokenVersion` property and set it to `2`:
|
||||
```json
|
||||
"api": {
|
||||
"requestedAccessTokenVersion": 2
|
||||
}
|
||||
```
|
||||
- Click **Save** at the top of the manifest editor.
|
||||
|
||||
<Warning>
|
||||
Access token v2 is required for FastMCP's Azure integration to work correctly. If this is not set, you may encounter authentication errors.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
In FastMCP's `AzureProvider`, set `identifier_uri` to your Application ID URI (optional; defaults to `api://{client_id}`) and set `required_scopes` to the unprefixed scope names (e.g., `read`, `write`). During authorization, FastMCP automatically prefixes scopes with your `identifier_uri`.
|
||||
</Note>
|
||||
|
||||
|
||||
</Step>
|
||||
|
||||
|
||||
<Step title="Create Client Secret">
|
||||
After registration, navigate to **Certificates & secrets** in your app's settings.
|
||||
|
||||
- Click **"New client secret"**
|
||||
- Add a description (e.g., "FastMCP Server")
|
||||
- Choose an expiration period
|
||||
- Click **"Add"**
|
||||
|
||||
<Warning>
|
||||
Copy the secret value immediately - it won't be shown again! You'll need to create a new secret if you lose it.
|
||||
</Warning>
|
||||
</Step>
|
||||
|
||||
<Step title="Note Your Credentials">
|
||||
From the **Overview** page of your app registration, note:
|
||||
|
||||
- **Application (client) ID**: A UUID like `835f09b6-0f0f-40cc-85cb-f32c5829a149`
|
||||
- **Directory (tenant) ID**: A UUID like `08541b6e-646d-43de-a0eb-834e6713d6d5`
|
||||
- **Client Secret**: The value you copied in the previous step
|
||||
|
||||
<Tip>
|
||||
Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server using the `AzureProvider`, which handles Azure's OAuth flow automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.azure import AzureProvider
|
||||
|
||||
# The AzureProvider handles Azure's token format and validation
|
||||
auth_provider = AzureProvider(
|
||||
client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149", # Your Azure App Client ID
|
||||
client_secret="your-client-secret", # Your Azure App Client Secret
|
||||
tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", # Your Azure Tenant ID (REQUIRED)
|
||||
base_url="http://localhost:8000", # Must match your App registration
|
||||
required_scopes=["your-scope"], # At least one scope REQUIRED - name of scope from your App
|
||||
# identifier_uri defaults to api://{client_id}
|
||||
# identifier_uri="api://your-api-id",
|
||||
# Optional: request additional upstream scopes in the authorize request
|
||||
# additional_authorize_scopes=["User.Read", "offline_access", "openid", "email"],
|
||||
# redirect_path="/auth/callback" # Default value, customize if needed
|
||||
# base_authority="login.microsoftonline.us" # For Azure Government (default: login.microsoftonline.com)
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Azure Secured App", auth=auth_provider)
|
||||
|
||||
# Add a protected tool to test authentication
|
||||
@mcp.tool
|
||||
async def get_user_info() -> dict:
|
||||
"""Returns information about the authenticated Azure user."""
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
token = get_access_token()
|
||||
# The AzureProvider stores user data in token claims
|
||||
return {
|
||||
"azure_id": token.claims.get("sub"),
|
||||
"email": token.claims.get("email"),
|
||||
"name": token.claims.get("name"),
|
||||
"job_title": token.claims.get("job_title"),
|
||||
"office_location": token.claims.get("office_location")
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Important**: The `tenant_id` parameter is **REQUIRED**. Azure no longer supports using "common" for new applications due to security requirements. You must use one of:
|
||||
|
||||
- **Your specific tenant ID**: Found in Azure Portal (e.g., `08541b6e-646d-43de-a0eb-834e6713d6d5`)
|
||||
- **"organizations"**: For work and school accounts only
|
||||
- **"consumers"**: For personal Microsoft accounts only
|
||||
|
||||
Using your specific tenant ID is recommended for better security and control.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
**Important**: The `required_scopes` parameter is **REQUIRED** and must include at least one scope. Azure's OAuth API requires the `scope` parameter in all authorization requests - you cannot authenticate without specifying at least one scope. Use the unprefixed scope names from your Azure App registration (e.g., `["read", "write"]`). These scopes must be created under **Expose an API** in your App registration.
|
||||
</Note>
|
||||
|
||||
### Scope Handling
|
||||
|
||||
FastMCP automatically prefixes `required_scopes` with your `identifier_uri` (e.g., `api://your-client-id`) since these are your custom API scopes. Scopes in `additional_authorize_scopes` are sent as-is since they target external resources like Microsoft Graph.
|
||||
|
||||
**`required_scopes`** — Your custom API scopes, defined in Azure "Expose an API":
|
||||
|
||||
| You write | Sent to Azure | Validated on tokens |
|
||||
|-----------|---------------|---------------------|
|
||||
| `mcp-read` | `api://xxx/mcp-read` | ✓ |
|
||||
| `my.scope` | `api://xxx/my.scope` | ✓ |
|
||||
| `openid` | `openid` | ✗ (OIDC scope) |
|
||||
| `api://xxx/read` | `api://xxx/read` | ✓ |
|
||||
|
||||
**`additional_authorize_scopes`** — External scopes (e.g., Microsoft Graph) for server-side use:
|
||||
|
||||
| You write | Sent to Azure | Validated on tokens |
|
||||
|-----------|---------------|---------------------|
|
||||
| `User.Read` | `User.Read` | ✗ |
|
||||
| `Mail.Send` | `Mail.Send` | ✗ |
|
||||
|
||||
<Info>
|
||||
**Why aren't `additional_authorize_scopes` validated?** Azure issues separate tokens per resource. The access token FastMCP receives is for *your API*—Graph scopes aren't in its `scp` claim. To call Graph APIs, your server uses the upstream Azure token in an on-behalf-of (OBO) flow.
|
||||
</Info>
|
||||
|
||||
<Note>
|
||||
OIDC scopes (`openid`, `profile`, `email`, `offline_access`) are never prefixed and excluded from validation because Azure doesn't include them in access token `scp` claims.
|
||||
</Note>
|
||||
|
||||
## Testing
|
||||
|
||||
### Running the Server
|
||||
|
||||
Start your FastMCP server with HTTP transport to enable OAuth flows:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Your server is now running and protected by Azure OAuth authentication.
|
||||
|
||||
### Testing with a Client
|
||||
|
||||
Create a test client that authenticates with your Azure-protected server:
|
||||
|
||||
```python test_client.py
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# The client will automatically handle Azure OAuth
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
# First-time connection will open Azure login in your browser
|
||||
print("✓ Authenticated with Azure!")
|
||||
|
||||
# Test the protected tool
|
||||
result = await client.call_tool("get_user_info")
|
||||
print(f"Azure user: {result['email']}")
|
||||
print(f"Name: {result['name']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
When you run the client for the first time:
|
||||
1. Your browser will open to Microsoft's authorization page
|
||||
2. Sign in with your Microsoft account (work, school, or personal based on your tenant configuration)
|
||||
3. Grant the requested permissions
|
||||
4. After authorization, you'll be redirected back
|
||||
5. The client receives the token and can make authenticated requests
|
||||
|
||||
<Info>
|
||||
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
|
||||
</Info>
|
||||
|
||||
## Production Configuration
|
||||
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`:
|
||||
|
||||
```python server.py
|
||||
import os
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.azure import AzureProvider
|
||||
from key_value.aio.stores.redis import RedisStore
|
||||
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
# Production setup with encrypted persistent token storage
|
||||
auth_provider = AzureProvider(
|
||||
client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149",
|
||||
client_secret="your-client-secret",
|
||||
tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5",
|
||||
base_url="https://your-production-domain.com",
|
||||
required_scopes=["your-scope"],
|
||||
|
||||
# Production token management
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
client_storage=FernetEncryptionWrapper(
|
||||
key_value=RedisStore(
|
||||
host=os.environ["REDIS_HOST"],
|
||||
port=int(os.environ["REDIS_PORT"])
|
||||
),
|
||||
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
|
||||
)
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Production Azure App", auth=auth_provider)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
|
||||
|
||||
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
|
||||
</Note>
|
||||
|
||||
## Environment Variables
|
||||
|
||||
<VersionBadge version="2.12.1" />
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the Azure provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.azure.AzureProvider` to use Azure authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Azure-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the Azure provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID" required>
|
||||
Your Azure App registration Client ID (e.g., `835f09b6-0f0f-40cc-85cb-f32c5829a149`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET" required>
|
||||
Your Azure App registration Client Secret
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_TENANT_ID" required>
|
||||
Your Azure tenant ID (specific ID, "organizations", or "consumers")
|
||||
|
||||
<Note>
|
||||
This is **REQUIRED**. Find your tenant ID in Azure Portal under Microsoft Entra ID → Overview.
|
||||
</Note>
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_BASE_URL" default="http://localhost:8000">
|
||||
Public URL where OAuth endpoints will be accessible (includes any mount path)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_ISSUER_URL" default="Uses BASE_URL">
|
||||
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REDIRECT_PATH" default="/auth/callback">
|
||||
Redirect path configured in your Azure App registration
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES" required>
|
||||
Comma-, space-, or JSON-separated list of required scopes for your API (at least one scope required). These are validated on tokens and used as defaults if the client does not request specific scopes. Use unprefixed scope names from your Azure App registration (e.g., `read,write`).
|
||||
|
||||
You can include standard OIDC scopes (`openid`, `profile`, `email`, `offline_access`) in `required_scopes`. FastMCP automatically handles them correctly: they're sent to Azure unprefixed and excluded from token validation (since Azure doesn't include OIDC scopes in access token `scp` claims).
|
||||
|
||||
<Note>
|
||||
Azure's OAuth API requires the `scope` parameter - you must provide at least one scope.
|
||||
</Note>
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES" default="">
|
||||
Comma-, space-, or JSON-separated list of additional scopes to include in the authorization request without prefixing. Use this to request upstream scopes such as Microsoft Graph permissions. These are not used for token validation.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI" default="api://{client_id}">
|
||||
Application ID URI used to prefix scopes during authorization.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_BASE_AUTHORITY" default="login.microsoftonline.com">
|
||||
Azure authority base URL. Override this to use Azure Government:
|
||||
|
||||
- `login.microsoftonline.com` - Azure Public Cloud (default)
|
||||
- `login.microsoftonline.us` - Azure Government
|
||||
|
||||
This setting affects all Azure OAuth endpoints (authorization, token, issuer, JWKS).
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
# Use the Azure provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.azure.AzureProvider
|
||||
|
||||
# Azure OAuth credentials
|
||||
FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID=835f09b6-0f0f-40cc-85cb-f32c5829a149
|
||||
FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET=your-client-secret-here
|
||||
FASTMCP_SERVER_AUTH_AZURE_TENANT_ID=08541b6e-646d-43de-a0eb-834e6713d6d5
|
||||
FASTMCP_SERVER_AUTH_AZURE_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES=read,write
|
||||
# Optional custom API configuration
|
||||
# FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI=api://your-api-id
|
||||
# Request additional upstream scopes (optional)
|
||||
# FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES=User.Read,Mail.Read
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="Azure Secured App")
|
||||
|
||||
@mcp.tool
|
||||
async def protected_tool(query: str) -> str:
|
||||
"""A tool that requires Azure authentication to access."""
|
||||
# Your tool implementation here
|
||||
return f"Processing authenticated request: {query}"
|
||||
```
|
||||
|
||||
157
docs/v2/integrations/chatgpt.mdx
Normal file
157
docs/v2/integrations/chatgpt.mdx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
title: ChatGPT 🤝 FastMCP
|
||||
sidebarTitle: ChatGPT
|
||||
description: Connect FastMCP servers to ChatGPT in Chat and Deep Research modes
|
||||
icon: message-smile
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
ChatGPT supports MCP servers through remote HTTP connections in two modes: **Chat mode** for interactive conversations and **Deep Research mode** for comprehensive information retrieval.
|
||||
|
||||
<Tip>
|
||||
**Developer Mode Required for Chat Mode**: To use MCP servers in regular ChatGPT conversations, you must first enable Developer Mode in your ChatGPT settings. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Learn more from their [MCP documentation](https://platform.openai.com/docs/mcp) and [Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode).
|
||||
</Note>
|
||||
|
||||
## Build a Server
|
||||
|
||||
First, let's create a simple FastMCP server:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
import random
|
||||
|
||||
mcp = FastMCP("Demo Server")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(sides: int = 6) -> int:
|
||||
"""Roll a dice with the specified number of sides."""
|
||||
return random.randint(1, sides)
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
### Deploy Your Server
|
||||
|
||||
Your server must be accessible from the internet. For development, use `ngrok`:
|
||||
|
||||
<CodeGroup>
|
||||
```bash Terminal 1
|
||||
python server.py
|
||||
```
|
||||
|
||||
```bash Terminal 2
|
||||
ngrok http 8000
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Note your public URL (e.g., `https://abc123.ngrok.io`) for the next steps.
|
||||
|
||||
## Chat Mode
|
||||
|
||||
Chat mode lets you use MCP tools directly in ChatGPT conversations. See [OpenAI's Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode) for the latest requirements.
|
||||
|
||||
### Add to ChatGPT
|
||||
|
||||
#### 1. Enable Developer Mode
|
||||
|
||||
1. Open ChatGPT and go to **Settings** → **Connectors**
|
||||
2. Under **Advanced**, toggle **Developer Mode** to enabled
|
||||
|
||||
#### 2. Create Connector
|
||||
|
||||
1. In **Settings** → **Connectors**, click **Create**
|
||||
2. Enter:
|
||||
- **Name**: Your server name
|
||||
- **Server URL**: `https://your-server.ngrok.io/mcp/`
|
||||
3. Check **I trust this provider**
|
||||
4. Add authentication if needed
|
||||
5. Click **Create**
|
||||
|
||||
<Note>
|
||||
**Without Developer Mode**: If you don't have search/fetch tools, ChatGPT will reject the server. With Developer Mode enabled, you don't need search/fetch tools for Chat mode.
|
||||
</Note>
|
||||
|
||||
#### 3. Use in Chat
|
||||
|
||||
1. Start a new chat
|
||||
2. Click the **+** button → **More** → **Developer Mode**
|
||||
3. **Enable your MCP server connector** (required - the connector must be explicitly added to each chat)
|
||||
4. Now you can use your tools:
|
||||
|
||||
Example usage:
|
||||
- "Roll a 20-sided dice"
|
||||
- "Roll dice" (uses default 6 sides)
|
||||
|
||||
<Tip>
|
||||
The connector must be explicitly enabled in each chat session through Developer Mode. Once added, it remains active for the entire conversation.
|
||||
</Tip>
|
||||
|
||||
### Skip Confirmations
|
||||
|
||||
Use `annotations={"readOnlyHint": True}` to skip confirmation prompts for read-only tools:
|
||||
|
||||
```python
|
||||
@mcp.tool(annotations={"readOnlyHint": True})
|
||||
def get_status() -> str:
|
||||
"""Check system status."""
|
||||
return "All systems operational"
|
||||
|
||||
@mcp.tool() # No annotation - ChatGPT may ask for confirmation
|
||||
def delete_item(id: str) -> str:
|
||||
"""Delete an item."""
|
||||
return f"Deleted {id}"
|
||||
```
|
||||
|
||||
## Deep Research Mode
|
||||
|
||||
Deep Research mode provides systematic information retrieval with citations. See [OpenAI's MCP documentation](https://platform.openai.com/docs/mcp) for the latest Deep Research specifications.
|
||||
|
||||
<Warning>
|
||||
**Search and Fetch Required**: Without Developer Mode, ChatGPT will reject any server that doesn't have both `search` and `fetch` tools. Even in Developer Mode, Deep Research only uses these two tools.
|
||||
</Warning>
|
||||
|
||||
### Tool Implementation
|
||||
|
||||
Deep Research tools must follow this pattern:
|
||||
|
||||
```python
|
||||
@mcp.tool()
|
||||
def search(query: str) -> dict:
|
||||
"""
|
||||
Search for records matching the query.
|
||||
Must return {"ids": [list of string IDs]}
|
||||
"""
|
||||
# Your search logic
|
||||
matching_ids = ["id1", "id2", "id3"]
|
||||
return {"ids": matching_ids}
|
||||
|
||||
@mcp.tool()
|
||||
def fetch(id: str) -> dict:
|
||||
"""
|
||||
Fetch a complete record by ID.
|
||||
Return the full record data for ChatGPT to analyze.
|
||||
"""
|
||||
# Your fetch logic
|
||||
return {
|
||||
"id": id,
|
||||
"title": "Record Title",
|
||||
"content": "Full record content...",
|
||||
"metadata": {"author": "Jane Doe", "date": "2024"}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Deep Research
|
||||
|
||||
1. Ensure your server is added to ChatGPT's connectors (same as Chat mode)
|
||||
2. Start a new chat
|
||||
3. Click **+** → **Deep Research**
|
||||
4. Select your MCP server as a source
|
||||
5. Ask research questions
|
||||
|
||||
ChatGPT will use your `search` and `fetch` tools to find and cite relevant information.
|
||||
|
||||
177
docs/v2/integrations/claude-code.mdx
Normal file
177
docs/v2/integrations/claude-code.mdx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
---
|
||||
title: Claude Code 🤝 FastMCP
|
||||
sidebarTitle: Claude Code
|
||||
description: Install and use FastMCP servers in Claude Code
|
||||
icon: message-smile
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
||||
|
||||
<LocalFocusTip />
|
||||
|
||||
Claude Code supports MCP servers through multiple transport methods including STDIO, SSE, and HTTP, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
||||
|
||||
## Requirements
|
||||
|
||||
This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly using Claude Code's built-in MCP management commands.
|
||||
|
||||
## Create a Server
|
||||
|
||||
The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.
|
||||
|
||||
```python server.py
|
||||
import random
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Dice Roller")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
## Install the Server
|
||||
|
||||
### FastMCP CLI
|
||||
<VersionBadge version="2.10.3" />
|
||||
|
||||
The easiest way to install a FastMCP server in Claude Code is using the `fastmcp install claude-code` command. This automatically handles the configuration, dependency management, and calls Claude Code's built-in MCP management system.
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py
|
||||
```
|
||||
|
||||
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
|
||||
|
||||
```bash
|
||||
# These are equivalent if your server object is named 'mcp'
|
||||
fastmcp install claude-code server.py
|
||||
fastmcp install claude-code server.py:mcp
|
||||
|
||||
# Use explicit object name if your server has a different name
|
||||
fastmcp install claude-code server.py:my_custom_server
|
||||
```
|
||||
|
||||
The command will automatically configure the server with Claude Code's `claude mcp add` command.
|
||||
|
||||
#### Dependencies
|
||||
|
||||
FastMCP provides flexible dependency management options for your Claude Code servers:
|
||||
|
||||
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --with pandas --with requests
|
||||
```
|
||||
|
||||
**Requirements file**: If you maintain a `requirements.txt` file with all your dependencies, use `--with-requirements` to install them:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --with-requirements requirements.txt
|
||||
```
|
||||
|
||||
**Editable packages**: For local packages under development, use `--with-editable` to install them in editable mode:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --with-editable ./my-local-package
|
||||
```
|
||||
|
||||
Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
||||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Python Version and Project Configuration
|
||||
|
||||
Control the Python environment for your server with these options:
|
||||
|
||||
**Python version**: Use `--python` to specify which Python version your server requires. This ensures compatibility when your server needs specific Python features:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --python 3.11
|
||||
```
|
||||
|
||||
**Project directory**: Use `--project` to run your server within a specific project context. This tells `uv` to use the project's configuration files and virtual environment:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --project /path/to/my-project
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
If your server needs environment variables (like API keys), you must include them:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --server-name "Weather Server" \
|
||||
--env API_KEY=your-api-key \
|
||||
--env DEBUG=true
|
||||
```
|
||||
|
||||
Or load them from a `.env` file:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-code server.py --server-name "Weather Server" --env-file .env
|
||||
```
|
||||
|
||||
<Warning>
|
||||
**Claude Code must be installed**. The integration looks for the Claude Code CLI at the default installation location (`~/.claude/local/claude`) and uses the `claude mcp add` command to register servers.
|
||||
</Warning>
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
For more control over the configuration, you can manually use Claude Code's built-in MCP management commands. This gives you direct control over how your server is launched:
|
||||
|
||||
```bash
|
||||
# Add a server with custom configuration
|
||||
claude mcp add dice-roller -- uv run --with fastmcp fastmcp run server.py
|
||||
|
||||
# Add with environment variables
|
||||
claude mcp add weather-server -e API_KEY=secret -e DEBUG=true -- uv run --with fastmcp fastmcp run server.py
|
||||
|
||||
# Add with specific scope (local, user, or project)
|
||||
claude mcp add my-server --scope user -- uv run --with fastmcp fastmcp run server.py
|
||||
```
|
||||
|
||||
You can also manually specify Python versions and project directories in your Claude Code commands:
|
||||
|
||||
```bash
|
||||
# With specific Python version
|
||||
claude mcp add ml-server -- uv run --python 3.11 --with fastmcp fastmcp run server.py
|
||||
|
||||
# Within a project directory
|
||||
claude mcp add project-server -- uv run --project /path/to/project --with fastmcp fastmcp run server.py
|
||||
```
|
||||
|
||||
## Using the Server
|
||||
|
||||
Once your server is installed, you can start using your FastMCP server with Claude Code.
|
||||
|
||||
Try asking Claude something like:
|
||||
|
||||
> "Roll some dice for me"
|
||||
|
||||
Claude will automatically detect your `roll_dice` tool and use it to fulfill your request, returning something like:
|
||||
|
||||
> I'll roll some dice for you! Here are your results: [4, 2, 6]
|
||||
>
|
||||
> You rolled three dice and got a 4, a 2, and a 6!
|
||||
|
||||
Claude Code can now access all the tools, resources, and prompts you've defined in your FastMCP server.
|
||||
|
||||
If your server provides resources, you can reference them with `@` mentions using the format `@server:protocol://resource/path`. If your server provides prompts, you can use them as slash commands with `/mcp__servername__promptname`.
|
||||
298
docs/v2/integrations/claude-desktop.mdx
Normal file
298
docs/v2/integrations/claude-desktop.mdx
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
---
|
||||
title: Claude Desktop 🤝 FastMCP
|
||||
sidebarTitle: Claude Desktop
|
||||
description: Connect FastMCP servers to Claude Desktop
|
||||
icon: message-smile
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
||||
|
||||
<LocalFocusTip />
|
||||
|
||||
Claude Desktop supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
||||
|
||||
<Note>
|
||||
Remote MCP server support is currently in beta and available for users on Claude Pro, Max, Team, and Enterprise plans (as of June 2025). Most users will still need to use local STDIO connections.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
This guide focuses specifically on using FastMCP servers with Claude Desktop. For general Claude Desktop MCP setup and official examples, see the [official Claude Desktop quickstart guide](https://modelcontextprotocol.io/quickstart/user).
|
||||
</Note>
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
Claude Desktop traditionally requires MCP servers to run locally using STDIO transport, where your server communicates with Claude through standard input/output rather than HTTP. However, users on certain plans now have access to remote server support as well.
|
||||
|
||||
<Tip>
|
||||
If you don't have access to remote server support or need to connect to remote servers, you can create a **proxy server** that runs locally via STDIO and forwards requests to remote HTTP servers. See the [Proxy Servers](#proxy-servers) section below.
|
||||
</Tip>
|
||||
|
||||
## Create a Server
|
||||
|
||||
The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.
|
||||
|
||||
```python server.py
|
||||
import random
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Dice Roller")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
## Install the Server
|
||||
|
||||
### FastMCP CLI
|
||||
<VersionBadge version="2.10.3" />
|
||||
|
||||
The easiest way to install a FastMCP server in Claude Desktop is using the `fastmcp install claude-desktop` command. This automatically handles the configuration and dependency management.
|
||||
|
||||
<Tip>
|
||||
Prior to version 2.10.3, Claude Desktop could be managed by running `fastmcp install <path>` without specifying the client.
|
||||
</Tip>
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py
|
||||
```
|
||||
|
||||
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
|
||||
|
||||
```bash
|
||||
# These are equivalent if your server object is named 'mcp'
|
||||
fastmcp install claude-desktop server.py
|
||||
fastmcp install claude-desktop server.py:mcp
|
||||
|
||||
# Use explicit object name if your server has a different name
|
||||
fastmcp install claude-desktop server.py:my_custom_server
|
||||
```
|
||||
|
||||
After installation, restart Claude Desktop completely. You should see a hammer icon (🔨) in the bottom left of the input box, indicating that MCP tools are available.
|
||||
|
||||
#### Dependencies
|
||||
|
||||
FastMCP provides several ways to manage your server's dependencies when installing in Claude Desktop:
|
||||
|
||||
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --with pandas --with requests
|
||||
```
|
||||
|
||||
**Requirements file**: If you have a `requirements.txt` file listing all your dependencies, use `--with-requirements` to install them all at once:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --with-requirements requirements.txt
|
||||
```
|
||||
|
||||
**Editable packages**: For local packages in development, use `--with-editable` to install them in editable mode:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --with-editable ./my-local-package
|
||||
```
|
||||
|
||||
Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
||||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Python Version and Project Directory
|
||||
|
||||
FastMCP allows you to control the Python environment for your server:
|
||||
|
||||
**Python version**: Use `--python` to specify which Python version your server should run with. This is particularly useful when your server requires a specific Python version:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --python 3.11
|
||||
```
|
||||
|
||||
**Project directory**: Use `--project` to run your server within a specific project directory. This ensures that `uv` will discover all `pyproject.toml`, `uv.toml`, and `.python-version` files from that project:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --project /path/to/my-project
|
||||
```
|
||||
|
||||
When you specify a project directory, all relative paths in your server will be resolved from that directory, and the project's virtual environment will be used.
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
<Warning>
|
||||
Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
|
||||
</Warning>
|
||||
|
||||
If your server needs environment variables (like API keys), you must include them:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --server-name "Weather Server" \
|
||||
--env API_KEY=your-api-key \
|
||||
--env DEBUG=true
|
||||
```
|
||||
|
||||
Or load them from a `.env` file:
|
||||
|
||||
```bash
|
||||
fastmcp install claude-desktop server.py --server-name "Weather Server" --env-file .env
|
||||
```
|
||||
<Warning>
|
||||
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
|
||||
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
|
||||
</Warning>
|
||||
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
For more control over the configuration, you can manually edit Claude Desktop's configuration file. You can open the configuration file from Claude's developer settings, or find it in the following locations:
|
||||
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
The configuration file is a JSON object with a `mcpServers` key, which contains the configuration for each MCP server.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"dice-roller": {
|
||||
"command": "python",
|
||||
"args": ["path/to/your/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After updating the configuration file, restart Claude Desktop completely. Look for the hammer icon (🔨) to confirm your server is loaded.
|
||||
|
||||
#### Dependencies
|
||||
|
||||
If your server has dependencies, you can use `uv` or another package manager to set up the environment.
|
||||
|
||||
|
||||
When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration uses `uv run` to create an isolated environment with your specified packages:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"dice-roller": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--with", "fastmcp",
|
||||
"--with", "pandas",
|
||||
"--with", "requests",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"path/to/your/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also manually specify Python versions and project directories in your configuration. Add `--python` to use a specific Python version, or `--project` to run within a project directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"dice-roller": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--python", "3.11",
|
||||
"--project", "/path/to/project",
|
||||
"--with", "fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"path/to/your/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The order of arguments matters: Python version and project settings come before package specifications, which come before the actual command to run.
|
||||
|
||||
<Warning>
|
||||
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
|
||||
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.
|
||||
</Warning>
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
You can also specify environment variables in the configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather-server": {
|
||||
"command": "python",
|
||||
"args": ["path/to/weather_server.py"],
|
||||
"env": {
|
||||
"API_KEY": "your-api-key",
|
||||
"DEBUG": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
<Warning>
|
||||
Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
|
||||
</Warning>
|
||||
|
||||
|
||||
## Remote Servers
|
||||
|
||||
|
||||
Users on Claude Pro, Max, Team, and Enterprise plans have first-class remote server support via integrations. For other users, or as an alternative approach, FastMCP can create a proxy server that forwards requests to a remote HTTP server. You can install the proxy server in Claude Desktop.
|
||||
|
||||
Create a proxy server that connects to a remote HTTP server:
|
||||
|
||||
```python proxy_server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Create a proxy to a remote server
|
||||
proxy = FastMCP.as_proxy(
|
||||
"https://example.com/mcp/sse",
|
||||
name="Remote Server Proxy"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
proxy.run() # Runs via STDIO for Claude Desktop
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
For authenticated remote servers, create an authenticated client following the guidance in the [client auth documentation](/clients/auth/bearer) and pass it to the proxy:
|
||||
|
||||
```python auth_proxy_server.py {7}
|
||||
from fastmcp import FastMCP, Client
|
||||
from fastmcp.client.auth import BearerAuth
|
||||
|
||||
# Create authenticated client
|
||||
client = Client(
|
||||
"https://api.example.com/mcp/sse",
|
||||
auth=BearerAuth(token="your-access-token")
|
||||
)
|
||||
|
||||
# Create proxy using the authenticated client
|
||||
proxy = FastMCP.as_proxy(client, name="Authenticated Proxy")
|
||||
|
||||
if __name__ == "__main__":
|
||||
proxy.run()
|
||||
```
|
||||
|
||||
284
docs/v2/integrations/cursor.mdx
Normal file
284
docs/v2/integrations/cursor.mdx
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
---
|
||||
title: Cursor 🤝 FastMCP
|
||||
sidebarTitle: Cursor
|
||||
description: Install and use FastMCP servers in Cursor
|
||||
icon: message-smile
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
||||
|
||||
<LocalFocusTip />
|
||||
|
||||
Cursor supports MCP servers through multiple transport methods including STDIO, SSE, and Streamable HTTP, allowing you to extend Cursor's AI assistant with custom tools, resources, and prompts from your FastMCP servers.
|
||||
|
||||
## Requirements
|
||||
|
||||
This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly in Cursor's settings.
|
||||
|
||||
## Create a Server
|
||||
|
||||
The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.
|
||||
|
||||
```python server.py
|
||||
import random
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Dice Roller")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
## Install the Server
|
||||
|
||||
### FastMCP CLI
|
||||
<VersionBadge version="2.10.3" />
|
||||
|
||||
The easiest way to install a FastMCP server in Cursor is using the `fastmcp install cursor` command. This automatically handles the configuration, dependency management, and opens Cursor with a deeplink to install the server.
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py
|
||||
```
|
||||
|
||||
#### Workspace Installation
|
||||
<VersionBadge version="2.12.0" />
|
||||
|
||||
By default, FastMCP installs servers globally for Cursor. You can also install servers to project-specific workspaces using the `--workspace` flag:
|
||||
|
||||
```bash
|
||||
# Install to current directory's .cursor/ folder
|
||||
fastmcp install cursor server.py --workspace .
|
||||
|
||||
# Install to specific workspace
|
||||
fastmcp install cursor server.py --workspace /path/to/project
|
||||
```
|
||||
|
||||
This creates a `.cursor/mcp.json` configuration file in the specified workspace directory, allowing different projects to have their own MCP server configurations.
|
||||
|
||||
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
|
||||
|
||||
```bash
|
||||
# These are equivalent if your server object is named 'mcp'
|
||||
fastmcp install cursor server.py
|
||||
fastmcp install cursor server.py:mcp
|
||||
|
||||
# Use explicit object name if your server has a different name
|
||||
fastmcp install cursor server.py:my_custom_server
|
||||
```
|
||||
|
||||
After running the command, Cursor will open automatically and prompt you to install the server. The command will be `uv`, which is expected as this is a Python STDIO server. Click "Install" to confirm:
|
||||
|
||||

|
||||
|
||||
#### Dependencies
|
||||
|
||||
FastMCP offers multiple ways to manage dependencies for your Cursor servers:
|
||||
|
||||
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --with pandas --with requests
|
||||
```
|
||||
|
||||
**Requirements file**: For projects with a `requirements.txt` file, use `--with-requirements` to install all dependencies at once:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --with-requirements requirements.txt
|
||||
```
|
||||
|
||||
**Editable packages**: When developing local packages, use `--with-editable` to install them in editable mode:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --with-editable ./my-local-package
|
||||
```
|
||||
|
||||
Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
||||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Python Version and Project Configuration
|
||||
|
||||
Control your server's Python environment with these options:
|
||||
|
||||
**Python version**: Use `--python` to specify which Python version your server should use. This is essential when your server requires specific Python features:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --python 3.11
|
||||
```
|
||||
|
||||
**Project directory**: Use `--project` to run your server within a specific project context. This ensures `uv` discovers all project configuration files and uses the correct virtual environment:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --project /path/to/my-project
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
<Warning>
|
||||
Cursor runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
|
||||
</Warning>
|
||||
|
||||
If your server needs environment variables (like API keys), you must include them:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --server-name "Weather Server" \
|
||||
--env API_KEY=your-api-key \
|
||||
--env DEBUG=true
|
||||
```
|
||||
|
||||
Or load them from a `.env` file:
|
||||
|
||||
```bash
|
||||
fastmcp install cursor server.py --server-name "Weather Server" --env-file .env
|
||||
```
|
||||
|
||||
<Warning>
|
||||
**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies.
|
||||
</Warning>
|
||||
|
||||
### Generate MCP JSON
|
||||
|
||||
<Note>
|
||||
**Use the first-class integration above for the best experience.** The MCP JSON generation is useful for advanced use cases, manual configuration, or integration with other tools.
|
||||
</Note>
|
||||
|
||||
You can generate MCP JSON configuration for manual use:
|
||||
|
||||
```bash
|
||||
# Generate configuration and output to stdout
|
||||
fastmcp install mcp-json server.py --server-name "Dice Roller" --with pandas
|
||||
|
||||
# Copy configuration to clipboard for easy pasting
|
||||
fastmcp install mcp-json server.py --server-name "Dice Roller" --copy
|
||||
```
|
||||
|
||||
This generates the standard `mcpServers` configuration format that can be used with any MCP-compatible client.
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
For more control over the configuration, you can manually edit Cursor's configuration file. The configuration file is located at:
|
||||
- **All platforms**: `~/.cursor/mcp.json`
|
||||
|
||||
The configuration file is a JSON object with a `mcpServers` key, which contains the configuration for each MCP server.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"dice-roller": {
|
||||
"command": "python",
|
||||
"args": ["path/to/your/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After updating the configuration file, your server should be available in Cursor.
|
||||
|
||||
#### Dependencies
|
||||
|
||||
If your server has dependencies, you can use `uv` or another package manager to set up the environment.
|
||||
|
||||
When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration should use `uv run` to create an isolated environment with your specified packages:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"dice-roller": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--with", "fastmcp",
|
||||
"--with", "pandas",
|
||||
"--with", "requests",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"path/to/your/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also manually specify Python versions and project directories in your configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"dice-roller": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--python", "3.11",
|
||||
"--project", "/path/to/project",
|
||||
"--with", "fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"path/to/your/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the order of arguments is important: Python version and project settings should come before package specifications.
|
||||
|
||||
<Warning>
|
||||
**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies.
|
||||
</Warning>
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
You can also specify environment variables in the configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather-server": {
|
||||
"command": "python",
|
||||
"args": ["path/to/weather_server.py"],
|
||||
"env": {
|
||||
"API_KEY": "your-api-key",
|
||||
"DEBUG": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Cursor runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs.
|
||||
</Warning>
|
||||
|
||||
## Using the Server
|
||||
|
||||
Once your server is installed, you can start using your FastMCP server with Cursor's AI assistant.
|
||||
|
||||
Try asking Cursor something like:
|
||||
|
||||
> "Roll some dice for me"
|
||||
|
||||
Cursor will automatically detect your `roll_dice` tool and use it to fulfill your request, returning something like:
|
||||
|
||||
> 🎲 Here are your dice rolls: 4, 6, 4
|
||||
>
|
||||
> You rolled 3 dice with a total of 14! The 6 was a nice high roll there!
|
||||
|
||||
The AI assistant can now access all the tools, resources, and prompts you've defined in your FastMCP server.
|
||||
146
docs/v2/integrations/descope.mdx
Normal file
146
docs/v2/integrations/descope.mdx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
title: Descope 🤝 FastMCP
|
||||
sidebarTitle: Descope
|
||||
description: Secure your FastMCP server with Descope
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.12.4" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using [**Descope**](https://www.descope.com), a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where Descope handles user login and your FastMCP server validates the tokens.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
|
||||
1. To [sign up](https://www.descope.com/sign-up) for a Free Forever Descope account
|
||||
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:3000`)
|
||||
|
||||
### Step 1: Configure Descope
|
||||
|
||||
<Steps>
|
||||
<Step title="Create an MCP Server">
|
||||
1. Go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console, and create a new MCP Server.
|
||||
2. Give the MCP server a name and description.
|
||||
3. Ensure that **Dynamic Client Registration (DCR)** is enabled. Then click **Create**.
|
||||
4. Once you've created the MCP Server, note your Well-Known URL.
|
||||
|
||||
|
||||
<Warning>
|
||||
DCR is required for FastMCP clients to automatically register with your authentication server.
|
||||
</Warning>
|
||||
</Step>
|
||||
|
||||
<Step title="Note Your Well-Known URL">
|
||||
Save your Well-Known URL from [MCP Server Settings](https://app.descope.com/mcp-servers):
|
||||
```
|
||||
Well-Known URL: https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: Environment Setup
|
||||
|
||||
Create a `.env` file with your Descope configuration:
|
||||
|
||||
```bash
|
||||
DESCOPE_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration # Your Descope Well-Known URL
|
||||
SERVER_URL=http://localhost:3000 # Your server's base URL
|
||||
```
|
||||
|
||||
### Step 3: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server file and use the DescopeProvider to handle all the OAuth integration automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.descope import DescopeProvider
|
||||
|
||||
# The DescopeProvider automatically discovers Descope endpoints
|
||||
# and configures JWT token validation
|
||||
auth_provider = DescopeProvider(
|
||||
config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL
|
||||
base_url=SERVER_URL, # Your server's public URL
|
||||
)
|
||||
|
||||
# Create FastMCP server with auth
|
||||
mcp = FastMCP(name="My Descope Protected Server", auth=auth_provider)
|
||||
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the environment variables with your actual values!), you can run the following command:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Now, you can use a FastMCP client to test that you can reach your server after authenticating:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the Descope provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.descope.DescopeProvider` to use
|
||||
Descope authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Descope-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the Descope provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL" required>
|
||||
Your Well-Known URL from the [Descope Console](https://app.descope.com/mcp-servers)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL" required>
|
||||
Public URL of your FastMCP server (e.g., `https://your-server.com` or
|
||||
`http://localhost:8000` for development)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
|
||||
```bash
|
||||
# Use the Descope provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.descope.DescopeProvider
|
||||
|
||||
# Descope configuration
|
||||
FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration
|
||||
FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL=https://your-server.com
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="My Descope Protected Server")
|
||||
```
|
||||
255
docs/v2/integrations/discord.mdx
Normal file
255
docs/v2/integrations/discord.mdx
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
---
|
||||
title: Discord OAuth 🤝 FastMCP
|
||||
sidebarTitle: Discord
|
||||
description: Secure your FastMCP server with Discord OAuth
|
||||
icon: discord
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.13.2" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **Discord OAuth**. Since Discord doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Discord's traditional OAuth with MCP's authentication requirements.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. A **[Discord Account](https://discord.com/)** with access to create applications
|
||||
2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
|
||||
|
||||
### Step 1: Create a Discord Application
|
||||
|
||||
Create an application in the Discord Developer Portal to get the credentials needed for authentication:
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to Discord Developer Portal">
|
||||
Go to the [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
|
||||
Click **"New Application"** and give it a name users will recognize (e.g., "My FastMCP Server").
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OAuth2 Settings">
|
||||
In the left sidebar, click **"OAuth2"**.
|
||||
|
||||
In the **Redirects** section, click **"Add Redirect"** and enter your callback URL:
|
||||
- For development: `http://localhost:8000/auth/callback`
|
||||
- For production: `https://your-domain.com/auth/callback`
|
||||
|
||||
<Warning>
|
||||
The redirect URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. Discord allows `http://localhost` URLs for development. For production, use HTTPS.
|
||||
</Warning>
|
||||
</Step>
|
||||
|
||||
<Step title="Save Your Credentials">
|
||||
On the same OAuth2 page, you'll find:
|
||||
|
||||
- **Client ID**: A numeric string like `12345`
|
||||
- **Client Secret**: Click "Reset Secret" to generate one
|
||||
|
||||
<Tip>
|
||||
Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server using the `DiscordProvider`, which handles Discord's OAuth flow automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.discord import DiscordProvider
|
||||
|
||||
auth_provider = DiscordProvider(
|
||||
client_id="12345", # Your Discord Application Client ID
|
||||
client_secret="your-client-secret", # Your Discord OAuth Client Secret
|
||||
base_url="http://localhost:8000", # Must match your OAuth configuration
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Discord Secured App", auth=auth_provider)
|
||||
|
||||
@mcp.tool
|
||||
async def get_user_info() -> dict:
|
||||
"""Returns information about the authenticated Discord user."""
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
token = get_access_token()
|
||||
return {
|
||||
"discord_id": token.claims.get("sub"),
|
||||
"username": token.claims.get("username"),
|
||||
"avatar": token.claims.get("avatar"),
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Running the Server
|
||||
|
||||
Start your FastMCP server with HTTP transport to enable OAuth flows:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Your server is now running and protected by Discord OAuth authentication.
|
||||
|
||||
### Testing with a Client
|
||||
|
||||
Create a test client that authenticates with your Discord-protected server:
|
||||
|
||||
```python test_client.py
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
print("✓ Authenticated with Discord!")
|
||||
|
||||
result = await client.call_tool("get_user_info")
|
||||
print(f"Discord user: {result['username']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
When you run the client for the first time:
|
||||
1. Your browser will open to Discord's authorization page
|
||||
2. Sign in with your Discord account and authorize the app
|
||||
3. After authorization, you'll be redirected back
|
||||
4. The client receives the token and can make authenticated requests
|
||||
|
||||
<Info>
|
||||
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
|
||||
</Info>
|
||||
|
||||
## Discord Scopes
|
||||
|
||||
Discord OAuth supports several scopes for accessing different types of user data:
|
||||
|
||||
| Scope | Description |
|
||||
|-------|-------------|
|
||||
| `identify` | Access username, avatar, and discriminator (default) |
|
||||
| `email` | Access the user's email address |
|
||||
| `guilds` | Access the user's list of servers |
|
||||
| `guilds.join` | Ability to add the user to a server |
|
||||
|
||||
To request additional scopes:
|
||||
|
||||
```python
|
||||
auth_provider = DiscordProvider(
|
||||
client_id="...",
|
||||
client_secret="...",
|
||||
base_url="http://localhost:8000",
|
||||
required_scopes=["identify", "email"],
|
||||
)
|
||||
```
|
||||
|
||||
## Production Configuration
|
||||
|
||||
For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`:
|
||||
|
||||
```python server.py
|
||||
import os
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.discord import DiscordProvider
|
||||
from key_value.aio.stores.redis import RedisStore
|
||||
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
auth_provider = DiscordProvider(
|
||||
client_id="12345",
|
||||
client_secret=os.environ["DISCORD_CLIENT_SECRET"],
|
||||
base_url="https://your-production-domain.com",
|
||||
|
||||
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
||||
client_storage=FernetEncryptionWrapper(
|
||||
key_value=RedisStore(
|
||||
host=os.environ["REDIS_HOST"],
|
||||
port=int(os.environ["REDIS_PORT"])
|
||||
),
|
||||
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
|
||||
)
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Production Discord App", auth=auth_provider)
|
||||
```
|
||||
|
||||
<Note>
|
||||
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
|
||||
|
||||
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
|
||||
</Note>
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the Discord provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.discord.DiscordProvider` to use Discord authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Discord-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the Discord provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID" required>
|
||||
Your Discord Application Client ID (e.g., `12345`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET" required>
|
||||
Your Discord OAuth Client Secret
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_BASE_URL" default="http://localhost:8000">
|
||||
Public URL where OAuth endpoints will be accessible (includes any mount path)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_ISSUER_URL" default="Uses BASE_URL">
|
||||
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_REDIRECT_PATH" default="/auth/callback">
|
||||
Redirect path configured in your Discord OAuth settings
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_REQUIRED_SCOPES" default='["identify"]'>
|
||||
Comma-, space-, or JSON-separated list of required Discord scopes (e.g., `identify,email` or `["identify","email"]`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DISCORD_TIMEOUT_SECONDS" default="10">
|
||||
HTTP request timeout for Discord API calls
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.discord.DiscordProvider
|
||||
|
||||
FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID=12345
|
||||
FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET=your-client-secret
|
||||
FASTMCP_SERVER_AUTH_DISCORD_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_DISCORD_REQUIRED_SCOPES=identify,email
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Discord Secured App")
|
||||
|
||||
@mcp.tool
|
||||
async def protected_tool(query: str) -> str:
|
||||
"""A tool that requires Discord authentication to access."""
|
||||
return f"Processing authenticated request: {query}"
|
||||
```
|
||||
129
docs/v2/integrations/eunomia-authorization.mdx
Normal file
129
docs/v2/integrations/eunomia-authorization.mdx
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
title: Eunomia Authorization 🤝 FastMCP
|
||||
sidebarTitle: Eunomia Auth
|
||||
description: Add policy-based authorization to your FastMCP servers with Eunomia
|
||||
icon: shield-check
|
||||
---
|
||||
|
||||
Add **policy-based authorization** to your FastMCP servers with one-line code addition with the **[Eunomia][eunomia-github] authorization middleware**.
|
||||
|
||||
Control which tools, resources and prompts MCP clients can view and execute on your server. Define dynamic JSON-based policies and obtain a comprehensive audit log of all access attempts and violations.
|
||||
|
||||
## How it Works
|
||||
|
||||
Exploiting FastMCP's [Middleware][fastmcp-middleare], the Eunomia middleware intercepts all MCP requests to your server and automatically maps MCP methods to authorization checks.
|
||||
|
||||
### Listing Operations
|
||||
|
||||
The middleware behaves as a filter for listing operations (`tools/list`, `resources/list`, `prompts/list`), hiding to the client components that are not authorized by the defined policies.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MCPClient as MCP Client
|
||||
participant EunomiaMiddleware as Eunomia Middleware
|
||||
participant MCPServer as FastMCP Server
|
||||
participant EunomiaServer as Eunomia Server
|
||||
|
||||
MCPClient->>EunomiaMiddleware: MCP Listing Request (e.g., tools/list)
|
||||
EunomiaMiddleware->>MCPServer: MCP Listing Request
|
||||
MCPServer-->>EunomiaMiddleware: MCP Listing Response
|
||||
EunomiaMiddleware->>EunomiaServer: Authorization Checks
|
||||
EunomiaServer->>EunomiaMiddleware: Authorization Decisions
|
||||
EunomiaMiddleware-->>MCPClient: Filtered MCP Listing Response
|
||||
```
|
||||
|
||||
### Execution Operations
|
||||
|
||||
The middleware behaves as a firewall for execution operations (`tools/call`, `resources/read`, `prompts/get`), blocking operations that are not authorized by the defined policies.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MCPClient as MCP Client
|
||||
participant EunomiaMiddleware as Eunomia Middleware
|
||||
participant MCPServer as FastMCP Server
|
||||
participant EunomiaServer as Eunomia Server
|
||||
|
||||
MCPClient->>EunomiaMiddleware: MCP Execution Request (e.g., tools/call)
|
||||
EunomiaMiddleware->>EunomiaServer: Authorization Check
|
||||
EunomiaServer->>EunomiaMiddleware: Authorization Decision
|
||||
EunomiaMiddleware-->>MCPClient: MCP Unauthorized Error (if denied)
|
||||
EunomiaMiddleware->>MCPServer: MCP Execution Request (if allowed)
|
||||
MCPServer-->>EunomiaMiddleware: MCP Execution Response (if allowed)
|
||||
EunomiaMiddleware-->>MCPClient: MCP Execution Response (if allowed)
|
||||
```
|
||||
|
||||
## Add Authorization to Your Server
|
||||
|
||||
<Note>
|
||||
Eunomia is an AI-specific authorization server that handles policy decisions. The server runs embedded within your MCP server by default for a zero-effort configuration, but can alternatively be run remotely for centralized policy decisions.
|
||||
|
||||
</Note>
|
||||
|
||||
### Create a Server with Authorization
|
||||
|
||||
First, install the `eunomia-mcp` package:
|
||||
|
||||
```bash
|
||||
pip install eunomia-mcp
|
||||
```
|
||||
|
||||
Then create a FastMCP server and add the Eunomia middleware in one line:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from eunomia_mcp import create_eunomia_middleware
|
||||
|
||||
# Create your FastMCP server
|
||||
mcp = FastMCP("Secure MCP Server 🔒")
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
# Add middleware to your server
|
||||
middleware = create_eunomia_middleware(policy_file="mcp_policies.json")
|
||||
mcp.add_middleware(middleware)
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
### Configure Access Policies
|
||||
|
||||
Use the `eunomia-mcp` CLI in your terminal to manage your authorization policies:
|
||||
|
||||
```bash
|
||||
# Create a default policy file
|
||||
eunomia-mcp init
|
||||
|
||||
# Or create a policy file customized for your FastMCP server
|
||||
eunomia-mcp init --custom-mcp "app.server:mcp"
|
||||
```
|
||||
|
||||
This creates `mcp_policies.json` file that you can further edit to your access control needs.
|
||||
|
||||
```bash
|
||||
# Once edited, validate your policy file
|
||||
eunomia-mcp validate mcp_policies.json
|
||||
```
|
||||
|
||||
### Run the Server
|
||||
|
||||
Start your FastMCP server normally:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
The middleware will now intercept all MCP requests and check them against your policies. Requests include agent identification through headers like `X-Agent-ID`, `X-User-ID`, `User-Agent`, or `Authorization` and an automatic mapping of MCP methods to authorization resources and actions.
|
||||
|
||||
<Tip>
|
||||
For detailed policy configuration, custom authentication, and remote
|
||||
deployments, visit the [Eunomia MCP Middleware
|
||||
repository][eunomia-mcp-github].
|
||||
</Tip>
|
||||
|
||||
[eunomia-github]: https://github.com/whataboutyou-ai/eunomia
|
||||
[eunomia-mcp-github]: https://github.com/whataboutyou-ai/eunomia/tree/main/pkgs/extensions/mcp
|
||||
[fastmcp-middleare]: /servers/middleware
|
||||
451
docs/v2/integrations/fastapi.mdx
Normal file
451
docs/v2/integrations/fastapi.mdx
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
---
|
||||
title: FastAPI 🤝 FastMCP
|
||||
sidebarTitle: FastAPI
|
||||
description: Integrate FastMCP with FastAPI applications
|
||||
icon: bolt
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
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
|
||||
|
||||
|
||||
<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.
|
||||
|
||||
We recommend using the FastAPI integration for bootstrapping and prototyping, not for mirroring your API to LLM clients. See the post [Stop Converting Your REST APIs to MCP](https://www.jlowin.dev/blog/stop-converting-rest-apis-to-mcp) for more details.
|
||||
</Tip>
|
||||
|
||||
|
||||
<Note>
|
||||
FastMCP does *not* include FastAPI as a dependency; you must install it separately to use this integration.
|
||||
</Note>
|
||||
|
||||
## Example FastAPI Application
|
||||
|
||||
Throughout this guide, we'll use this e-commerce API as our example (click the `Copy` button to copy it for use with other code blocks):
|
||||
|
||||
```python [expandable]
|
||||
# Copy this FastAPI server into other code blocks in this guide
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Models
|
||||
class Product(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
category: str
|
||||
description: str | None = None
|
||||
|
||||
class ProductResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
price: float
|
||||
category: str
|
||||
description: str | None = None
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(title="E-commerce API", version="1.0.0")
|
||||
|
||||
# In-memory database
|
||||
products_db = {
|
||||
1: ProductResponse(
|
||||
id=1, name="Laptop", price=999.99, category="Electronics"
|
||||
),
|
||||
2: ProductResponse(
|
||||
id=2, name="Mouse", price=29.99, category="Electronics"
|
||||
),
|
||||
3: ProductResponse(
|
||||
id=3, name="Desk Chair", price=299.99, category="Furniture"
|
||||
),
|
||||
}
|
||||
next_id = 4
|
||||
|
||||
@app.get("/products", response_model=list[ProductResponse])
|
||||
def list_products(
|
||||
category: str | None = None,
|
||||
max_price: float | None = None,
|
||||
) -> list[ProductResponse]:
|
||||
"""List all products with optional filtering."""
|
||||
products = list(products_db.values())
|
||||
if category:
|
||||
products = [p for p in products if p.category == category]
|
||||
if max_price:
|
||||
products = [p for p in products if p.price <= max_price]
|
||||
return products
|
||||
|
||||
@app.get("/products/{product_id}", response_model=ProductResponse)
|
||||
def get_product(product_id: int):
|
||||
"""Get a specific product by ID."""
|
||||
if product_id not in products_db:
|
||||
raise HTTPException(status_code=404, detail="Product not found")
|
||||
return products_db[product_id]
|
||||
|
||||
@app.post("/products", response_model=ProductResponse)
|
||||
def create_product(product: Product):
|
||||
"""Create a new product."""
|
||||
global next_id
|
||||
product_response = ProductResponse(id=next_id, **product.model_dump())
|
||||
products_db[next_id] = product_response
|
||||
next_id += 1
|
||||
return product_response
|
||||
|
||||
@app.put("/products/{product_id}", response_model=ProductResponse)
|
||||
def update_product(product_id: int, product: Product):
|
||||
"""Update an existing product."""
|
||||
if product_id not in products_db:
|
||||
raise HTTPException(status_code=404, detail="Product not found")
|
||||
products_db[product_id] = ProductResponse(
|
||||
id=product_id,
|
||||
**product.model_dump(),
|
||||
)
|
||||
return products_db[product_id]
|
||||
|
||||
@app.delete("/products/{product_id}")
|
||||
def delete_product(product_id: int):
|
||||
"""Delete a product."""
|
||||
if product_id not in products_db:
|
||||
raise HTTPException(status_code=404, detail="Product not found")
|
||||
del products_db[product_id]
|
||||
return {"message": "Product deleted"}
|
||||
```
|
||||
|
||||
<Tip>
|
||||
All subsequent code examples in this guide assume you have the above FastAPI application code already defined. Each example builds upon this base application, `app`.
|
||||
</Tip>
|
||||
|
||||
## Generating an MCP Server
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
One of the most common ways to bootstrap an MCP server is to generate it from an existing FastAPI application. FastMCP will expose your FastAPI endpoints as MCP components (tools, by default) in order to expose your API to LLM clients.
|
||||
|
||||
|
||||
|
||||
### Basic Conversion
|
||||
|
||||
Convert the FastAPI app to an MCP server with a single line:
|
||||
|
||||
```python {5}
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Convert to MCP server
|
||||
mcp = FastMCP.from_fastapi(app=app)
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
### Adding Components
|
||||
|
||||
Your converted MCP server is a full FastMCP instance, meaning you can add new tools, resources, and other components to it just like you would with any other FastMCP instance.
|
||||
|
||||
```python {8-11}
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Convert to MCP server
|
||||
mcp = FastMCP.from_fastapi(app=app)
|
||||
|
||||
# Add a new tool
|
||||
@mcp.tool
|
||||
def get_product(product_id: int) -> ProductResponse:
|
||||
"""Get a product by ID."""
|
||||
return products_db[product_id]
|
||||
|
||||
# Run the MCP server
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Interacting with the MCP Server
|
||||
|
||||
Once you've converted your FastAPI app to an MCP server, you can interact with it using the FastMCP client to test functionality before deploying it to an LLM-based application.
|
||||
|
||||
```python {3, }
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
import asyncio
|
||||
|
||||
# Convert to MCP server
|
||||
mcp = FastMCP.from_fastapi(app=app)
|
||||
|
||||
async def demo():
|
||||
async with Client(mcp) as client:
|
||||
# List available tools
|
||||
tools = await client.list_tools()
|
||||
print(f"Available tools: {[t.name for t in tools]}")
|
||||
|
||||
# Create a product
|
||||
result = await client.call_tool(
|
||||
"create_product_products_post",
|
||||
{
|
||||
"name": "Wireless Keyboard",
|
||||
"price": 79.99,
|
||||
"category": "Electronics",
|
||||
"description": "Bluetooth mechanical keyboard"
|
||||
}
|
||||
)
|
||||
print(f"Created product: {result.data}")
|
||||
|
||||
# List electronics under $100
|
||||
result = await client.call_tool(
|
||||
"list_products_products_get",
|
||||
{"category": "Electronics", "max_price": 100}
|
||||
)
|
||||
print(f"Affordable electronics: {result.data}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(demo())
|
||||
```
|
||||
|
||||
### Custom Route Mapping
|
||||
|
||||
Because FastMCP's FastAPI integration is based on its [OpenAPI integration](/integrations/openapi), you can customize how endpoints are converted to MCP components in exactly the same way. For example, here we use a `RouteMap` to map all GET requests to MCP resources, and all POST/PUT/DELETE requests to MCP tools:
|
||||
|
||||
```python
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.openapi import RouteMap, MCPType
|
||||
|
||||
# Custom mapping rules
|
||||
mcp = FastMCP.from_fastapi(
|
||||
app=app,
|
||||
route_maps=[
|
||||
# GET with path params → ResourceTemplates
|
||||
RouteMap(
|
||||
methods=["GET"],
|
||||
pattern=r".*\{.*\}.*",
|
||||
mcp_type=MCPType.RESOURCE_TEMPLATE
|
||||
),
|
||||
# Other GETs → Resources
|
||||
RouteMap(
|
||||
methods=["GET"],
|
||||
pattern=r".*",
|
||||
mcp_type=MCPType.RESOURCE
|
||||
),
|
||||
# POST/PUT/DELETE → Tools (default)
|
||||
],
|
||||
)
|
||||
|
||||
# Now:
|
||||
# - GET /products → Resource
|
||||
# - GET /products/{id} → ResourceTemplate
|
||||
# - POST/PUT/DELETE → Tools
|
||||
```
|
||||
|
||||
<Tip>
|
||||
To learn more about customizing the conversion process, see the [OpenAPI Integration guide](/integrations/openapi).
|
||||
</Tip>
|
||||
|
||||
### Authentication and Headers
|
||||
|
||||
You can configure headers and other client options via the `httpx_client_kwargs` parameter. For example, to add authentication to your FastAPI app, you can pass a `headers` dictionary to the `httpx_client_kwargs` parameter:
|
||||
|
||||
```python {27-31}
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Add authentication to your FastAPI app
|
||||
from fastapi import Depends, Header
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
||||
if credentials.credentials != "secret-token":
|
||||
raise HTTPException(status_code=401, detail="Invalid authentication")
|
||||
return credentials.credentials
|
||||
|
||||
# Add a protected endpoint
|
||||
@app.get("/admin/stats", dependencies=[Depends(verify_token)])
|
||||
def get_admin_stats():
|
||||
return {
|
||||
"total_products": len(products_db),
|
||||
"categories": list(set(p.category for p in products_db.values()))
|
||||
}
|
||||
|
||||
# Create MCP server with authentication headers
|
||||
mcp = FastMCP.from_fastapi(
|
||||
app=app,
|
||||
httpx_client_kwargs={
|
||||
"headers": {
|
||||
"Authorization": "Bearer secret-token",
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Mounting an MCP Server
|
||||
|
||||
<VersionBadge version="2.3.1" />
|
||||
|
||||
In addition to generating servers, FastMCP can facilitate adding MCP servers to your existing FastAPI application. You can do this by mounting the MCP ASGI application.
|
||||
|
||||
### Basic Mounting
|
||||
|
||||
To mount an MCP server, you can use the `http_app` method on your FastMCP instance. This will return an ASGI application that can be mounted to your FastAPI application.
|
||||
|
||||
```python {23-30}
|
||||
from fastmcp import FastMCP
|
||||
from fastapi import FastAPI
|
||||
|
||||
# Create MCP server
|
||||
mcp = FastMCP("Analytics Tools")
|
||||
|
||||
@mcp.tool
|
||||
def analyze_pricing(category: str) -> dict:
|
||||
"""Analyze pricing for a category."""
|
||||
products = [p for p in products_db.values() if p.category == category]
|
||||
if not products:
|
||||
return {"error": f"No products in {category}"}
|
||||
|
||||
prices = [p.price for p in products]
|
||||
return {
|
||||
"category": category,
|
||||
"avg_price": round(sum(prices) / len(prices), 2),
|
||||
"min": min(prices),
|
||||
"max": max(prices),
|
||||
}
|
||||
|
||||
# Create ASGI app from MCP server
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
|
||||
# Key: Pass lifespan to FastAPI
|
||||
app = FastAPI(title="E-commerce API", lifespan=mcp_app.lifespan)
|
||||
|
||||
# Mount the MCP server
|
||||
app.mount("/analytics", mcp_app)
|
||||
|
||||
# Now: API at /products/*, MCP at /analytics/mcp/
|
||||
```
|
||||
|
||||
## Offering an LLM-Friendly API
|
||||
|
||||
A common pattern is to generate an MCP server from your FastAPI app and serve both interfaces from the same application. This provides an LLM-optimized interface alongside your regular API:
|
||||
|
||||
```python
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
from fastmcp import FastMCP
|
||||
from fastapi import FastAPI
|
||||
|
||||
# 1. Generate MCP server from your API
|
||||
mcp = FastMCP.from_fastapi(app=app, name="E-commerce MCP")
|
||||
|
||||
# 2. Create the MCP's ASGI app
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
|
||||
# 3. Create a new FastAPI app that combines both sets of routes
|
||||
combined_app = FastAPI(
|
||||
title="E-commerce API with MCP",
|
||||
routes=[
|
||||
*mcp_app.routes, # MCP routes
|
||||
*app.routes, # Original API routes
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
|
||||
# Now you have:
|
||||
# - Regular API: http://localhost:8000/products
|
||||
# - LLM-friendly MCP: http://localhost:8000/mcp
|
||||
# Both served from the same FastAPI application!
|
||||
```
|
||||
|
||||
This approach lets you maintain a single codebase while offering both traditional REST endpoints and MCP-compatible endpoints for LLM clients.
|
||||
|
||||
## Key Considerations
|
||||
|
||||
### Operation IDs
|
||||
|
||||
FastAPI operation IDs become MCP component names. Always specify meaningful operation IDs:
|
||||
|
||||
```python
|
||||
# Good - explicit operation_id
|
||||
@app.get("/users/{user_id}", operation_id="get_user_by_id")
|
||||
def get_user(user_id: int):
|
||||
return {"id": user_id}
|
||||
|
||||
# Less ideal - auto-generated name
|
||||
@app.get("/users/{user_id}")
|
||||
def get_user(user_id: int):
|
||||
return {"id": user_id}
|
||||
```
|
||||
|
||||
### Lifespan Management
|
||||
|
||||
When mounting MCP servers, always pass the lifespan context:
|
||||
|
||||
```python
|
||||
# Correct - lifespan passed
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
app = FastAPI(lifespan=mcp_app.lifespan)
|
||||
app.mount("/mcp", mcp_app)
|
||||
|
||||
# Incorrect - missing lifespan
|
||||
app = FastAPI()
|
||||
app.mount("/mcp", mcp.http_app()) # Session manager won't initialize
|
||||
```
|
||||
|
||||
If you're mounting an authenticated MCP server under a path prefix, see [Mounting Authenticated Servers](/deployment/http#mounting-authenticated-servers) for important OAuth routing considerations.
|
||||
|
||||
### CORS Middleware
|
||||
|
||||
If your FastAPI app uses `CORSMiddleware` and you're mounting an OAuth-protected FastMCP server, avoid adding application-wide CORS middleware. FastMCP and the MCP SDK already handle CORS for OAuth routes, and layering CORS middleware can cause conflicts (such as 404 errors on `.well-known` routes or OPTIONS requests).
|
||||
|
||||
If you need CORS on your own FastAPI routes, use the sub-app pattern: mount your API and FastMCP as separate apps, each with their own middleware, rather than adding top-level `CORSMiddleware` to the combined application.
|
||||
|
||||
### Combining Lifespans
|
||||
|
||||
If your FastAPI app already has a lifespan (for database connections, startup tasks, etc.), you can't simply replace it with the MCP lifespan. Instead, you need to create a new lifespan function that manages both contexts. This ensures that both your app's initialization logic and the MCP server's session manager run properly:
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Your existing lifespan
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(app: FastAPI):
|
||||
# Startup
|
||||
print("Starting up the app...")
|
||||
# Initialize database, cache, etc.
|
||||
yield
|
||||
# Shutdown
|
||||
print("Shutting down the app...")
|
||||
|
||||
# Create MCP server
|
||||
mcp = FastMCP("Tools")
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
|
||||
# Combine both lifespans
|
||||
@asynccontextmanager
|
||||
async def combined_lifespan(app: FastAPI):
|
||||
# Run both lifespans
|
||||
async with app_lifespan(app):
|
||||
async with mcp_app.lifespan(app):
|
||||
yield
|
||||
|
||||
# Use the combined lifespan
|
||||
app = FastAPI(lifespan=combined_lifespan)
|
||||
app.mount("/mcp", mcp_app)
|
||||
```
|
||||
|
||||
This pattern ensures both your app's initialization logic and the MCP server's session manager are properly managed. The key is using nested `async with` statements - the inner context (MCP) will be initialized after the outer context (your app), and cleaned up before it. This maintains the correct initialization and cleanup order for all your resources.
|
||||
|
||||
### Performance Tips
|
||||
|
||||
1. **Use in-memory transport for testing** - Pass MCP servers directly to clients
|
||||
2. **Design purpose-built MCP tools** - Better than auto-converting complex APIs
|
||||
3. **Keep tool parameters simple** - LLMs perform better with focused interfaces
|
||||
|
||||
For more details on configuration options, see the [OpenAPI Integration guide](/integrations/openapi).
|
||||
174
docs/v2/integrations/gemini-cli.mdx
Normal file
174
docs/v2/integrations/gemini-cli.mdx
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
---
|
||||
title: Gemini CLI 🤝 FastMCP
|
||||
sidebarTitle: Gemini CLI
|
||||
description: Install and use FastMCP servers in Gemini CLI
|
||||
icon: message-smile
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
import { LocalFocusTip } from "/snippets/local-focus.mdx"
|
||||
|
||||
<LocalFocusTip />
|
||||
|
||||
Gemini CLI supports MCP servers through multiple transport methods including STDIO, SSE, and HTTP, allowing you to extend Gemini's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
||||
|
||||
## Requirements
|
||||
|
||||
This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly using Gemini CLI's built-in MCP management commands.
|
||||
|
||||
## Create a Server
|
||||
|
||||
The examples in this guide will use the following simple dice-rolling server, saved as `server.py`.
|
||||
|
||||
```python server.py
|
||||
import random
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Dice Roller")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
## Install the Server
|
||||
|
||||
### FastMCP CLI
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
The easiest way to install a FastMCP server in Gemini CLI is using the `fastmcp install gemini-cli` command. This automatically handles the configuration, dependency management, and calls Gemini CLI's built-in MCP management system.
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py
|
||||
```
|
||||
|
||||
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
|
||||
|
||||
```bash
|
||||
# These are equivalent if your server object is named 'mcp'
|
||||
fastmcp install gemini-cli server.py
|
||||
fastmcp install gemini-cli server.py:mcp
|
||||
|
||||
# Use explicit object name if your server has a different name
|
||||
fastmcp install gemini-cli server.py:my_custom_server
|
||||
```
|
||||
|
||||
The command will automatically configure the server with Gemini CLI's `gemini mcp add` command.
|
||||
|
||||
#### Dependencies
|
||||
|
||||
FastMCP provides flexible dependency management options for your Gemini CLI servers:
|
||||
|
||||
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --with pandas --with requests
|
||||
```
|
||||
|
||||
**Requirements file**: If you maintain a `requirements.txt` file with all your dependencies, use `--with-requirements` to install them:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --with-requirements requirements.txt
|
||||
```
|
||||
|
||||
**Editable packages**: For local packages under development, use `--with-editable` to install them in editable mode:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --with-editable ./my-local-package
|
||||
```
|
||||
|
||||
Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
||||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Python Version and Project Configuration
|
||||
|
||||
Control the Python environment for your server with these options:
|
||||
|
||||
**Python version**: Use `--python` to specify which Python version your server requires. This ensures compatibility when your server needs specific Python features:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --python 3.11
|
||||
```
|
||||
|
||||
**Project directory**: Use `--project` to run your server within a specific project context. This tells `uv` to use the project's configuration files and virtual environment:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --project /path/to/my-project
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
If your server needs environment variables (like API keys), you must include them:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --server-name "Weather Server" \
|
||||
--env API_KEY=your-api-key \
|
||||
--env DEBUG=true
|
||||
```
|
||||
|
||||
Or load them from a `.env` file:
|
||||
|
||||
```bash
|
||||
fastmcp install gemini-cli server.py --server-name "Weather Server" --env-file .env
|
||||
```
|
||||
|
||||
<Warning>
|
||||
**Gemini CLI must be installed**. The integration looks for the Gemini CLI and uses the `gemini mcp add` command to register servers.
|
||||
</Warning>
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
For more control over the configuration, you can manually use Gemini CLI's built-in MCP management commands. This gives you direct control over how your server is launched:
|
||||
|
||||
```bash
|
||||
# Add a server with custom configuration
|
||||
gemini mcp add dice-roller uv -- run --with fastmcp fastmcp run server.py
|
||||
|
||||
# Add with environment variables
|
||||
gemini mcp add weather-server -e API_KEY=secret -e DEBUG=true uv -- run --with fastmcp fastmcp run server.py
|
||||
|
||||
# Add with specific scope (user, or project)
|
||||
gemini mcp add my-server --scope user uv -- run --with fastmcp fastmcp run server.py
|
||||
```
|
||||
|
||||
You can also manually specify Python versions and project directories in your Gemini CLI commands:
|
||||
|
||||
```bash
|
||||
# With specific Python version
|
||||
gemini mcp add ml-server uv -- run --python 3.11 --with fastmcp fastmcp run server.py
|
||||
|
||||
# Within a project directory
|
||||
gemini mcp add project-server uv -- run --project /path/to/project --with fastmcp fastmcp run server.py
|
||||
```
|
||||
|
||||
## Using the Server
|
||||
|
||||
Once your server is installed, you can start using your FastMCP server with Gemini CLI.
|
||||
|
||||
Try asking Gemini something like:
|
||||
|
||||
> "Roll some dice for me"
|
||||
|
||||
Gemini will automatically detect your `roll_dice` tool and use it to fulfill your request.
|
||||
|
||||
Gemini CLI can now access all the tools and prompts you've defined in your FastMCP server.
|
||||
|
||||
If your server provides prompts, you can use them as slash commands with `/prompt_name`.
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue