Merge branch 'main' into protocol-update

This commit is contained in:
Jeremiah Lowin 2025-06-22 13:45:29 -04:00
commit eb00f2238b
112 changed files with 6315 additions and 736 deletions

View file

@ -1,4 +1,4 @@
fail_fast: true
fail_fast: false
repos:
- repo: https://github.com/abravalheri/validate-pyproject

View file

@ -27,4 +27,9 @@ Only use HTTP transport when testing network-specific features. Prefer Streamabl
# Only when network testing is required
async with Client(transport=StreamableHttpTransport(server_url)) as client:
result = await client.ping()
```
```
## Development Workflow
- You must always run pre-commit if you open a PR, because it is run as part of a required check.
- When opening PRs, apply labels appropriately for bugs/breaking changes/enhancements/features. Generally, improvements are enhancements (not features) unless told otherwise.

View file

@ -1,5 +1,5 @@
---
mode: center
icon: "list-check"
---
<Update label="v2.8.0" description="2024-06-10">

View file

@ -1,152 +0,0 @@
---
title: Advanced Features
sidebarTitle: Advanced Features
description: Learn about the advanced features of the FastMCP Client.
icon: stars
---
import { VersionBadge } from '/snippets/version-badge.mdx'
In addition to basic server interaction, FastMCP clients can also handle more advanced features and server interaction patterns. The `Client` constructor accepts additional configuration to handle these server requests.
<Tip>
To enable many of these features, you must provide an appropriate handler or callback function. For example. In most cases, if you do not provide a handler, FastMCP's default handler will emit a `DEBUG` level log.
</Tip>
## Logging and Notifications
<VersionBadge version="2.0.0" />
MCP servers can emit logs to clients. To process these logs, you can provide a `log_handler` to the client.
The `log_handler` must be an async function that accepts a single argument, which is an instance of `fastmcp.client.logging.LogMessage`. This has attributes like `level`, `logger`, and `data`.
```python {2, 12}
from fastmcp import Client
from fastmcp.client.logging import LogMessage
async def log_handler(message: LogMessage):
level = message.level.upper()
logger = message.logger or 'default'
data = message.data
print(f"[Server Log - {level}] {logger}: {data}")
client_with_logging = Client(
...,
log_handler=log_handler,
)
```
## Progress Monitoring
<VersionBadge version="2.3.5" />
MCP servers can report progress during long-running operations. The client can set a progress handler to receive and process these updates.
```python {2, 13}
from fastmcp import Client
from fastmcp.client.progress import ProgressHandler
async def my_progress_handler(
progress: float,
total: float | None,
message: str | None
) -> None:
print(f"Progress: {progress} / {total} ({message})")
client = Client(
...,
progress_handler=my_progress_handler
)
```
By default, FastMCP uses a handler that logs progress updates at the debug level. This default handler properly handles cases where `total` or `message` might be None.
You can override the progress handler for specific tool calls:
```python
# Client uses the default debug logger for progress
client = Client(...)
async with client:
# Use default progress handler (debug logging)
result1 = await client.call_tool("long_task", {"param": "value"})
# Override with custom progress handler just for this call
result2 = await client.call_tool(
"another_task",
{"param": "value"},
progress_handler=my_progress_handler
)
```
A typical progress update includes:
- Current progress value (e.g., 2 of 5 steps completed)
- Total expected value (may be None)
- Status message (may be None)
## LLM Sampling
<VersionBadge version="2.0.0" />
MCP Servers can request LLM completions from clients. The client can provide a `sampling_handler` to handle these requests. The sampling handler receives a list of messages and other parameters from the server, and should return a string completion.
The following example uses the `marvin` library to generate a completion:
```python {8-17, 21}
import marvin
from fastmcp import Client
from fastmcp.client.sampling import (
SamplingMessage,
SamplingParams,
RequestContext,
)
async def sampling_handler(
messages: list[SamplingMessage],
params: SamplingParams,
context: RequestContext
) -> str:
return await marvin.say_async(
message=[m.content.text for m in messages],
instructions=params.systemPrompt,
)
client = Client(
...,
sampling_handler=sampling_handler,
)
```
## Roots
<VersionBadge version="2.0.0" />
Roots are a way for clients to inform servers about the resources they have access to or certain boundaries on their access. The server can use this information to adjust behavior or provide more accurate responses.
Servers can request roots from clients, and clients can notify servers when their roots change.
To set the roots when creating a client, users can either provide a list of roots (which can be a list of strings) or an async function that returns a list of roots.
<CodeGroup>
```python Static Roots {5}
from fastmcp import Client
client = Client(
...,
roots=["/path/to/root1", "/path/to/root2"],
)
```
```python Dynamic Roots Callback {4-6, 10}
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(
...,
roots=roots_callback,
)
```
</CodeGroup>

View file

@ -3,7 +3,7 @@ title: Bearer Token Authentication
sidebarTitle: Bearer Auth
description: Authenticate your FastMCP client with a Bearer token.
icon: key
tag: "New!"
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -3,7 +3,7 @@ title: OAuth Authentication
sidebarTitle: OAuth
description: Authenticate your FastMCP client via OAuth 2.1.
icon: window
tag: "New!"
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -1,7 +1,7 @@
---
title: Client Overview
title: The FastMCP Client
sidebarTitle: Overview
description: Learn how to use the FastMCP Client to interact with MCP servers.
description: Programmatic client for interacting with MCP servers through a well-typed, Pythonic interface.
icon: user-robot
---
@ -9,270 +9,213 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
The `fastmcp.Client` provides a high-level, asynchronous interface for interacting with any Model Context Protocol (MCP) server, whether it's built with FastMCP or another implementation. It simplifies communication by handling protocol details and connection management.
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.
## FastMCP Client
The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for:
The FastMCP Client architecture separates the protocol logic (`Client`) from the connection mechanism (`Transport`).
- **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
- **`Client`**: Handles sending MCP requests (like `tools/call`, `resources/read`), receiving responses, and managing callbacks.
- **`Transport`**: Responsible for establishing and maintaining the connection to the server (e.g., via WebSockets, SSE, Stdio, or in-memory).
All client operations require using the `async with` context manager for proper connection lifecycle management.
### Transports
Clients must be initialized with a `transport`. You can either provide an already instantiated transport object, or provide a transport source and let FastMCP attempt to infer the correct transport to use.
<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>
The following inference rules are used to determine the appropriate `ClientTransport` based on the input type:
## Creating a Client
1. **`ClientTransport` Instance**: If you provide an already instantiated transport object, it's used directly.
2. **`FastMCP` Instance**: Creates a `FastMCPTransport` for efficient in-memory communication (ideal for testing). This also works with a **FastMCP 1.0 server** created via `mcp.server.fastmcp.FastMCP`.
3. **`Path` or `str` pointing to an existing file**:
* If it ends with `.py`: Creates a `PythonStdioTransport` to run the script using `python`.
* If it ends with `.js`: Creates a `NodeStdioTransport` to run the script using `node`.
4. **`AnyUrl` or `str` pointing to a URL that begins with `http://` or `https://`**:
* Creates a `StreamableHttpTransport`
5. **`MCPConfig` or dictionary matching MCPConfig schema**: Creates a client that connects to one or more MCP servers specified in the config.
6. **Other**: Raises a `ValueError` if the type cannot be inferred.
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
# Example transports (more details in Transports page)
server_instance = FastMCP(name="TestServer") # In-memory server
http_url = "https://example.com/mcp" # HTTP server URL
server_script = "my_mcp_server.py" # Path to a Python server file
# In-memory server (ideal for testing)
server = FastMCP("TestServer")
client = Client(server)
# Client automatically infers the transport type
client_in_memory = Client(server_instance)
client_http = Client(http_url)
# HTTP server
client = Client("https://example.com/mcp")
client_stdio = Client(server_script)
# Local Python script
client = Client("my_mcp_server.py")
print(client_in_memory.transport)
print(client_http.transport)
print(client_stdio.transport)
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)
# Expected Output (types may vary slightly based on environment):
# <FastMCP(server='TestServer')>
# <StreamableHttp(url='https://example.com/mcp')>
# <PythonStdioTransport(command='python', args=['/path/to/your/my_mcp_server.py'])>
asyncio.run(main())
```
You can also initialize a client from an MCP configuration dictionary or `MCPConfig` file:
## 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
from fastmcp import Client, FastMCP
config = {
"mcpServers": {
"local": {"command": "python", "args": ["local_server.py"]},
"remote": {"url": "https://example.com/mcp"},
}
}
client_config = Client(config)
# Examples of transport inference
client_memory = Client(FastMCP("TestServer"))
client_script = Client("./server.py")
client_http = Client("https://api.example.com/mcp")
```
<Tip>
For more control over connection details (like headers for SSE, environment variables for Stdio), you can instantiate the specific `ClientTransport` class yourself and pass it to the `Client`. See the [Transports](/clients/transports) page for details.
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>
### Multi-Server Clients
## Configuration-Based Clients
<VersionBadge version="2.4.0" />
FastMCP supports creating clients that connect to multiple MCP servers through a single client interface using a standard MCP configuration format (`MCPConfig`). This configuration approach makes it easy to connect to multiple specialized servers or create composable systems with a simple, declarative syntax.
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.
<Note>
The MCP configuration format follows an emerging standard and may evolve as the specification matures. FastMCP will strive to maintain compatibility with future versions, but be aware that field names or structure might change.
</Note>
When you create a client with an `MCPConfig` containing multiple servers:
1. FastMCP creates a composite client that internally mounts all servers using their config names as prefixes
2. Tools and resources from each server are accessible with appropriate prefixes in the format `servername_toolname` and `protocol://servername/resource/path`
3. You interact with this as a single unified client, with requests automatically routed to the appropriate server
### Configuration Format
```python
from fastmcp import Client
# Create a standard MCP configuration with multiple servers
config = {
"mcpServers": {
# A remote HTTP server
"weather": {
"url": "https://weather-api.example.com/mcp",
"transport": "streamable-http"
"server_name": {
# Remote HTTP/SSE server
"transport": "streamable-http", # or "sse"
"url": "https://api.example.com/mcp",
"headers": {"Authorization": "Bearer token"},
"auth": "oauth" # or bearer token string
},
# A local server running via stdio
"assistant": {
"local_server": {
# Local stdio server
"transport": "stdio"
"command": "python",
"args": ["./my_assistant_server.py"],
"env": {"DEBUG": "true"}
"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"]}
}
}
# Create a client that connects to both servers
client = Client(config)
async def main():
async with client:
# Access tools from different servers with prefixes
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?"})
# Access resources with prefixed URIs
weather_icons = await client.read_resource("weather://weather/icons/sunny")
templates = await client.read_resource("resource://assistant/templates/list")
print(f"Weather: {weather_data}")
print(f"Assistant: {response}")
if __name__ == "__main__":
asyncio.run(main())
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")
```
If your configuration has only a single server, FastMCP will create a direct client to that server without any prefixing.
## Connection Lifecycle
## Client Usage
### Connection Lifecycle
The client operates asynchronously and must be used within an `async with` block. This context manager handles establishing the connection, initializing the MCP session, and cleaning up resources upon exit.
The client operates asynchronously and uses context managers for connection management:
```python
import asyncio
from fastmcp import Client
client = Client("my_mcp_server.py") # Assumes my_mcp_server.py exists
async def main():
# Connection is established here
async def example():
client = Client("my_mcp_server.py")
# Connection established here
async with client:
print(f"Client connected: {client.is_connected()}")
# Make MCP calls within the context
print(f"Connected: {client.is_connected()}")
# Make multiple calls within the same session
tools = await client.list_tools()
print(f"Available tools: {tools}")
if any(tool.name == "greet" for tool in tools):
result = await client.call_tool("greet", {"name": "World"})
print(f"Greet result: {result}")
# Connection is closed automatically here
print(f"Client connected: {client.is_connected()}")
if __name__ == "__main__":
asyncio.run(main())
result = await client.call_tool("greet", {"name": "World"})
# Connection closed automatically here
print(f"Connected: {client.is_connected()}")
```
You can make multiple calls to the server within the same `async with` block using the established session.
## Operations
### Client Methods
FastMCP clients can interact with several types of server components:
The `Client` provides methods corresponding to standard MCP requests:
### Tools
<Warning>
The standard client methods return user-friendly representations that may change as the protocol evolves. For consistent access to the complete data structure, use the `*_mcp` methods described later.
</Warning>
#### Tool Operations
* **`list_tools()`**: Retrieves a list of tools available on the server.
```python
tools = await client.list_tools()
# tools -> list[mcp.types.Tool]
```
* **`call_tool(name: str, arguments: dict[str, Any] | None = None, timeout: float | None = None, progress_handler: ProgressHandler | None = None)`**: Executes a tool on the server.
```python
result = await client.call_tool("add", {"a": 5, "b": 3})
# result -> list[mcp.types.TextContent | mcp.types.ImageContent | ...]
print(result[0].text) # Assuming TextContent, e.g., '8'
# 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
)
```
* Arguments are passed as a dictionary. FastMCP servers automatically handle JSON string parsing for complex types if needed.
* Returns a list of content objects (usually `TextContent` or `ImageContent`).
* The optional `timeout` parameter limits the maximum execution time (in seconds) for this specific call, overriding any client-level timeout.
* The optional `progress_handler` parameter receives progress updates during execution, overriding any client-level progress handler.
#### Resource Operations
* **`list_resources()`**: Retrieves a list of static resources.
```python
resources = await client.list_resources()
# resources -> list[mcp.types.Resource]
```
* **`list_resource_templates()`**: Retrieves a list of resource templates.
```python
templates = await client.list_resource_templates()
# templates -> list[mcp.types.ResourceTemplate]
```
* **`read_resource(uri: str | AnyUrl)`**: Reads the content of a resource or a resolved template.
```python
# Read a static resource
readme_content = await client.read_resource("file:///path/to/README.md")
# readme_content -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
print(readme_content[0].text) # Assuming text
# Read a resource generated from a template
weather_content = await client.read_resource("data://weather/london")
print(weather_content[0].text) # Assuming text JSON
```
#### Prompt Operations
* **`list_prompts()`**: Retrieves available prompt templates.
* **`get_prompt(name: str, arguments: dict[str, Any] | None = None)`**: Retrieves a rendered prompt message list.
### Raw MCP Protocol Objects
<VersionBadge version="2.2.7" />
The FastMCP client attempts to provide a "friendly" interface to the MCP protocol, but sometimes you may need access to the raw MCP protocol objects. Each of the main client methods that returns data has a corresponding `*_mcp` method that returns the raw MCP protocol objects directly.
<Warning>
The standard client methods (without `_mcp`) return user-friendly representations of MCP data, while `*_mcp` methods will always return the complete MCP protocol objects. As the protocol evolves, changes to these user-friendly representations may occur and could potentially be breaking. If you need consistent, stable access to the full data structure, prefer using the `*_mcp` methods.
</Warning>
Tools are server-side functions that the client can execute with arguments.
```python
# Standard method - returns just the list of tools
tools = await client.list_tools()
# tools -> list[mcp.types.Tool]
# Raw MCP method - returns the full protocol object
result = await client.list_tools_mcp()
# result -> mcp.types.ListToolsResult
tools = result.tools
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[0].text) # "15"
```
Available raw MCP methods:
See [Tools](/clients/tools) for detailed documentation.
* **`list_tools_mcp()`**: Returns `mcp.types.ListToolsResult`
* **`call_tool_mcp(name, arguments)`**: Returns `mcp.types.CallToolResult`
* **`list_resources_mcp()`**: Returns `mcp.types.ListResourcesResult`
* **`list_resource_templates_mcp()`**: Returns `mcp.types.ListResourceTemplatesResult`
* **`read_resource_mcp(uri)`**: Returns `mcp.types.ReadResourceResult`
* **`list_prompts_mcp()`**: Returns `mcp.types.ListPromptsResult`
* **`get_prompt_mcp(name, arguments)`**: Returns `mcp.types.GetPromptResult`
* **`complete_mcp(ref, argument)`**: Returns `mcp.types.CompleteResult`
### Resources
These methods are especially useful for debugging or when you need to access metadata or fields that aren't exposed by the simplified methods.
Resources are data sources that the client can read, either static or templated.
### Additional Features
```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)
```
#### Pinging the Server
See [Resources](/clients/resources) for detailed documentation.
The client can be used to ping the server to verify connectivity.
### 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:
@ -280,93 +223,69 @@ async with client:
print("Server is reachable")
```
#### Session Management
## Client Configuration
When using stdio transports, clients support a `keep_alive` feature (enabled by default) that maintains subprocess sessions between connection contexts. You can manually control this behavior using the client's `close()` method.
Clients can be configured with additional handlers and settings for specialized use cases.
When `keep_alive=False`, the client will automatically close the session when the context manager exits.
### Callback Handlers
The client supports several callback handlers for advanced server interactions:
```python
from fastmcp import Client
from fastmcp.client.logging import LogMessage
client = Client("my_mcp_server.py") # keep_alive=True by default
async def log_handler(message: LogMessage):
print(f"Server log: {message.data}")
async def example():
async with client:
await client.ping()
async with client:
await client.ping() # Same subprocess as above
```
async def progress_handler(progress: float, total: float | None, message: str | None):
print(f"Progress: {progress}/{total} - {message}")
<Note>
For detailed examples and configuration options, see [Session Management in Transports](/clients/transports#session-management).
</Note>
async def sampling_handler(messages, params, context):
# Integrate with your LLM service here
return "Generated response"
#### Timeouts
<VersionBadge version="2.3.4" />
You can control request timeouts at both the client level and individual request level:
```python
from fastmcp import Client
from fastmcp.exceptions import McpError
# Client with a global 5-second timeout for all requests
client = Client(
my_mcp_server,
timeout=5.0 # Default timeout in seconds
"my_mcp_server.py",
log_handler=log_handler,
progress_handler=progress_handler,
sampling_handler=sampling_handler,
timeout=30.0
)
async with client:
# This uses the global 5-second timeout
result1 = await client.call_tool("quick_task", {"param": "value"})
# This specifies a 10-second timeout for this specific call
result2 = await client.call_tool("slow_task", {"param": "value"}, timeout=10.0)
try:
# This will likely timeout
result3 = await client.call_tool("medium_task", {"param": "value"}, timeout=0.01)
except McpError as e:
# Handle timeout error
print(f"The task timed out: {e}")
```
<Warning>
Timeout behavior varies between transport types:
The `Client` constructor accepts several configuration options:
- With **SSE** transport, the per-request (tool call) timeout **always** takes precedence, regardless of which is lower.
- With **HTTP** transport, the **lower** of the two timeouts (client or tool call) takes precedence.
- `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)
For consistent behavior across all transports, we recommend explicitly setting timeouts at the individual tool call level when needed, rather than relying on client-level timeouts.
</Warning>
### Transport Configuration
#### Error Handling
For detailed transport configuration (headers, authentication, environment variables), see the [Transports](/clients/transports) documentation.
When a `call_tool` request results in an error on the server (e.g., the tool function raised an exception), the `client.call_tool()` method will raise a `fastmcp.exceptions.ClientError`.
## Next Steps
```python
async def safe_call_tool():
async with client:
try:
# Assume 'divide' tool exists and might raise ZeroDivisionError
result = await client.call_tool("divide", {"a": 10, "b": 0})
print(f"Result: {result}")
except ClientError as e:
print(f"Tool call failed: {e}")
except ConnectionError as e:
print(f"Connection failed: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Explore the detailed documentation for each operation type:
# Example Output if division by zero occurs:
# Tool call failed: Division by zero is not allowed.
```
### 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
Other errors, like connection failures, will raise standard Python exceptions (e.g., `ConnectionError`, `TimeoutError`).
### 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 client transport often has its own error-handling mechanisms, so you can not always trap errors like those raised by `call_tool` outside of the `async with` block. Instead, you can use `call_tool_mcp()` to get the raw `mcp.types.CallToolResult` object and handle errors yourself by checking its `isError` attribute.
</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>

63
docs/clients/logging.mdx Normal file
View file

@ -0,0 +1,63 @@
---
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.
## Setting Up Log Handling
Provide a `log_handler` function when creating the client:
```python
from fastmcp import Client
from fastmcp.client.logging import LogMessage
async def log_handler(message: LogMessage):
level = message.level.upper()
logger = message.logger or 'server'
data = message.data
print(f"[{level}] {logger}: {data}")
client = Client(
"my_mcp_server.py",
log_handler=log_handler,
)
```
## LogMessage Structure
The `log_handler` receives a `LogMessage` object with:
- **`level`**: Log level (e.g., "debug", "info", "warning", "error")
- **`logger`**: Logger name (optional, may be None)
- **`data`**: The actual log message content
```python
async def detailed_log_handler(message: LogMessage):
if message.level == "error":
print(f"ERROR: {message.data}")
elif message.level == "warning":
print(f"WARNING: {message.data}")
else:
print(f"{message.level.upper()}: {message.data}")
```
## Default Log Handling
If you don't provide a custom `log_handler`, FastMCP uses a default handler that emits DEBUG level logs:
```python
# Without custom handler - uses default DEBUG logging
client = Client("my_mcp_server.py")
async with client:
# Server logs will be emitted at DEBUG level
await client.call_tool("some_tool")
```

59
docs/clients/progress.mdx Normal file
View file

@ -0,0 +1,59 @@
---
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.
## Setting Up Progress Handling
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
)
```
## 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
)
```
## Handler Parameters
The progress handler receives:
- **`progress`** (float): Current progress value
- **`total`** (float | None): Expected total value (may be None)
- **`message`** (str | None): Optional status message (may be None)

187
docs/clients/prompts.mdx Normal file
View file

@ -0,0 +1,187 @@
---
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]}")
```
## 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>

171
docs/clients/resources.mdx Normal file
View file

@ -0,0 +1,171 @@
---
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}")
```
### 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}")
```
## 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/clients/roots.mdx Normal file
View 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>

91
docs/clients/sampling.mdx Normal file
View file

@ -0,0 +1,91 @@
---
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.
## Setting Up Sampling Handling
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:
### SamplingMessage
- **`role`**: Message role (e.g., "user", "assistant", "system")
- **`content`**: Message content (usually has `.text` attribute)
### SamplingParams
- **`systemPrompt`**: System prompt string (optional)
- **`maxTokens`**: Maximum tokens to generate (optional)
- **`temperature`**: Sampling temperature (optional)
- **`topP`**: Top-p sampling parameter (optional)
- **`stopSequences`**: List of stop sequences (optional)
### RequestContext
- **`request_id`**: Unique identifier for the sampling request
## 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
)
```

143
docs/clients/tools.mdx Normal file
View file

@ -0,0 +1,143 @@
---
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}")
```
## 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 -> list[mcp.types.TextContent | mcp.types.ImageContent | ...]
# Access the result content
print(result[0].text) # Assuming TextContent, e.g., '8'
```
### 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)
## Handling Results
Tool execution returns a list of content objects. The most common types are:
- **`TextContent`**: Text-based results with a `.text` attribute
- **`ImageContent`**: Image data with image-specific attributes
- **`BlobContent`**: Binary data content
```python
async with client:
result = await client.call_tool("get_weather", {"city": "London"})
for content in result:
if hasattr(content, 'text'):
print(f"Text result: {content.text}")
elif hasattr(content, 'data'):
print(f"Binary data: {len(content.data)} bytes")
```
## 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)
except ToolError as e:
print(f"Tool failed: {e}")
```
### Manual Error Checking
For more granular control, use `call_tool_mcp()` which returns the raw MCP protocol object with an `isError` flag:
```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}")
```
## 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>

View file

@ -48,7 +48,7 @@ Both approaches return a Starlette application that can be integrated with other
The returned app stores the `FastMCP` instance on `app.state.fastmcp_server`, so you
can access it from custom middleware or routes via `request.app.state.fastmcp_server`.
The MCP server's endpoint is mounted at the root path `/mcp` for Streamable HTTP transport, and `/sse` for SSE transport, though you can change these paths by passing a `path` argument to the `http_app()` method:
The MCP server's endpoint is mounted at the root path `/mcp/` for Streamable HTTP transport, and `/sse/` for SSE transport, though you can change these paths by passing a `path` argument to the `http_app()` method:
```python
# For Streamable HTTP transport
@ -96,7 +96,13 @@ mcp = FastMCP("MyServer")
# Define custom middleware
custom_middleware = [
Middleware(CORSMiddleware, allow_origins=["*"]),
Middleware(
CORSMiddleware,
allow_origins=["https://example.com", "https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
),
]
# Create ASGI app with custom middleware
@ -131,7 +137,7 @@ app = Starlette(
)
```
The MCP endpoint will be available at `/mcp-server/mcp` of the resulting Starlette app.
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.
@ -161,7 +167,7 @@ app = Starlette(
)
```
In this setup, the MCP server is accessible at the `/outer/inner/mcp` path of the resulting Starlette app.
In this setup, the MCP server is accessible at the `/outer/inner/mcp/` path of the resulting Starlette app.
<Warning>
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the *outer* Starlette app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.
@ -188,7 +194,7 @@ app = FastAPI(lifespan=mcp_app.lifespan)
app.mount("/mcp-server", mcp_app)
```
The MCP endpoint will be available at `/mcp-server/mcp` of the resulting FastAPI app.
The MCP endpoint will be available at `/mcp-server/mcp/` of the resulting FastAPI app.
<Warning>
For Streamable HTTP transport, you **must** pass the lifespan context from the FastMCP app to the resulting FastAPI app, as nested lifespans are not recognized. Otherwise, the FastMCP server's session manager will not be properly initialized.

View file

@ -105,7 +105,7 @@ When using Stdio transport, you will typically *not* run the server yourself as
Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is the recommended transport for web-based deployments.
To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"streamable-http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp`).
To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"streamable-http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp/`).
<CodeGroup>
```python {6} server.py
from fastmcp import FastMCP
@ -120,7 +120,7 @@ import asyncio
from fastmcp import Client
async def example():
async with Client("http://127.0.0.1:8000/mcp") as client:
async with Client("http://127.0.0.1:8000/mcp/") as client:
await client.ping()
if __name__ == "__main__":
@ -168,7 +168,7 @@ New applications should use Streamable HTTP transport instead.
Server-Sent Events (SSE) is an HTTP-based protocol for server-to-client streaming. While FastMCP still supports SSE, it is deprecated and Streamable HTTP is preferred for new projects.
To run a server using SSE, you can use the `run()` method with the `transport` argument set to `"sse"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and with default SSE path (`/sse`) and message path (`/messages/`).
To run a server using SSE, you can use the `run()` method with the `transport` argument set to `"sse"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and with default SSE path (`/sse/`) and message path (`/messages/`).
<CodeGroup>
```python {6} server.py
@ -186,7 +186,7 @@ from fastmcp.client.transports import SSETransport
async def example():
async with Client(
transport=SSETransport("http://127.0.0.1:8000/sse")
transport=SSETransport("http://127.0.0.1:8000/sse/")
) as client:
await client.ping()

View file

@ -1,178 +1,296 @@
{
"$schema": "https://mintlify.com/docs.json",
"appearance": {
"default": "system",
"strict": false
"$schema": "https://mintlify.com/docs.json",
"appearance": {
"default": "system",
"strict": false
},
"background": {
"color": {
"dark": "#222831",
"light": "#EEEEEE"
},
"background": {
"color": {
"dark": "#222831",
"light": "#EEEEEE"
},
"decoration": "windows"
},
"banner": {
"content": "[FastMCP Cloud](https://fastmcp.link/x0Kyhy2) is coming!"
},
"colors": {
"dark": "#f72585",
"light": "#4cc9f0",
"primary": "#2d00f7"
},
"description": "The fast, Pythonic way to build MCP servers and clients.",
"favicon": {
"dark": "/assets/favicon.ico",
"light": "/assets/favicon.ico"
},
"footer": {
"socials": {
"bluesky": "https://bsky.app/profile/jlowin.dev",
"github": "https://github.com/jlowin/fastmcp",
"x": "https://x.com/jlowin"
}
},
"integrations": {
"ga4": {
"measurementId": "G-64R5W1TJXG"
}
},
"name": "FastMCP",
"navbar": {
"primary": {
"href": "https://github.com/jlowin/fastmcp",
"type": "github"
}
},
"navigation": {
"decoration": "windows"
},
"banner": {
"content": "[FastMCP Cloud](https://fastmcp.link/x0Kyhy2) is coming!"
},
"colors": {
"dark": "#f72585",
"light": "#4cc9f0",
"primary": "#2d00f7"
},
"description": "The fast, Pythonic way to build MCP servers and clients.",
"favicon": {
"dark": "/assets/favicon.ico",
"light": "/assets/favicon.ico"
},
"footer": {
"socials": {
"bluesky": "https://bsky.app/profile/jlowin.dev",
"github": "https://github.com/jlowin/fastmcp",
"x": "https://x.com/jlowin"
}
},
"integrations": {
"ga4": {
"measurementId": "G-64R5W1TJXG"
}
},
"name": "FastMCP",
"navbar": {
"primary": {
"href": "https://github.com/jlowin/fastmcp",
"type": "github"
}
},
"navigation": {
"tabs": [
{
"tab": "Documentation",
"anchors": [
{
"anchor": "Documentation",
"groups": [
{
"group": "Get Started",
"pages": [
"getting-started/welcome",
"getting-started/installation",
"getting-started/quickstart",
"updates"
]
},
{
"group": "Servers",
"pages": [
"servers/fastmcp",
{
"group": "Core Components",
"icon": "toolbox",
"pages": [
"servers/tools",
"servers/resources",
"servers/prompts",
"servers/context"
]
},
{
"group": "Authentication",
"icon": "shield-check",
"pages": [
"servers/auth/bearer"
]
},
"servers/middleware",
"servers/openapi",
"servers/proxy",
"servers/composition",
{
"group": "Deployment",
"icon": "upload",
"pages": [
"deployment/running-server",
"deployment/asgi"
]
}
]
},
{
"group": "Clients",
"pages": [
"clients/client",
"clients/transports",
{
"group": "Authentication",
"icon": "user-shield",
"pages": [
"clients/auth/oauth",
"clients/auth/bearer"
]
},
"clients/advanced-features"
]
},
{
"group": "Integrations",
"pages": [
"integrations/anthropic",
"integrations/claude-desktop",
"integrations/openai",
"integrations/gemini",
"integrations/contrib"
]
},
{
"group": "Patterns",
"pages": [
"patterns/tool-transformation",
"patterns/decorating-methods",
"patterns/http-requests",
"patterns/testing",
"patterns/cli"
]
}
],
"icon": "book"
},
{
"anchor": "Tutorials",
"groups": [
{
"group": "MCP",
"pages": [
"tutorials/mcp",
"tutorials/create-mcp-server",
"tutorials/rest-api"
]
}
],
"icon": "graduation-cap"
},
{
"anchor": "Changelog",
"icon": "list-check",
{
"anchor": "Documentation",
"groups": [
{
"group": "Get Started",
"pages": [
"changelog"
"getting-started/welcome",
"getting-started/installation",
"getting-started/quickstart"
]
},
{
"anchor": "Community",
"icon": "users",
},
{
"group": "Servers",
"pages": [
"community/showcase"
"servers/server",
{
"group": "Core Components",
"icon": "toolbox",
"pages": [
"servers/tools",
"servers/resources",
"servers/prompts",
"servers/context"
]
},
{
"group": "Authentication",
"icon": "shield-check",
"pages": ["servers/auth/bearer"]
},
"servers/middleware",
"servers/openapi",
"servers/proxy",
"servers/composition",
{
"group": "Deployment",
"icon": "upload",
"pages": ["deployment/running-server", "deployment/asgi"]
}
]
}
},
{
"group": "Clients",
"pages": [
"clients/client",
{
"group": "Core Operations",
"icon": "handshake",
"pages": [
"clients/tools",
"clients/resources",
"clients/prompts"
]
},
{
"group": "Advanced Features",
"icon": "stars",
"pages": [
"clients/logging",
"clients/progress",
"clients/sampling",
"clients/roots"
]
},
"clients/transports",
{
"group": "Authentication",
"icon": "user-shield",
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
}
]
},
{
"group": "Integrations",
"pages": [
"integrations/anthropic",
"integrations/claude-desktop",
"integrations/openai",
"integrations/gemini",
"integrations/contrib"
]
},
{
"group": "Patterns",
"pages": [
"patterns/tool-transformation",
"patterns/decorating-methods",
"patterns/http-requests",
"patterns/testing",
"patterns/cli"
]
},
{
"group": "Tutorials",
"pages": [
"tutorials/mcp",
"tutorials/create-mcp-server",
"tutorials/rest-api"
]
}
],
"icon": "book"
},
{
"anchor": "What's New",
"pages": ["updates", "changelog"]
},
{
"anchor": "Community",
"icon": "users",
"pages": ["community/showcase"]
}
]
},
{
"tab": "SDK Reference",
"anchors": [
{
"anchor": "Python SDK",
"icon": "python",
"pages": [
"python-sdk/fastmcp-exceptions",
"python-sdk/fastmcp-settings",
{
"group": "fastmcp.cli",
"pages": [
"python-sdk/fastmcp-cli-__init__",
"python-sdk/fastmcp-cli-claude",
"python-sdk/fastmcp-cli-cli",
"python-sdk/fastmcp-cli-run"
]
},
{
"group": "fastmcp.client",
"pages": [
"python-sdk/fastmcp-client-__init__",
{
"group": "auth",
"pages": [
"python-sdk/fastmcp-client-auth-__init__",
"python-sdk/fastmcp-client-auth-bearer",
"python-sdk/fastmcp-client-auth-oauth"
]
},
"python-sdk/fastmcp-client-client",
"python-sdk/fastmcp-client-logging",
"python-sdk/fastmcp-client-oauth_callback",
"python-sdk/fastmcp-client-progress",
"python-sdk/fastmcp-client-roots",
"python-sdk/fastmcp-client-sampling",
"python-sdk/fastmcp-client-transports"
]
},
{
"group": "fastmcp.prompts",
"pages": [
"python-sdk/fastmcp-prompts-__init__",
"python-sdk/fastmcp-prompts-prompt",
"python-sdk/fastmcp-prompts-prompt_manager"
]
},
{
"group": "fastmcp.resources",
"pages": [
"python-sdk/fastmcp-resources-__init__",
"python-sdk/fastmcp-resources-resource",
"python-sdk/fastmcp-resources-resource_manager",
"python-sdk/fastmcp-resources-template",
"python-sdk/fastmcp-resources-types"
]
},
{
"group": "fastmcp.server",
"pages": [
"python-sdk/fastmcp-server-__init__",
{
"group": "auth",
"pages": [
"python-sdk/fastmcp-server-auth-__init__",
"python-sdk/fastmcp-server-auth-auth",
{
"group": "providers",
"pages": [
"python-sdk/fastmcp-server-auth-providers-__init__",
"python-sdk/fastmcp-server-auth-providers-bearer",
"python-sdk/fastmcp-server-auth-providers-bearer_env",
"python-sdk/fastmcp-server-auth-providers-in_memory"
]
}
]
},
"python-sdk/fastmcp-server-context",
"python-sdk/fastmcp-server-dependencies",
"python-sdk/fastmcp-server-http",
"python-sdk/fastmcp-server-middleware",
"python-sdk/fastmcp-server-openapi",
"python-sdk/fastmcp-server-proxy",
"python-sdk/fastmcp-server-server"
]
},
{
"group": "fastmcp.tools",
"pages": [
"python-sdk/fastmcp-tools-__init__",
"python-sdk/fastmcp-tools-tool",
"python-sdk/fastmcp-tools-tool_manager",
"python-sdk/fastmcp-tools-tool_transform"
]
},
{
"group": "fastmcp.utilities",
"pages": [
"python-sdk/fastmcp-utilities-__init__",
"python-sdk/fastmcp-utilities-cache",
"python-sdk/fastmcp-utilities-components",
"python-sdk/fastmcp-utilities-exceptions",
"python-sdk/fastmcp-utilities-http",
"python-sdk/fastmcp-utilities-json_schema",
"python-sdk/fastmcp-utilities-logging",
"python-sdk/fastmcp-utilities-mcp_config",
"python-sdk/fastmcp-utilities-openapi",
"python-sdk/fastmcp-utilities-types"
]
}
]
}
]
}
]
},
"redirects": [
{
"destination": "/servers/proxy",
"source": "/patterns/proxy"
},
"redirects": [
{
"destination": "/servers/proxy",
"source": "/patterns/proxy"
},
{
"destination": "/servers/composition",
"source": "/patterns/composition"
}
],
"search": {
"prompt": "Search the docs..."
},
"theme": "mint"
}
{
"destination": "/servers/composition",
"source": "/patterns/composition"
}
],
"search": {
"prompt": "Search the docs..."
},
"theme": "mint"
}

View file

@ -3,7 +3,7 @@ title: Anthropic API + FastMCP
sidebarTitle: Anthropic API
description: Call FastMCP servers from the Anthropic API
icon: message-smile
tag: "New!"
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -3,7 +3,7 @@ title: Gemini SDK + FastMCP
sidebarTitle: Gemini SDK
description: Call FastMCP servers from the Google Gemini SDK
icon: message-smile
tag: "New!"
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -3,7 +3,7 @@ title: OpenAI API + FastMCP
sidebarTitle: OpenAI API
description: Call FastMCP servers from the OpenAI API
icon: message-smile
tag: "New!"
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -21,6 +21,7 @@ fastmcp --help
| `run` | Run a FastMCP server directly | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `dev` | Run a server with the MCP Inspector for testing | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `install` | Install a server in the Claude desktop app | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `inspect` | Generate a JSON report about a FastMCP server | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `version` | Display version information | N/A |
## Command Details
@ -179,6 +180,29 @@ fastmcp install server.py:my_server
fastmcp install server.py:my_server -n "My Analysis Server" --with pandas
```
### `inspect`
<VersionBadge version="2.9.0" />
Generate a detailed JSON report about a FastMCP server, including information about its tools, prompts, resources, and capabilities.
```bash
fastmcp inspect server.py
```
The command supports the same server specification format as `run` and `install`:
```bash
# Auto-detect server object
fastmcp inspect server.py
# Specify server object
fastmcp inspect server.py:my_server
# Custom output location
fastmcp inspect server.py --output analysis.json
```
### `version`
Display version information about FastMCP and related components.

View file

@ -0,0 +1,9 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.cli`
FastMCP CLI package.

View file

@ -0,0 +1,43 @@
---
title: claude
sidebarTitle: claude
---
# `fastmcp.cli.claude`
Claude app integration utilities.
## Functions
### `get_claude_config_path`
```python
get_claude_config_path() -> Path | None
```
Get the Claude config directory based on platform.
### `update_claude_config`
```python
update_claude_config(file_spec: str, server_name: str) -> bool
```
Add or update a FastMCP server in Claude's configuration.
**Args:**
- `file_spec`: Path to the server file, optionally with \:object suffix
- `server_name`: Name for the server in Claude's config
- `with_editable`: Optional directory to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables. These are merged with
any existing variables, with new values taking precedence.
**Raises:**
- `RuntimeError`: If Claude Desktop's config directory is not found, indicating
Claude Desktop may not be installed or properly set up.

View file

@ -0,0 +1,65 @@
---
title: cli
sidebarTitle: cli
---
# `fastmcp.cli.cli`
FastMCP CLI tools.
## Functions
### `version`
```python
version(ctx: Context)
```
### `dev`
```python
dev(server_spec: str = typer.Argument(..., help='Python file to run, optionally with :object suffix'), with_editable: Annotated[Path | None, typer.Option('--with-editable', '-e', help='Directory containing pyproject.toml to install in editable mode', exists=True, file_okay=False, resolve_path=True)] = None, with_packages: Annotated[list[str], typer.Option('--with', help='Additional packages to install')] = [], inspector_version: Annotated[str | None, typer.Option('--inspector-version', help='Version of the MCP Inspector to use')] = None, ui_port: Annotated[int | None, typer.Option('--ui-port', help='Port for the MCP Inspector UI')] = None, server_port: Annotated[int | None, typer.Option('--server-port', help='Port for the MCP Inspector Proxy server')] = None) -> None
```
Run a MCP server with the MCP Inspector.
### `run`
```python
run(ctx: typer.Context, server_spec: str = typer.Argument(..., help='Python file, object specification (file:obj), or URL'), transport: Annotated[str | None, typer.Option('--transport', '-t', help='Transport protocol to use (stdio, streamable-http, or sse)')] = None, host: Annotated[str | None, typer.Option('--host', help='Host to bind to when using http transport (default: 127.0.0.1)')] = None, port: Annotated[int | None, typer.Option('--port', '-p', help='Port to bind to when using http transport (default: 8000)')] = None, log_level: Annotated[str | None, typer.Option('--log-level', '-l', help='Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)')] = None) -> None
```
Run a MCP server or connect to a remote one.
The server can be specified in three ways:
1. Module approach: server.py - runs the module directly, looking for an object named mcp/server/app.
2. Import approach: server.py:app - imports and runs the specified server object.
3. URL approach: http://server-url - connects to a remote server and creates a proxy.
Note: This command runs the server directly. You are responsible for ensuring
all dependencies are available.
Server arguments can be passed after -- :
fastmcp run server.py -- --config config.json --debug
### `install`
```python
install(server_spec: str = typer.Argument(..., help='Python file to run, optionally with :object suffix'), server_name: Annotated[str | None, typer.Option('--name', '-n', help="Custom name for the server (defaults to server's name attribute or file name)")] = None, with_editable: Annotated[Path | None, typer.Option('--with-editable', '-e', help='Directory containing pyproject.toml to install in editable mode', exists=True, file_okay=False, resolve_path=True)] = None, with_packages: Annotated[list[str], typer.Option('--with', help='Additional packages to install')] = [], env_vars: Annotated[list[str], typer.Option('--env-var', '-v', help='Environment variables in KEY=VALUE format')] = [], env_file: Annotated[Path | None, typer.Option('--env-file', '-f', help='Load environment variables from a .env file', exists=True, file_okay=True, dir_okay=False, resolve_path=True)] = None) -> None
```
Install a MCP server in the Claude desktop app.
Environment variables are preserved once added and only updated if new values
are explicitly provided.

View file

@ -0,0 +1,106 @@
---
title: run
sidebarTitle: run
---
# `fastmcp.cli.run`
FastMCP run command implementation.
## Functions
### `is_url`
```python
is_url(path: str) -> bool
```
Check if a string is a URL.
### `parse_file_path`
```python
parse_file_path(server_spec: str) -> tuple[Path, str | None]
```
Parse a file path that may include a server object specification.
**Args:**
- `server_spec`: Path to file, optionally with \:object suffix
**Returns:**
- Tuple of (file_path, server_object)
### `import_server`
```python
import_server(file: Path, server_object: str | None = None) -> Any
```
Import a MCP server from a file.
**Args:**
- `file`: Path to the file
- `server_object`: Optional object name in format "module\:object" or just "object"
**Returns:**
- The server object
### `create_client_server`
```python
create_client_server(url: str) -> Any
```
Create a FastMCP server from a client URL.
**Args:**
- `url`: The URL to connect to
**Returns:**
- A FastMCP server instance
### `import_server_with_args`
```python
import_server_with_args(file: Path, server_object: str | None = None, server_args: list[str] | None = None) -> Any
```
Import a server with optional command line arguments.
**Args:**
- `file`: Path to the server file
- `server_object`: Optional server object name
- `server_args`: Optional command line arguments to inject
**Returns:**
- The imported server object
### `run_command`
```python
run_command(server_spec: str, transport: str | None = None, host: str | None = None, port: int | None = None, log_level: str | None = None, server_args: list[str] | None = None) -> None
```
Run a MCP server or connect to a remote one.
**Args:**
- `server_spec`: Python file, object specification (file\:obj), or URL
- `transport`: Transport protocol to use
- `host`: Host to bind to when using http transport
- `port`: Port to bind to when using http transport
- `log_level`: Log level
- `server_args`: Additional arguments to pass to the server

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.client`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.client.auth`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,18 @@
---
title: bearer
sidebarTitle: bearer
---
# `fastmcp.client.auth.bearer`
## Classes
### `BearerAuth`
**Methods:**
#### `auth_flow`
```python
auth_flow(self, request)
```

View file

@ -0,0 +1,102 @@
---
title: oauth
sidebarTitle: oauth
---
# `fastmcp.client.auth.oauth`
## Functions
### `default_cache_dir`
```python
default_cache_dir() -> Path
```
### `OAuth`
```python
OAuth(mcp_url: str, scopes: str | list[str] | None = None, client_name: str = 'FastMCP Client', token_storage_cache_dir: Path | None = None, additional_client_metadata: dict[str, Any] | None = None) -> _MCPOAuthClientProvider
```
Create an OAuthClientProvider for an MCP server.
This is intended to be provided to the `auth` parameter of an
httpx.AsyncClient (or appropriate FastMCP client/transport instance)
**Args:**
- `mcp_url`: Full URL to the MCP endpoint (e.g. "http\://host/mcp/sse/")
- `scopes`: OAuth scopes to request. Can be a
- `client_name`: Name for this client during registration
- `token_storage_cache_dir`: Directory for FileTokenStorage
- `additional_client_metadata`: Extra fields for OAuthClientMetadata
**Returns:**
- OAuthClientProvider
## Classes
### `ServerOAuthMetadata`
More flexible OAuth metadata model that accepts broader ranges of values
than the restrictive MCP standard model.
This handles real-world OAuth servers like PayPal that may support
additional methods not in the MCP specification.
### `OAuthClientProvider`
OAuth client provider with more flexible OAuth metadata discovery.
### `FileTokenStorage`
File-based token storage implementation for OAuth credentials and tokens.
Implements the mcp.client.auth.TokenStorage protocol.
Each instance is tied to a specific server URL for proper token isolation.
**Methods:**
#### `get_base_url`
```python
get_base_url(url: str) -> str
```
Extract the base URL (scheme + host) from a URL.
#### `get_cache_key`
```python
get_cache_key(self) -> str
```
Generate a safe filesystem key from the server's base URL.
#### `clear`
```python
clear(self) -> None
```
Clear all cached data for this server.
#### `clear_all`
```python
clear_all(cls, cache_dir: Path | None = None) -> None
```
Clear all cached data for all servers.

View file

@ -0,0 +1,94 @@
---
title: client
sidebarTitle: client
---
# `fastmcp.client.client`
## Classes
### `Client`
MCP client that delegates connection management to a Transport instance.
The Client class is responsible for MCP protocol logic, while the Transport
handles connection establishment and management. Client provides methods for
working with resources, prompts, tools and other MCP capabilities.
Args:
transport: Connection source specification, which can be:
- ClientTransport: Direct transport instance
- FastMCP: In-process FastMCP server
- AnyUrl | str: URL to connect to
- Path: File path for local socket
- MCPConfig: MCP server configuration
- dict: Transport configuration
roots: Optional RootsList or RootsHandler for filesystem access
sampling_handler: Optional handler for sampling requests
log_handler: Optional handler for log messages
message_handler: Optional handler for protocol messages
progress_handler: Optional handler for progress notifications
timeout: Optional timeout for requests (seconds or timedelta)
init_timeout: Optional timeout for initial connection (seconds or timedelta).
Set to 0 to disable. If None, uses the value in the FastMCP global settings.
Examples:
```python # Connect to FastMCP server client =
Client("http://localhost:8080")
async with client:
# List available resources resources = await client.list_resources()
# Call a tool result = await client.call_tool("my_tool", {"param":
"value"})
```
**Methods:**
#### `session`
```python
session(self) -> ClientSession
```
Get the current active session. Raises RuntimeError if not connected.
#### `initialize_result`
```python
initialize_result(self) -> mcp.types.InitializeResult
```
Get the result of the initialization request.
#### `set_roots`
```python
set_roots(self, roots: RootsList | RootsHandler) -> None
```
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
#### `set_sampling_callback`
```python
set_sampling_callback(self, sampling_callback: SamplingHandler) -> None
```
Set the sampling callback for the client.
#### `is_connected`
```python
is_connected(self) -> bool
```
Check if the client is currently connected.

View file

@ -0,0 +1,14 @@
---
title: logging
sidebarTitle: logging
---
# `fastmcp.client.logging`
## Functions
### `create_log_callback`
```python
create_log_callback(handler: LogHandler | None = None) -> LoggingFnT
```

View file

@ -0,0 +1,63 @@
---
title: oauth_callback
sidebarTitle: oauth_callback
---
# `fastmcp.client.oauth_callback`
OAuth callback server for handling authorization code flows.
This module provides a reusable callback server that can handle OAuth redirects
and display styled responses to users.
## Functions
### `create_callback_html`
```python
create_callback_html(message: str, is_success: bool = True, title: str = 'FastMCP OAuth', server_url: str | None = None) -> str
```
Create a styled HTML response for OAuth callbacks.
### `create_oauth_callback_server`
```python
create_oauth_callback_server(port: int, callback_path: str = '/callback', server_url: str | None = None, response_future: asyncio.Future | None = None) -> Server
```
Create an OAuth callback server.
**Args:**
- `port`: The port to run the server on
- `callback_path`: The path to listen for OAuth redirects on
- `server_url`: Optional server URL to display in success messages
- `response_future`: Optional future to resolve when OAuth callback is received
**Returns:**
- Configured uvicorn Server instance (not yet running)
## Classes
### `CallbackResponse`
**Methods:**
#### `from_dict`
```python
from_dict(cls, data: dict[str, str]) -> CallbackResponse
```
#### `to_dict`
```python
to_dict(self) -> dict[str, str]
```

View file

@ -0,0 +1,8 @@
---
title: progress
sidebarTitle: progress
---
# `fastmcp.client.progress`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,20 @@
---
title: roots
sidebarTitle: roots
---
# `fastmcp.client.roots`
## Functions
### `convert_roots_list`
```python
convert_roots_list(roots: RootsList) -> list[mcp.types.Root]
```
### `create_roots_callback`
```python
create_roots_callback(handler: RootsList | RootsHandler) -> ListRootsFnT
```

View file

@ -0,0 +1,14 @@
---
title: sampling
sidebarTitle: sampling
---
# `fastmcp.client.sampling`
## Functions
### `create_sampling_callback`
```python
create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT
```

View file

@ -0,0 +1,191 @@
---
title: transports
sidebarTitle: transports
---
# `fastmcp.client.transports`
## Functions
### `infer_transport`
```python
infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
```
Infer the appropriate transport type from the given transport argument.
This function attempts to infer the correct transport type from the provided
argument, handling various input types and converting them to the appropriate
ClientTransport subclass.
The function supports these input types:
- ClientTransport: Used directly without modification
- FastMCP or FastMCP1Server: Creates an in-memory FastMCPTransport
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.
For MCPConfig with multiple servers, a composite client is created where each server
is mounted with its name as prefix. This allows accessing tools and resources from multiple
servers through a single unified client interface, using naming patterns like
`servername_toolname` for tools and `protocol://servername/path` for resources.
If the MCPConfig contains only one server, a direct connection is established without prefixing.
Examples:
```python
# Connect to a local Python script
transport = infer_transport("my_script.py")
# Connect to a remote server via HTTP
transport = infer_transport("http://example.com/mcp")
# Connect to multiple servers using MCPConfig
config = {
"mcpServers": {
"weather": {"url": "http://weather.example.com/mcp"},
"calendar": {"url": "http://calendar.example.com/mcp"}
}
}
transport = infer_transport(config)
```
## Classes
### `SessionKwargs`
Keyword arguments for the MCP ClientSession constructor.
### `ClientTransport`
Abstract base class for different MCP client transport mechanisms.
A Transport is responsible for establishing and managing connections
to an MCP server, and providing a ClientSession within an async context.
### `WSTransport`
Transport implementation that connects to an MCP server via WebSockets.
### `SSETransport`
Transport implementation that connects to an MCP server via Server-Sent Events.
### `StreamableHttpTransport`
Transport implementation that connects to an MCP server via Streamable HTTP Requests.
### `StdioTransport`
Base transport for connecting to an MCP server via subprocess with stdio.
This is a base class that can be subclassed for specific command-based
transports like Python, Node, Uvx, etc.
### `PythonStdioTransport`
Transport for running Python scripts.
### `FastMCPStdioTransport`
Transport for running FastMCP servers using the FastMCP CLI.
### `NodeStdioTransport`
Transport for running Node.js scripts.
### `UvxStdioTransport`
Transport for running commands via the uvx tool.
### `NpxStdioTransport`
Transport for running commands via the npx tool.
### `FastMCPTransport`
In-memory transport for FastMCP servers.
This transport connects directly to a FastMCP server instance in the same
Python process. It works with both FastMCP 2.x servers and FastMCP 1.0
servers from the low-level MCP SDK. This is particularly useful for unit
tests or scenarios where client and server run in the same runtime.
### `MCPConfigTransport`
Transport for connecting to one or more MCP servers defined in an MCPConfig.
This transport provides a unified interface to multiple MCP servers defined in an MCPConfig
object or dictionary matching the MCPConfig schema. It supports two key scenarios:
1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
all servers on a single FastMCP instance, with each server's name used as its mounting prefix.
In the multi-server case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
and resources with the pattern `protocol://{server_name}/path/to/resource`.
This is particularly useful for creating clients that need to interact with multiple specialized
MCP servers through a single interface, simplifying client code.
Examples:
```python
from fastmcp import Client
from fastmcp.utilities.mcp_config import MCPConfig
# Create a config with multiple servers
config = {
"mcpServers": {
"weather": {
"url": "https://weather-api.example.com/mcp",
"transport": "streamable-http"
},
"calendar": {
"url": "https://calendar-api.example.com/mcp",
"transport": "streamable-http"
}
}
}
# Create a client with the config
client = Client(config)
async with client:
# Access tools with prefixes
weather = await client.call_tool("weather_get_forecast", {"city": "London"})
events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})
# Access resources with prefixed URIs
icons = await client.read_resource("weather://weather/icons/sunny")
```

View file

@ -0,0 +1,65 @@
---
title: exceptions
sidebarTitle: exceptions
---
# `fastmcp.exceptions`
Custom exceptions for FastMCP.
## Classes
### `FastMCPError`
Base error for FastMCP.
### `ValidationError`
Error in validating parameters or return values.
### `ResourceError`
Error in resource operations.
### `ToolError`
Error in tool operations.
### `PromptError`
Error in prompt operations.
### `InvalidSignature`
Invalid signature for use with FastMCP.
### `ClientError`
Error in client operations.
### `NotFoundError`
Object not found.
### `DisabledError`
Object is disabled.

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.prompts`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,84 @@
---
title: prompt
sidebarTitle: prompt
---
# `fastmcp.prompts.prompt`
Base classes for FastMCP prompts.
## Functions
### `Message`
```python
Message(content: str | MCPContent, role: Role | None = None, **kwargs: Any) -> PromptMessage
```
A user-friendly constructor for PromptMessage.
## Classes
### `PromptArgument`
An argument that can be passed to a prompt.
### `Prompt`
A prompt template that can be rendered with parameters.
**Methods:**
#### `to_mcp_prompt`
```python
to_mcp_prompt(self, **overrides: Any) -> MCPPrompt
```
Convert the prompt to an MCP prompt.
#### `from_function`
```python
from_function(fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionPrompt
```
Create a Prompt from a function.
The function can return:
- A string (converted to a message)
- A Message object
- A dict (converted to a message)
- A sequence of any of the above
### `FunctionPrompt`
A prompt that is a function.
**Methods:**
#### `from_function`
```python
from_function(cls, fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionPrompt
```
Create a Prompt from a function.
The function can return:
- A string (converted to a message)
- A Message object
- A dict (converted to a message)
- A sequence of any of the above

View file

@ -0,0 +1,43 @@
---
title: prompt_manager
sidebarTitle: prompt_manager
---
# `fastmcp.prompts.prompt_manager`
## Classes
### `PromptManager`
Manages FastMCP prompts.
**Methods:**
#### `mount`
```python
mount(self, server: MountedServer) -> None
```
Adds a mounted server as a source for prompts.
#### `add_prompt_from_fn`
```python
add_prompt_from_fn(self, fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None) -> FunctionPrompt
```
Create a prompt from a function.
#### `add_prompt`
```python
add_prompt(self, prompt: Prompt) -> Prompt
```
Add a prompt to the manager.

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.resources`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,90 @@
---
title: resource
sidebarTitle: resource
---
# `fastmcp.resources.resource`
Base classes and interfaces for FastMCP resources.
## Classes
### `Resource`
Base class for all resources.
**Methods:**
#### `from_function`
```python
from_function(fn: Callable[[], Any], uri: str | AnyUrl, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResource
```
#### `set_default_mime_type`
```python
set_default_mime_type(cls, mime_type: str | None) -> str
```
Set default MIME type if not provided.
#### `set_default_name`
```python
set_default_name(self) -> Self
```
Set default name from URI if not provided.
#### `to_mcp_resource`
```python
to_mcp_resource(self, **overrides: Any) -> MCPResource
```
Convert the resource to an MCPResource.
#### `key`
```python
key(self) -> str
```
The key of the component. This is used for internal bookkeeping
and may reflect e.g. prefixes or other identifiers. You should not depend on
keys having a certain value, as the same tool loaded from different
hierarchies of servers may have different keys.
### `FunctionResource`
A resource that defers data loading by wrapping a function.
The function is only called when the resource is read, allowing for lazy loading
of potentially expensive data. This is particularly useful when listing resources,
as the function won't be called until the resource is actually accessed.
The function can return:
- str for text content (default)
- bytes for binary content
- other types will be converted to JSON
**Methods:**
#### `from_function`
```python
from_function(cls, fn: Callable[[], Any], uri: str | AnyUrl, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResource
```
Create a FunctionResource from a function.

View file

@ -0,0 +1,111 @@
---
title: resource_manager
sidebarTitle: resource_manager
---
# `fastmcp.resources.resource_manager`
Resource manager functionality.
## Classes
### `ResourceManager`
Manages FastMCP resources.
**Methods:**
#### `mount`
```python
mount(self, server: MountedServer) -> None
```
Adds a mounted server as a source for resources and templates.
#### `add_resource_or_template_from_fn`
```python
add_resource_or_template_from_fn(self, fn: Callable[..., Any], uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> Resource | ResourceTemplate
```
Add a resource or template to the manager from a function.
**Args:**
- `fn`: The function to register as a resource or template
- `uri`: The URI for the resource or template
- `name`: Optional name for the resource or template
- `description`: Optional description of the resource or template
- `mime_type`: Optional MIME type for the resource or template
- `tags`: Optional set of tags for categorizing the resource or template
**Returns:**
- The added resource or template. If a resource or template with the same URI already exists,
- returns the existing resource or template.
#### `add_resource_from_fn`
```python
add_resource_from_fn(self, fn: Callable[..., Any], uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> Resource
```
Add a resource to the manager from a function.
**Args:**
- `fn`: The function to register as a resource
- `uri`: The URI for the resource
- `name`: Optional name for the resource
- `description`: Optional description of the resource
- `mime_type`: Optional MIME type for the resource
- `tags`: Optional set of tags for categorizing the resource
**Returns:**
- The added resource. If a resource with the same URI already exists,
- returns the existing resource.
#### `add_resource`
```python
add_resource(self, resource: Resource) -> Resource
```
Add a resource to the manager.
**Args:**
- `resource`: A Resource instance to add. The resource's .key attribute
will be used as the storage key. To overwrite it, call
Resource.with_key() before calling this method.
#### `add_template_from_fn`
```python
add_template_from_fn(self, fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> ResourceTemplate
```
Create a template from a function.
#### `add_template`
```python
add_template(self, template: ResourceTemplate) -> ResourceTemplate
```
Add a template to the manager.
**Args:**
- `template`: A ResourceTemplate instance to add. The template's .key attribute
will be used as the storage key. To overwrite it, call
ResourceTemplate.with_key() before calling this method.
**Returns:**
- The added template. If a template with the same URI already exists,
- returns the existing template.

View file

@ -0,0 +1,104 @@
---
title: template
sidebarTitle: template
---
# `fastmcp.resources.template`
Resource template functionality.
## Functions
### `build_regex`
```python
build_regex(template: str) -> re.Pattern
```
### `match_uri_template`
```python
match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None
```
## Classes
### `ResourceTemplate`
A template for dynamically creating resources.
**Methods:**
#### `from_function`
```python
from_function(fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResourceTemplate
```
#### `set_default_mime_type`
```python
set_default_mime_type(cls, mime_type: str | None) -> str
```
Set default MIME type if not provided.
#### `matches`
```python
matches(self, uri: str) -> dict[str, Any] | None
```
Check if URI matches template and extract parameters.
#### `to_mcp_template`
```python
to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate
```
Convert the resource template to an MCPResourceTemplate.
#### `from_mcp_template`
```python
from_mcp_template(cls, mcp_template: MCPResourceTemplate) -> ResourceTemplate
```
Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object.
#### `key`
```python
key(self) -> str
```
The key of the component. This is used for internal bookkeeping
and may reflect e.g. prefixes or other identifiers. You should not depend on
keys having a certain value, as the same tool loaded from different
hierarchies of servers may have different keys.
### `FunctionResourceTemplate`
A template for dynamically creating resources.
**Methods:**
#### `from_function`
```python
from_function(cls, fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResourceTemplate
```
Create a template from a function.

View file

@ -0,0 +1,83 @@
---
title: types
sidebarTitle: types
---
# `fastmcp.resources.types`
Concrete resource implementations.
## Classes
### `TextResource`
A resource that reads from a string.
### `BinaryResource`
A resource that reads from bytes.
### `FileResource`
A resource that reads from a file.
Set is_binary=True to read file as binary data instead of text.
**Methods:**
#### `validate_absolute_path`
```python
validate_absolute_path(cls, path: Path) -> Path
```
Ensure path is absolute.
#### `set_binary_from_mime_type`
```python
set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool
```
Set is_binary based on mime_type if not explicitly set.
### `HttpResource`
A resource that reads from an HTTP endpoint.
### `DirectoryResource`
A resource that lists files in a directory.
**Methods:**
#### `validate_absolute_path`
```python
validate_absolute_path(cls, path: Path) -> Path
```
Ensure path is absolute.
#### `list_files`
```python
list_files(self) -> list[Path]
```
List files in the directory.

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.server`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.server.auth`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,10 @@
---
title: auth
sidebarTitle: auth
---
# `fastmcp.server.auth.auth`
## Classes
### `OAuthProvider`

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.server.auth.providers`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,69 @@
---
title: bearer
sidebarTitle: bearer
---
# `fastmcp.server.auth.providers.bearer`
## Classes
### `JWKData`
JSON Web Key data structure.
### `JWKSData`
JSON Web Key Set data structure.
### `RSAKeyPair`
**Methods:**
#### `generate`
```python
generate(cls) -> 'RSAKeyPair'
```
Generate an RSA key pair for testing.
**Returns:**
- (private_key_pem, public_key_pem)
#### `create_token`
```python
create_token(self, subject: str = 'fastmcp-user', issuer: str = 'https://fastmcp.example.com', audience: str | list[str] | None = None, scopes: list[str] | None = None, expires_in_seconds: int = 3600, additional_claims: dict[str, Any] | None = None, kid: str | None = None) -> str
```
Generate a test JWT token for testing purposes.
**Args:**
- `private_key_pem`: RSA private key in PEM format
- `subject`: Subject claim (usually user ID)
- `issuer`: Issuer claim
- `audience`: Audience claim - can be a string or list of strings (optional)
- `scopes`: List of scopes to include
- `expires_in_seconds`: Token expiration time in seconds
- `additional_claims`: Any additional claims to include
- `kid`: Key ID for JWKS lookup (optional)
**Returns:**
- Signed JWT token string
### `BearerAuthProvider`
Simple JWT Bearer Token validator for hosted MCP servers.
Uses RS256 asymmetric encryption. Supports either static public key
or JWKS URI for key rotation.
Note that this provider DOES NOT permit client registration or revocation, or any OAuth flows.
It is intended to be used with a control plane that manages clients and tokens.

View file

@ -0,0 +1,22 @@
---
title: bearer_env
sidebarTitle: bearer_env
---
# `fastmcp.server.auth.providers.bearer_env`
## Classes
### `EnvBearerAuthProviderSettings`
Settings for the BearerAuthProvider.
### `EnvBearerAuthProvider`
A BearerAuthProvider that loads settings from environment variables. Any
providing setting will always take precedence over the environment
variables.

View file

@ -0,0 +1,15 @@
---
title: in_memory
sidebarTitle: in_memory
---
# `fastmcp.server.auth.providers.in_memory`
## Classes
### `InMemoryOAuthProvider`
An in-memory OAuth provider for testing purposes.
It simulates the OAuth 2.1 flow locally without external calls.

View file

@ -0,0 +1,118 @@
---
title: context
sidebarTitle: context
---
# `fastmcp.server.context`
## Functions
### `set_context`
```python
set_context(context: Context) -> Generator[Context, None, None]
```
## Classes
### `Context`
Context object providing access to MCP capabilities.
This provides a cleaner interface to MCP's RequestContext functionality.
It gets injected into tool and resource functions that request it via type hints.
To use context in a tool function, add a parameter with the Context type annotation:
```python
@server.tool
def my_tool(x: int, ctx: Context) -> str:
# Log messages to the client
ctx.info(f"Processing {x}")
ctx.debug("Debug info")
ctx.warning("Warning message")
ctx.error("Error message")
# Report progress
ctx.report_progress(50, 100, "Processing")
# Access resources
data = ctx.read_resource("resource://data")
# Get request info
request_id = ctx.request_id
client_id = ctx.client_id
return str(x)
```
The context parameter name can be anything as long as it's annotated with Context.
The context is optional - tools that don't need it can omit the parameter.
**Methods:**
#### `request_context`
```python
request_context(self) -> RequestContext
```
Access to the underlying request context.
If called outside of a request context, this will raise a ValueError.
#### `client_id`
```python
client_id(self) -> str | None
```
Get the client ID if available.
#### `request_id`
```python
request_id(self) -> str
```
Get the unique ID for this request.
#### `session_id`
```python
session_id(self) -> str | None
```
Get the MCP session ID for HTTP transports.
Returns the session ID that can be used as a key for session-based
data storage (e.g., Redis) to share data between tool calls within
the same client session.
**Returns:**
- The session ID for HTTP transports (SSE, StreamableHTTP), or None
- for stdio and in-memory transports which don't use session IDs.
#### `session`
```python
session(self)
```
Access to the underlying session for advanced usage.
#### `get_http_request`
```python
get_http_request(self) -> Request
```
Get the active starlette request.

View file

@ -0,0 +1,36 @@
---
title: dependencies
sidebarTitle: dependencies
---
# `fastmcp.server.dependencies`
## Functions
### `get_context`
```python
get_context() -> Context
```
### `get_http_request`
```python
get_http_request() -> Request
```
### `get_http_headers`
```python
get_http_headers(include_all: bool = False) -> dict[str, str]
```
Extract headers from the current HTTP request if available.
Never raises an exception, even if there is no active HTTP request (in which case
an empty dict is returned).
By default, strips problematic headers like `content-length` that cause issues if forwarded to downstream clients.
If `include_all` is True, all headers are returned.

View file

@ -0,0 +1,113 @@
---
title: http
sidebarTitle: http
---
# `fastmcp.server.http`
## Functions
### `set_http_request`
```python
set_http_request(request: Request) -> Generator[Request, None, None]
```
### `setup_auth_middleware_and_routes`
```python
setup_auth_middleware_and_routes(auth: OAuthProvider) -> tuple[list[Middleware], list[BaseRoute], list[str]]
```
Set up authentication middleware and routes if auth is enabled.
**Args:**
- `auth`: The OAuthProvider authorization server provider
**Returns:**
- Tuple of (middleware, auth_routes, required_scopes)
### `create_base_app`
```python
create_base_app(routes: list[BaseRoute], middleware: list[Middleware], debug: bool = False, lifespan: Callable | None = None) -> StarletteWithLifespan
```
Create a base Starlette app with common middleware and routes.
**Args:**
- `routes`: List of routes to include in the app
- `middleware`: List of middleware to include in the app
- `debug`: Whether to enable debug mode
- `lifespan`: Optional lifespan manager for the app
**Returns:**
- A Starlette application
### `create_sse_app`
```python
create_sse_app(server: FastMCP[LifespanResultT], message_path: str, sse_path: str, auth: OAuthProvider | None = None, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
```
Return an instance of the SSE server app.
**Args:**
- `server`: The FastMCP server instance
- `message_path`: Path for SSE messages
- `sse_path`: Path for SSE connections
- `auth`: Optional auth provider
- `debug`: Whether to enable debug mode
- `routes`: Optional list of custom routes
- `middleware`: Optional list of middleware
Returns:
A Starlette application with RequestContextMiddleware
### `create_streamable_http_app`
```python
create_streamable_http_app(server: FastMCP[LifespanResultT], streamable_http_path: str, event_store: EventStore | None = None, auth: OAuthProvider | None = None, json_response: bool = False, stateless_http: bool = False, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
```
Return an instance of the StreamableHTTP server app.
**Args:**
- `server`: The FastMCP server instance
- `streamable_http_path`: Path for StreamableHTTP connections
- `event_store`: Optional event store for session management
- `auth`: Optional auth provider
- `json_response`: Whether to use JSON response format
- `stateless_http`: Whether to use stateless mode (new transport per request)
- `debug`: Whether to enable debug mode
- `routes`: Optional list of custom routes
- `middleware`: Optional list of middleware
**Returns:**
- A Starlette application with StreamableHTTP support
## Classes
### `StarletteWithLifespan`
**Methods:**
#### `lifespan`
```python
lifespan(self) -> Lifespan
```
### `RequestContextMiddleware`
Middleware that stores each request in a ContextVar

View file

@ -0,0 +1,56 @@
---
title: middleware
sidebarTitle: middleware
---
# `fastmcp.server.middleware`
## Functions
### `make_middleware_wrapper`
```python
make_middleware_wrapper(middleware: Middleware, call_next: CallNext[T, R]) -> CallNext[T, R]
```
Create a wrapper that applies a single middleware to a context. The
closure bakes in the middleware and call_next function, so it can be
passed to other functions that expect a call_next function.
## Classes
### `CallNext`
### `CallToolResult`
### `ListToolsResult`
### `ListResourcesResult`
### `ListResourceTemplatesResult`
### `ListPromptsResult`
### `ServerResultProtocol`
### `MiddlewareContext`
Unified context for all middleware operations.
**Methods:**
#### `copy`
```python
copy(self, **kwargs: Any) -> MiddlewareContext[T]
```
### `Middleware`
Base class for FastMCP middleware with dispatching hooks.

View file

@ -0,0 +1,58 @@
---
title: openapi
sidebarTitle: openapi
---
# `fastmcp.server.openapi`
FastMCP server implementation for OpenAPI integration.
## Classes
### `MCPType`
Type of FastMCP component to create from a route.
### `RouteType`
Deprecated: Use MCPType instead.
This enum is kept for backward compatibility and will be removed in a future version.
### `RouteMap`
Mapping configuration for HTTP routes to FastMCP component types.
### `OpenAPITool`
Tool implementation for OpenAPI endpoints.
### `OpenAPIResource`
Resource implementation for OpenAPI endpoints.
### `OpenAPIResourceTemplate`
Resource template implementation for OpenAPI endpoints.
### `FastMCPOpenAPI`
FastMCP server implementation that creates components from an OpenAPI schema.
This class parses an OpenAPI specification and creates appropriate FastMCP components
(Tools, Resources, ResourceTemplates) based on route mappings.

View file

@ -0,0 +1,101 @@
---
title: proxy
sidebarTitle: proxy
---
# `fastmcp.server.proxy`
## Classes
### `ProxyToolManager`
A ToolManager that sources its tools from a remote client in addition to local and mounted tools.
### `ProxyResourceManager`
A ResourceManager that sources its resources from a remote client in addition to local and mounted resources.
### `ProxyPromptManager`
A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts.
### `ProxyTool`
A Tool that represents and executes a tool on a remote server.
**Methods:**
#### `from_mcp_tool`
```python
from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
```
Factory method to create a ProxyTool from a raw MCP tool schema.
### `ProxyResource`
A Resource that represents and reads a resource from a remote server.
**Methods:**
#### `from_mcp_resource`
```python
from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> ProxyResource
```
Factory method to create a ProxyResource from a raw MCP resource schema.
### `ProxyTemplate`
A ResourceTemplate that represents and creates resources from a remote server template.
**Methods:**
#### `from_mcp_template`
```python
from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate
```
Factory method to create a ProxyTemplate from a raw MCP template schema.
### `ProxyPrompt`
A Prompt that represents and renders a prompt from a remote server.
**Methods:**
#### `from_mcp_prompt`
```python
from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt
```
Factory method to create a ProxyPrompt from a raw MCP prompt schema.
### `FastMCPProxy`
A FastMCP server that acts as a proxy to a remote MCP-compliant server.
It uses specialized managers that fulfill requests via an HTTP client.

View file

@ -0,0 +1,542 @@
---
title: server
sidebarTitle: server
---
# `fastmcp.server.server`
FastMCP - A more ergonomic interface for MCP servers.
## Functions
### `add_resource_prefix`
```python
add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
```
Add a prefix to a resource URI.
Args:
uri: The original resource URI
prefix: The prefix to add
Returns:
The resource URI with the prefix added
Examples:
>>> add_resource_prefix("resource://path/to/resource", "prefix")
"resource://prefix/path/to/resource" # with new style
>>> add_resource_prefix("resource://path/to/resource", "prefix")
"prefix+resource://path/to/resource" # with legacy style
>>> add_resource_prefix("resource:///absolute/path", "prefix")
"resource://prefix//absolute/path" # with new style
Raises:
ValueError: If the URI doesn't match the expected protocol://path format
### `remove_resource_prefix`
```python
remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
```
Remove a prefix from a resource URI.
Args:
uri: The resource URI with a prefix
prefix: The prefix to remove
prefix_format: The format of the prefix to remove
Returns:
The resource URI with the prefix removed
Examples:
>>> remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
"resource://path/to/resource" # with new style
>>> remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
"resource://path/to/resource" # with legacy style
>>> remove_resource_prefix("resource://prefix//absolute/path", "prefix")
"resource:///absolute/path" # with new style
Raises:
ValueError: If the URI doesn't match the expected protocol://path format
### `has_resource_prefix`
```python
has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
```
Check if a resource URI has a specific prefix.
Args:
uri: The resource URI to check
prefix: The prefix to look for
Returns:
True if the URI has the specified prefix, False otherwise
Examples:
>>> has_resource_prefix("resource://prefix/path/to/resource", "prefix")
True # with new style
>>> has_resource_prefix("prefix+resource://path/to/resource", "prefix")
True # with legacy style
>>> has_resource_prefix("resource://other/path/to/resource", "prefix")
False
Raises:
ValueError: If the URI doesn't match the expected protocol://path format
## Classes
### `FastMCP`
**Methods:**
#### `settings`
```python
settings(self) -> Settings
```
#### `name`
```python
name(self) -> str
```
#### `instructions`
```python
instructions(self) -> str | None
```
#### `run`
```python
run(self, transport: Literal['stdio', 'streamable-http', 'sse'] | None = None, **transport_kwargs: Any) -> None
```
Run the FastMCP server. Note this is a synchronous function.
**Args:**
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
#### `add_middleware`
```python
add_middleware(self, middleware: Middleware) -> None
```
#### `custom_route`
```python
custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True)
```
Decorator to register a custom HTTP route on the FastMCP server.
Allows adding arbitrary HTTP endpoints outside the standard MCP protocol,
which can be useful for OAuth callbacks, health checks, or admin APIs.
The handler function must be an async function that accepts a Starlette
Request and returns a Response.
**Args:**
- `path`: URL path for the route (e.g., "/oauth/callback")
- `methods`: List of HTTP methods to support (e.g., ["GET", "POST"])
- `name`: Optional name for the route (to reference this route with
Starlette's reverse URL lookup feature)
- `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
#### `add_tool`
```python
add_tool(self, tool: Tool) -> None
```
Add a tool to the server.
The tool function can optionally request a Context object by adding a parameter
with the Context type annotation. See the @tool decorator for examples.
**Args:**
- `tool`: The Tool instance to register
#### `remove_tool`
```python
remove_tool(self, name: str) -> None
```
Remove a tool from the server.
**Args:**
- `name`: The name of the tool to remove
**Raises:**
- `NotFoundError`: If the tool is not found
#### `tool`
```python
tool(self, name_or_fn: AnyFunction) -> FunctionTool
```
#### `tool`
```python
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
```
#### `tool`
```python
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
```
Decorator to register a tool.
Tools can optionally request a Context object by adding a parameter with the
Context type annotation. The context provides access to MCP capabilities like
logging, progress reporting, and resource access.
This decorator supports multiple calling patterns:
- @server.tool (without parentheses)
- @server.tool (with empty parentheses)
- @server.tool("custom_name") (with name as first argument)
- @server.tool(name="custom_name") (with name as keyword argument)
- server.tool(function, name="custom_name") (direct function call)
**Args:**
- `name_or_fn`: Either a function (when used as @tool), a string name, or None
- `name`: Optional name for the tool (keyword-only, alternative to name_or_fn)
- `description`: Optional description of what the tool does
- `tags`: Optional set of tags for categorizing the tool
- `annotations`: Optional annotations about the tool's behavior (e.g. {"is_async"\: True})
- `exclude_args`: Optional list of argument names to exclude from the tool schema
- `enabled`: Optional boolean to enable or disable the tool
#### `add_resource`
```python
add_resource(self, resource: Resource) -> None
```
Add a resource to the server.
**Args:**
- `resource`: A Resource instance to add
#### `add_template`
```python
add_template(self, template: ResourceTemplate) -> None
```
Add a resource template to the server.
**Args:**
- `template`: A ResourceTemplate instance to add
#### `add_resource_fn`
```python
add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
```
Add a resource or template to the server from a function.
If the URI contains parameters (e.g. "resource://{param}") or the function
has parameters, it will be registered as a template resource.
**Args:**
- `fn`: The function to register as a resource
- `uri`: The URI for the resource
- `name`: Optional name for the resource
- `description`: Optional description of the resource
- `mime_type`: Optional MIME type for the resource
- `tags`: Optional set of tags for categorizing the resource
#### `resource`
```python
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
```
Decorator to register a function as a resource.
The function will be called when the resource is read to generate its content.
The function can return:
- str for text content
- bytes for binary content
- other types will be converted to JSON
Resources can optionally request a Context object by adding a parameter with the
Context type annotation. The context provides access to MCP capabilities like
logging, progress reporting, and session information.
If the URI contains parameters (e.g. "resource://{param}") or the function
has parameters, it will be registered as a template resource.
**Args:**
- `uri`: URI for the resource (e.g. "resource\://my-resource" or "resource\://{param}")
- `name`: Optional name for the resource
- `description`: Optional description of the resource
- `mime_type`: Optional MIME type for the resource
- `tags`: Optional set of tags for categorizing the resource
- `enabled`: Optional boolean to enable or disable the resource
#### `add_prompt`
```python
add_prompt(self, prompt: Prompt) -> None
```
Add a prompt to the server.
**Args:**
- `prompt`: A Prompt instance to add
#### `prompt`
```python
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
```
#### `prompt`
```python
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
```
#### `prompt`
```python
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
```
Decorator to register a prompt.
Prompts can optionally request a Context object by adding a parameter with the
Context type annotation. The context provides access to MCP capabilities like
logging, progress reporting, and session information.
This decorator supports multiple calling patterns:
- @server.prompt (without parentheses)
- @server.prompt() (with empty parentheses)
- @server.prompt("custom_name") (with name as first argument)
- @server.prompt(name="custom_name") (with name as keyword argument)
- server.prompt(function, name="custom_name") (direct function call)
Args:
name_or_fn: Either a function (when used as @prompt), a string name, or None
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
description: Optional description of what the prompt does
tags: Optional set of tags for categorizing the prompt
enabled: Optional boolean to enable or disable the prompt
Example:
@server.prompt
def analyze_table(table_name: str) -> list\[Message]:
schema = read_table_schema(table_name)
return [
{
"role": "user",
"content": f"Analyze this schema:
{schema}"
}
]
@server.prompt()
def analyze_with_context(table_name: str, ctx: Context) -> list\[Message]:
ctx.info(f"Analyzing table {table_name}")
schema = read_table_schema(table_name)
return [
{
"role": "user",
"content": f"Analyze this schema:
{schema}"
}
]
@server.prompt("custom_name")
def analyze_file(path: str) -> list\[Message]:
content = await read_file(path)
return [
{
"role": "user",
"content": {
"type": "resource",
"resource": {
"uri": f"file://{path}",
"text": content
}
}
}
]
@server.prompt(name="custom_name")
def another_prompt(data: str) -> list\[Message]:
return [{"role": "user", "content": data}]
# Direct function call
server.prompt(my_function, name="custom_name")
#### `sse_app`
```python
sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
```
Create a Starlette app for the SSE server.
**Args:**
- `path`: The path to the SSE endpoint
- `message_path`: The path to the message endpoint
- `middleware`: A list of middleware to apply to the app
#### `streamable_http_app`
```python
streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
```
Create a Starlette app for the StreamableHTTP server.
**Args:**
- `path`: The path to the StreamableHTTP endpoint
- `middleware`: A list of middleware to apply to the app
#### `http_app`
```python
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['streamable-http', 'sse'] = 'streamable-http') -> StarletteWithLifespan
```
Create a Starlette app using the specified HTTP transport.
**Args:**
- `path`: The path for the HTTP endpoint
- `middleware`: A list of middleware to apply to the app
- `transport`: Transport protocol to use - either "streamable-http" (default) or "sse"
**Returns:**
- A Starlette application configured with the specified transport
#### `mount`
```python
mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
```
Mount another FastMCP server on this server with an optional prefix.
Unlike importing (with import_server), mounting establishes a dynamic connection
between servers. When a client interacts with a mounted server's objects through
the parent server, requests are forwarded to the mounted server in real-time.
This means changes to the mounted server are immediately reflected when accessed
through the parent.
When a server is mounted with a prefix:
- Tools from the mounted server are accessible with prefixed names.
Example: If server has a tool named "get_weather", it will be available as "prefix_get_weather".
- Resources are accessible with prefixed URIs.
Example: If server has a resource with URI "weather://forecast", it will be available as
"weather://prefix/forecast".
- Templates are accessible with prefixed URI templates.
Example: If server has a template with URI "weather://location/{id}", it will be available
as "weather://prefix/location/{id}".
- Prompts are accessible with prefixed names.
Example: If server has a prompt named "weather_prompt", it will be available as
"prefix_weather_prompt".
When a server is mounted without a prefix (prefix=None), its tools, resources, templates,
and prompts are accessible with their original names. Multiple servers can be mounted
without prefixes, and they will be tried in order until a match is found.
There are two modes for mounting servers:
1. Direct mounting (default when server has no custom lifespan): The parent server
directly accesses the mounted server's objects in-memory for better performance.
In this mode, no client lifecycle events occur on the mounted server, including
lifespan execution.
2. Proxy mounting (default when server has a custom lifespan): The parent server
treats the mounted server as a separate entity and communicates with it via a
Client transport. This preserves all client-facing behaviors, including lifespan
execution, but with slightly higher overhead.
**Args:**
- `server`: The FastMCP server to mount.
- `prefix`: Optional prefix to use for the mounted server's objects. If None,
the server's objects are accessible with their original names.
- `as_proxy`: Whether to treat the mounted server as a proxy. If None (default),
automatically determined based on whether the server has a custom lifespan
(True if it has a custom lifespan, False otherwise).
- `tool_separator`: Deprecated. Separator character for tool names.
- `resource_separator`: Deprecated. Separator character for resource URIs.
- `prompt_separator`: Deprecated. Separator character for prompt names.
#### `from_openapi`
```python
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI
```
Create a FastMCP server from an OpenAPI specification.
#### `from_fastapi`
```python
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI
```
Create a FastMCP server from a FastAPI application.
#### `as_proxy`
```python
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
```
Create a FastMCP proxy server for the given backend.
The ``backend`` argument can be either an existing :class:`~fastmcp.client.Client`
instance or any value accepted as the ``transport`` argument of
:class:`~fastmcp.client.Client`. This mirrors the convenience of the
``Client`` constructor.
#### `from_client`
```python
from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
```
Create a FastMCP proxy server from a FastMCP client.
### `MountedServer`

View file

@ -0,0 +1,59 @@
---
title: settings
sidebarTitle: settings
---
# `fastmcp.settings`
## Classes
### `ExtendedEnvSettingsSource`
A special EnvSettingsSource that allows for multiple env var prefixes to be used.
Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
**Methods:**
#### `get_field_value`
```python
get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]
```
### `ExtendedSettingsConfigDict`
### `Settings`
FastMCP settings.
**Methods:**
#### `settings_customise_sources`
```python
settings_customise_sources(cls, settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource) -> tuple[PydanticBaseSettingsSource, ...]
```
#### `settings`
```python
settings(self) -> Self
```
This property is for backwards compatibility with FastMCP < 2.8.0,
which accessed fastmcp.settings.settings
#### `setup_logging`
```python
setup_logging(self) -> Self
```
Finalize the settings.

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.tools`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,68 @@
---
title: tool
sidebarTitle: tool
---
# `fastmcp.tools.tool`
## Functions
### `default_serializer`
```python
default_serializer(data: Any) -> str
```
## Classes
### `Tool`
Internal tool registration info.
**Methods:**
#### `to_mcp_tool`
```python
to_mcp_tool(self, **overrides: Any) -> MCPTool
```
#### `from_function`
```python
from_function(fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> FunctionTool
```
Create a Tool from a function.
#### `from_tool`
```python
from_tool(cls, tool: Tool, transform_fn: Callable[..., Any] | None = None, name: str | None = None, transform_args: dict[str, ArgTransform] | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool
```
### `FunctionTool`
**Methods:**
#### `from_function`
```python
from_function(cls, fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> FunctionTool
```
Create a Tool from a function.
### `ParsedFunction`
**Methods:**
#### `from_function`
```python
from_function(cls, fn: Callable[..., Any], exclude_args: list[str] | None = None, validate: bool = True) -> ParsedFunction
```

View file

@ -0,0 +1,58 @@
---
title: tool_manager
sidebarTitle: tool_manager
---
# `fastmcp.tools.tool_manager`
## Classes
### `ToolManager`
Manages FastMCP tools.
**Methods:**
#### `mount`
```python
mount(self, server: MountedServer) -> None
```
Adds a mounted server as a source for tools.
#### `add_tool_from_fn`
```python
add_tool_from_fn(self, fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, exclude_args: list[str] | None = None) -> Tool
```
Add a tool to the server.
#### `add_tool`
```python
add_tool(self, tool: Tool) -> Tool
```
Register a tool with the server.
#### `remove_tool`
```python
remove_tool(self, key: str) -> None
```
Remove a tool from the server.
**Args:**
- `key`: The key of the tool to remove
**Raises:**
- `NotFoundError`: If the tool is not found

View file

@ -0,0 +1,117 @@
---
title: tool_transform
sidebarTitle: tool_transform
---
# `fastmcp.tools.tool_transform`
## Classes
### `ArgTransform`
Configuration for transforming a parent tool's argument.
This class allows fine-grained control over how individual arguments are transformed
when creating a new tool from an existing one. You can rename arguments, change their
descriptions, add default values, or hide them from clients while passing constants.
Attributes:
name: New name for the argument. Use None to keep original name, or ... for no change.
description: New description for the argument. Use None to remove description, or ... for no change.
default: New default value for the argument. Use ... for no change.
default_factory: Callable that returns a default value. Cannot be used with default.
type: New type for the argument. Use ... for no change.
hide: If True, hide this argument from clients but pass a constant value to parent.
required: If True, make argument required (remove default). Use ... for no change.
examples: Examples for the argument. Use ... for no change.
Examples:
# Rename argument 'old_name' to 'new_name'
ArgTransform(name="new_name")
# Change description only
ArgTransform(description="Updated description")
# Add a default value (makes argument optional)
ArgTransform(default=42)
# Add a default factory (makes argument optional)
ArgTransform(default_factory=lambda: time.time())
# Change the type
ArgTransform(type=str)
# Hide the argument entirely from clients
ArgTransform(hide=True)
# Hide argument but pass a constant value to parent
ArgTransform(hide=True, default="constant_value")
# Hide argument but pass a factory-generated value to parent
ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex)
# Make an optional parameter required (removes any default)
ArgTransform(required=True)
# Combine multiple transformations
ArgTransform(name="new_name", description="New desc", default=None, type=int)
### `TransformedTool`
A tool that is transformed from another tool.
This class represents a tool that has been created by transforming another tool.
It supports argument renaming, schema modification, custom function injection,
and provides context for the forward() and forward_raw() functions.
The transformation can be purely schema-based (argument renaming, dropping, etc.)
or can include a custom function that uses forward() to call the parent tool
with transformed arguments.
**Methods:**
#### `from_tool`
```python
from_tool(cls, tool: Tool, name: str | None = None, description: str | None = None, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool
```
Create a transformed tool from a parent tool.
**Args:**
- `tool`: The parent tool to transform.
- `transform_fn`: Optional custom function. Can use forward() and forward_raw()
to call the parent tool. Functions with **kwargs receive transformed
argument names.
- `name`: New name for the tool. Defaults to parent tool's name.
- `transform_args`: Optional transformations for parent tool arguments.
Only specified arguments are transformed, others pass through unchanged\:
- str\: Simple rename
- ArgTransform\: Complex transformation (rename/description/default/drop)
- None\: Drop the argument
- `description`: New description. Defaults to parent's description.
- `tags`: New tags. Defaults to parent's tags.
- `annotations`: New annotations. Defaults to parent's annotations.
- `serializer`: New serializer. Defaults to parent's serializer.
**Returns:**
- TransformedTool with the specified transformations.
Examples:
- # Transform specific arguments only
- Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged
- # Custom function with partial transforms
- async def custom(x: int, y: int) -> str:
result = await forward(x=x, y=y)
return f"Custom: {result}"
- Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"})
- # Using **kwargs (gets all args, transformed and untransformed)
- async def flexible(**kwargs) -> str:
result = await forward(**kwargs)
return f"Got: {kwargs}"
- Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})

View file

@ -0,0 +1,9 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.utilities`
FastMCP utility modules.

View file

@ -0,0 +1,30 @@
---
title: cache
sidebarTitle: cache
---
# `fastmcp.utilities.cache`
## Classes
### `TimedCache`
**Methods:**
#### `set`
```python
set(self, key: Any, value: Any) -> None
```
#### `get`
```python
get(self, key: Any) -> Any
```
#### `clear`
```python
clear(self) -> None
```

View file

@ -0,0 +1,52 @@
---
title: components
sidebarTitle: components
---
# `fastmcp.utilities.components`
## Classes
### `FastMCPComponent`
Base class for FastMCP tools, prompts, resources, and resource templates.
**Methods:**
#### `key`
```python
key(self) -> str
```
The key of the component. This is used for internal bookkeeping
and may reflect e.g. prefixes or other identifiers. You should not depend on
keys having a certain value, as the same tool loaded from different
hierarchies of servers may have different keys.
#### `with_key`
```python
with_key(self, key: str) -> Self
```
#### `enable`
```python
enable(self) -> None
```
Enable the component.
#### `disable`
```python
disable(self) -> None
```
Disable the component.

View file

@ -0,0 +1,20 @@
---
title: exceptions
sidebarTitle: exceptions
---
# `fastmcp.utilities.exceptions`
## Functions
### `iter_exc`
```python
iter_exc(group: BaseExceptionGroup)
```
### `get_catch_handlers`
```python
get_catch_handlers() -> Mapping[type[BaseException] | Iterable[type[BaseException]], Callable[[BaseExceptionGroup[Any]], Any]]
```

View file

@ -0,0 +1,18 @@
---
title: http
sidebarTitle: http
---
# `fastmcp.utilities.http`
## Functions
### `find_available_port`
```python
find_available_port() -> int
```
Find an available port by letting the OS assign one.

View file

@ -0,0 +1,25 @@
---
title: json_schema
sidebarTitle: json_schema
---
# `fastmcp.utilities.json_schema`
## Functions
### `compress_schema`
```python
compress_schema(schema: dict, prune_params: list[str] | None = None, prune_defs: bool = True, prune_additional_properties: bool = True, prune_titles: bool = False) -> dict
```
Remove the given parameters from the schema.
**Args:**
- `schema`: The schema to compress
- `prune_params`: List of parameter names to remove from properties
- `prune_defs`: Whether to remove unused definitions
- `prune_additional_properties`: Whether to remove additionalProperties\: false
- `prune_titles`: Whether to remove title fields from the schema

View file

@ -0,0 +1,41 @@
---
title: logging
sidebarTitle: logging
---
# `fastmcp.utilities.logging`
Logging utilities for FastMCP.
## Functions
### `get_logger`
```python
get_logger(name: str) -> logging.Logger
```
Get a logger nested under FastMCP namespace.
**Args:**
- `name`: the name of the logger, which will be prefixed with 'FastMCP.'
**Returns:**
- a configured logger instance
### `configure_logging`
```python
configure_logging(level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] | int = 'INFO', logger: logging.Logger | None = None, enable_rich_tracebacks: bool = True) -> None
```
Configure logging for FastMCP.
**Args:**
- `logger`: the logger to configure
- `level`: the log level to use

View file

@ -0,0 +1,50 @@
---
title: mcp_config
sidebarTitle: mcp_config
---
# `fastmcp.utilities.mcp_config`
## Functions
### `infer_transport_type_from_url`
```python
infer_transport_type_from_url(url: str | AnyUrl) -> Literal['streamable-http', 'sse']
```
Infer the appropriate transport type from the given URL.
## Classes
### `StdioMCPServer`
**Methods:**
#### `to_transport`
```python
to_transport(self) -> StdioTransport
```
### `RemoteMCPServer`
**Methods:**
#### `to_transport`
```python
to_transport(self) -> StreamableHttpTransport | SSETransport
```
### `MCPConfig`
**Methods:**
#### `from_dict`
```python
from_dict(cls, config: dict[str, Any]) -> MCPConfig
```

View file

@ -0,0 +1,118 @@
---
title: openapi
sidebarTitle: openapi
---
# `fastmcp.utilities.openapi`
## Functions
### `parse_openapi_to_http_routes`
```python
parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute]
```
Parses an OpenAPI schema dictionary into a list of HTTPRoute objects
using the openapi-pydantic library.
Supports both OpenAPI 3.0.x and 3.1.x versions.
### `clean_schema_for_display`
```python
clean_schema_for_display(schema: JsonSchema | None) -> JsonSchema | None
```
Clean up a schema dictionary for display by removing internal/complex fields.
### `generate_example_from_schema`
```python
generate_example_from_schema(schema: JsonSchema | None) -> Any
```
Generate a simple example value from a JSON schema dictionary.
Very basic implementation focusing on types.
### `format_json_for_description`
```python
format_json_for_description(data: Any, indent: int = 2) -> str
```
Formats Python data as a JSON string block for markdown.
### `format_description_with_responses`
```python
format_description_with_responses(base_description: str, responses: dict[str, Any], parameters: list[ParameterInfo] | None = None, request_body: RequestBodyInfo | None = None) -> str
```
Formats the base description string with response, parameter, and request body information.
**Args:**
- `base_description`: The initial description to be formatted.
- `responses`: A dictionary of response information, keyed by status code.
- `parameters`: A list of parameter information,
including path and query parameters. Each parameter includes details such as name,
location, whether it is required, and a description.
- `request_body`: Information about the request body,
including its description, whether it is required, and its content schema.
**Returns:**
- The formatted description string with additional details about responses, parameters,
- and the request body.
## Classes
### `ParameterInfo`
Represents a single parameter for an HTTP operation in our IR.
### `RequestBodyInfo`
Represents the request body for an HTTP operation in our IR.
### `ResponseInfo`
Represents response information in our IR.
### `HTTPRoute`
Intermediate Representation for a single OpenAPI operation.
### `OpenAPIParser`
Unified parser for OpenAPI schemas with generic type parameters to handle both 3.0 and 3.1.
**Methods:**
#### `parse`
```python
parse(self) -> list[HTTPRoute]
```
Parse the OpenAPI schema into HTTP routes.

View file

@ -0,0 +1,112 @@
---
title: types
sidebarTitle: types
---
# `fastmcp.utilities.types`
Common types used across FastMCP.
## Functions
### `get_cached_typeadapter`
```python
get_cached_typeadapter(cls: T) -> TypeAdapter[T]
```
TypeAdapters are heavy objects, and in an application context we'd typically
create them once in a global scope and reuse them as often as possible.
However, this isn't feasible for user-generated functions. Instead, we use a
cache to minimize the cost of creating them as much as possible.
### `issubclass_safe`
```python
issubclass_safe(cls: type, base: type) -> bool
```
Check if cls is a subclass of base, even if cls is a type variable.
### `is_class_member_of_type`
```python
is_class_member_of_type(cls: type, base: type) -> bool
```
Check if cls is a member of base, even if cls is a type variable.
Base can be a type, a UnionType, or an Annotated type. Generic types are not
considered members (e.g. T is not a member of list\[T]).
### `find_kwarg_by_type`
```python
find_kwarg_by_type(fn: Callable, kwarg_type: type) -> str | None
```
Find the name of the kwarg that is of type kwarg_type.
Includes union types that contain the kwarg_type, as well as Annotated types.
## Classes
### `FastMCPBaseModel`
Base model for FastMCP models.
### `Image`
Helper class for returning images from tools.
**Methods:**
#### `to_image_content`
```python
to_image_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> ImageContent
```
Convert to MCP ImageContent.
### `Audio`
Helper class for returning audio from tools.
**Methods:**
#### `to_audio_content`
```python
to_audio_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> AudioContent
```
### `File`
Helper class for returning audio from tools.
**Methods:**
#### `to_resource_content`
```python
to_resource_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> EmbeddedResource
```

View file

@ -3,7 +3,7 @@ title: Bearer Token Authentication
sidebarTitle: Bearer Auth
description: Secure your FastMCP server's HTTP endpoints by validating JWT Bearer tokens.
icon: key
tag: "New!"
tag: NEW
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -57,6 +57,84 @@ def generate_code_request(language: str, task_description: str) -> PromptMessage
Functions with `*args` or `**kwargs` are not supported as prompts. This restriction exists because FastMCP needs to generate a complete parameter schema for the MCP protocol, which isn't possible with variable argument lists.
</Tip>
### Argument Types
<VersionBadge version="2.9.0" />
The MCP specification requires that all prompt arguments be passed as strings, but FastMCP allows you to use typed annotations for better developer experience. When you use complex types like `list[int]` or `dict[str, str]`, FastMCP:
1. **Automatically converts** string arguments from MCP clients to the expected types
2. **Generates helpful descriptions** showing the exact JSON string format needed
3. **Preserves direct usage** - you can still call prompts with properly typed arguments
Since the MCP specification only allows string arguments, clients need to know what string format to use for complex types. FastMCP solves this by automatically enhancing the argument descriptions with JSON schema information, making it clear to both humans and LLMs how to format their arguments.
<CodeGroup>
```python Python Code
@mcp.prompt
def analyze_data(
numbers: list[int],
metadata: dict[str, str],
threshold: float
) -> str:
"""Analyze numerical data."""
avg = sum(numbers) / len(numbers)
return f"Average: {avg}, above threshold: {avg > threshold}"
```
```json Resulting MCP Prompt
{
"name": "analyze_data",
"description": "Analyze numerical data.",
"arguments": [
{
"name": "numbers",
"description": "Provide as a JSON string matching the following schema: {\"items\":{\"type\":\"integer\"},\"type\":\"array\"}",
"required": true
},
{
"name": "metadata",
"description": "Provide as a JSON string matching the following schema: {\"additionalProperties\":{\"type\":\"string\"},\"type\":\"object\"}",
"required": true
},
{
"name": "threshold",
"description": "Provide as a JSON string matching the following schema: {\"type\":\"number\"}",
"required": true
}
]
}
```
</CodeGroup>
**MCP clients will call this prompt with string arguments:**
```json
{
"numbers": "[1, 2, 3, 4, 5]",
"metadata": "{\"source\": \"api\", \"version\": \"1.0\"}",
"threshold": "2.5"
}
```
**But you can still call it directly with proper types:**
```python
# This also works for direct calls
result = await prompt.render({
"numbers": [1, 2, 3, 4, 5],
"metadata": {"source": "api", "version": "1.0"},
"threshold": 2.5
})
```
<Warning>
Keep your type annotations simple when using this feature. Complex nested types or custom classes may not convert reliably from JSON strings. The automatically generated schema descriptions are the only guidance users receive about the expected format.
Good choices: `list[int]`, `dict[str, str]`, `float`, `bool`
Avoid: Complex Pydantic models, deeply nested structures, custom classes
</Warning>
### Return Values
FastMCP intelligently handles different return types from your prompt function:
@ -78,33 +156,6 @@ def roleplay_scenario(character: str, situation: str) -> list[Message]:
]
```
### Type Annotations
Type annotations are important for prompts. They:
1. Inform FastMCP about the expected types for each parameter.
2. Allow validation of parameters received from clients.
3. Are used to generate the prompt's schema for the MCP protocol.
```python
from pydantic import Field
from typing import Literal, Optional
@mcp.prompt
def generate_content_request(
topic: str = Field(description="The main subject to cover"),
format: Literal["blog", "email", "social"] = "blog",
tone: str = "professional",
word_count: Optional[int] = None
) -> str:
"""Create a request for generating content in a specific format."""
prompt = f"Please write a {format} post about {topic} in a {tone} tone."
if word_count:
prompt += f" It should be approximately {word_count} words long."
return prompt
```
### Required vs. Optional Parameters

View file

@ -2,7 +2,7 @@
title: Resources & Templates
sidebarTitle: Resources
description: Expose data sources and dynamic content generators to your MCP client.
icon: database
icon: folder-open
---
import { VersionBadge } from "/snippets/version-badge.mdx"

View file

@ -1,7 +1,7 @@
---
title: The FastMCP Server
sidebarTitle: FastMCP Servers
description: Learn about the core FastMCP server class and how to run it.
sidebarTitle: Overview
description: The core FastMCP server class for building MCP applications with tools, resources, and prompts.
icon: server
---

View file

@ -103,7 +103,7 @@ from fastmcp import Client
async def main():
# Connect to the MCP server we just created
async with Client("http://127.0.0.1:8000/mcp") as client:
async with Client("http://127.0.0.1:8000/mcp/") as client:
# List the tools that were automatically generated
tools = await client.list_tools()

View file

@ -2,7 +2,7 @@
title: "FastMCP Updates"
sidebarTitle: "Updates"
icon: "sparkles"
tag: "New!"
tag: NEW
---
<Update label="FastMCP 2.8" description="June 11, 2025" tags={["Releases", "Blog Posts"]}>

View file

@ -1,6 +1,8 @@
# Build the project
build:
uv sync
# Run tests
test: build
uv run --frozen pytest -xvs tests
@ -8,5 +10,21 @@ test: build
typecheck:
uv run --frozen pyright
# Serve documentation locally
docs:
cd docs && npx mintlify dev
cd docs && npx mint@latest dev
# Generate API reference documentation for all modules
api-ref-all:
uvx --with-editable . --refresh-package mdxify mdxify@latest --all --root-module fastmcp --anchor-name "SDK Reference"
# Generate API reference for specific modules (e.g., just api-ref prefect.flows prefect.tasks)
api-ref *MODULES:
uvx --with-editable . --refresh-package mdxify mdxify@latest {{MODULES}} --root-module fastmcp --anchor-name "SDK Reference"
# Clean up API reference documentation
api-ref-clean:
rm -rf docs/python-sdk
copy-context:
uvx --with-editable . --refresh-package copychat copychat@latest src/ docs/ -x changelog.mdx -x python-sdk/ -v

View file

@ -1,5 +1,6 @@
"""FastMCP CLI tools."""
import asyncio
import importlib.metadata
import importlib.util
import os
@ -11,6 +12,7 @@ from typing import Annotated
import dotenv
import typer
from pydantic import TypeAdapter
from rich.console import Console
from rich.table import Table
from typer import Context, Exit
@ -19,6 +21,7 @@ import fastmcp
from fastmcp.cli import claude
from fastmcp.cli import run as run_module
from fastmcp.server.server import FastMCP
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
from fastmcp.utilities.logging import get_logger
logger = get_logger("cli")
@ -435,3 +438,98 @@ def install(
else:
logger.error(f"Failed to install {name} in Claude app")
sys.exit(1)
@app.command()
def inspect(
server_spec: str = typer.Argument(
...,
help="Python file to inspect, optionally with :object suffix",
),
output: Annotated[
Path,
typer.Option(
"--output",
"-o",
help="Output file path for the JSON report (default: server-info.json)",
),
] = Path("server-info.json"),
) -> None:
"""Inspect a FastMCP server and generate a JSON report.
This command analyzes a FastMCP server (v1.x or v2.x) and generates
a comprehensive JSON report containing information about the server's
name, instructions, version, tools, prompts, resources, templates,
and capabilities.
Examples:
fastmcp inspect server.py
fastmcp inspect server.py -o report.json
fastmcp inspect server.py:mcp -o analysis.json
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
"""
# Parse the server specification
file, server_object = run_module.parse_file_path(server_spec)
logger.debug(
"Inspecting server",
extra={
"file": str(file),
"server_object": server_object,
"output": str(output),
},
)
try:
# Import the server
server = run_module.import_server(file, server_object)
# Get server information
async def get_info():
return await inspect_fastmcp(server)
try:
# Try to use existing event loop if available
asyncio.get_running_loop()
# If there's already a loop running, we need to run in a thread
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(asyncio.run, get_info())
info = future.result()
except RuntimeError:
# No running loop, safe to use asyncio.run
info = asyncio.run(get_info())
info_json = TypeAdapter(FastMCPInfo).dump_json(info, indent=2)
# Ensure output directory exists
output.parent.mkdir(parents=True, exist_ok=True)
# Write JSON report (always pretty-printed)
with output.open("w", encoding="utf-8") as f:
f.write(info_json.decode("utf-8"))
logger.info(f"Server inspection complete. Report saved to {output}")
# Print summary to console
console.print(
f"[bold green]✓[/bold green] Inspected server: [bold]{info.name}[/bold]"
)
console.print(f" Tools: {len(info.tools)}")
console.print(f" Prompts: {len(info.prompts)}")
console.print(f" Resources: {len(info.resources)}")
console.print(f" Templates: {len(info.templates)}")
console.print(f" Report saved to: [cyan]{output}[/cyan]")
except Exception as e:
logger.error(
f"Failed to inspect server: {e}",
extra={
"server_spec": server_spec,
"error": str(e),
},
)
console.print(f"[bold red]✗[/bold red] Failed to inspect server: {e}")
sys.exit(1)

View file

@ -306,8 +306,7 @@ def OAuth(
httpx.AsyncClient (or appropriate FastMCP client/transport instance)
Args:
mcp_url: Full URL to the MCP endpoint (e.g.,
"http://host/mcp/sse")
mcp_url: Full URL to the MCP endpoint (e.g. "http://host/mcp/sse/")
scopes: OAuth scopes to request. Can be a
space-separated string or a list of strings.
client_name: Name for this client during registration

View file

@ -7,6 +7,7 @@ from typing import Any, Generic, Literal, cast, overload
import anyio
import httpx
import mcp.types
import pydantic_core
from exceptiongroup import catch
from mcp import ClientSession
from mcp.types import ContentBlock
@ -508,13 +509,13 @@ class Client(Generic[ClientTransportT]):
# --- Prompt ---
async def get_prompt_mcp(
self, name: str, arguments: dict[str, str] | None = None
self, name: str, arguments: dict[str, Any] | None = None
) -> mcp.types.GetPromptResult:
"""Send a prompts/get request and return the complete MCP protocol result.
Args:
name (str): The name of the prompt to retrieve.
arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
Returns:
mcp.types.GetPromptResult: The complete response object from the protocol,
@ -523,17 +524,32 @@ class Client(Generic[ClientTransportT]):
Raises:
RuntimeError: If called while the client is not connected.
"""
result = await self.session.get_prompt(name=name, arguments=arguments)
# Serialize arguments for MCP protocol - convert non-string values to JSON
serialized_arguments: dict[str, str] | None = None
if arguments:
serialized_arguments = {}
for key, value in arguments.items():
if isinstance(value, str):
serialized_arguments[key] = value
else:
# Use pydantic_core.to_json for consistent serialization
serialized_arguments[key] = pydantic_core.to_json(value).decode(
"utf-8"
)
result = await self.session.get_prompt(
name=name, arguments=serialized_arguments
)
return result
async def get_prompt(
self, name: str, arguments: dict[str, str] | None = None
self, name: str, arguments: dict[str, Any] | None = None
) -> mcp.types.GetPromptResult:
"""Retrieve a rendered prompt message list from the server.
Args:
name (str): The name of the prompt to retrieve.
arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
Returns:
mcp.types.GetPromptResult: The complete response object from the protocol,

View file

@ -9,6 +9,7 @@ import warnings
from collections.abc import AsyncIterator, Callable
from pathlib import Path
from typing import Any, Literal, TypedDict, TypeVar, cast, overload
from urllib.parse import urlparse, urlunparse
import anyio
import httpx
@ -159,6 +160,13 @@ class SSETransport(ClientTransport):
url = str(url)
if not isinstance(url, str) or not url.startswith("http"):
raise ValueError("Invalid HTTP/S URL provided for SSE.")
# Ensure the URL path ends with a trailing slash to avoid automatic redirects
parsed = urlparse(url)
if not parsed.path.endswith("/"):
parsed = parsed._replace(path=parsed.path + "/")
url = urlunparse(parsed)
self.url = url
self.headers = headers or {}
self._set_auth(auth)
@ -227,6 +235,13 @@ class StreamableHttpTransport(ClientTransport):
url = str(url)
if not isinstance(url, str) or not url.startswith("http"):
raise ValueError("Invalid HTTP/S URL provided for Streamable HTTP.")
# Ensure the URL path ends with a trailing slash to avoid automatic redirects
parsed = urlparse(url)
if not parsed.path.endswith("/"):
parsed = parsed._replace(path=parsed.path + "/")
url = urlunparse(parsed)
self.url = url
self.headers = headers or {}
self._set_auth(auth)

View file

@ -3,15 +3,16 @@
from __future__ import annotations as _annotations
import inspect
import json
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable, Sequence
from typing import TYPE_CHECKING, Any
from typing import Any
import pydantic_core
from mcp.types import ContentBlock, PromptMessage, Role, TextContent
from mcp.types import Prompt as MCPPrompt
from mcp.types import PromptArgument as MCPPromptArgument
from pydantic import Field, TypeAdapter, validate_call
from pydantic import Field, TypeAdapter
from fastmcp.exceptions import PromptError
from fastmcp.server.dependencies import get_context
@ -24,10 +25,6 @@ from fastmcp.utilities.types import (
get_cached_typeadapter,
)
if TYPE_CHECKING:
pass
logger = get_logger(__name__)
@ -180,17 +177,43 @@ class FunctionPrompt(Prompt):
arguments: list[PromptArgument] = []
if "properties" in parameters:
for param_name, param in parameters["properties"].items():
arg_description = param.get("description")
# For non-string parameters, append JSON schema info to help users
# understand the expected format when passing as strings (MCP requirement)
if param_name in sig.parameters:
sig_param = sig.parameters[param_name]
if (
sig_param.annotation != inspect.Parameter.empty
and sig_param.annotation is not str
and param_name != context_kwarg
):
# Get the JSON schema for this specific parameter type
try:
param_adapter = get_cached_typeadapter(sig_param.annotation)
param_schema = param_adapter.json_schema()
# Create compact schema representation
schema_str = json.dumps(param_schema, separators=(",", ":"))
# Append schema info to description
schema_note = f"Provide as a JSON string matching the following schema: {schema_str}"
if arg_description:
arg_description = f"{arg_description}\n\n{schema_note}"
else:
arg_description = schema_note
except Exception:
# If schema generation fails, skip enhancement
pass
arguments.append(
PromptArgument(
name=param_name,
description=param.get("description"),
description=arg_description,
required=param_name in parameters.get("required", []),
)
)
# ensure the arguments are properly cast
fn = validate_call(fn)
return cls(
name=func_name,
description=description,
@ -200,6 +223,60 @@ class FunctionPrompt(Prompt):
fn=fn,
)
def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
"""Convert string arguments to expected types based on function signature."""
from fastmcp.server.context import Context
sig = inspect.signature(self.fn)
converted_kwargs = {}
# Find context parameter name if any
context_param_name = find_kwarg_by_type(self.fn, kwarg_type=Context)
for param_name, param_value in kwargs.items():
if param_name in sig.parameters:
param = sig.parameters[param_name]
# Skip Context parameters - they're handled separately
if param_name == context_param_name:
converted_kwargs[param_name] = param_value
continue
# If parameter has no annotation or annotation is str, pass as-is
if (
param.annotation == inspect.Parameter.empty
or param.annotation is str
):
converted_kwargs[param_name] = param_value
# If argument is not a string, pass as-is (already properly typed)
elif not isinstance(param_value, str):
converted_kwargs[param_name] = param_value
else:
# Try to convert string argument using type adapter
try:
adapter = get_cached_typeadapter(param.annotation)
# Try JSON parsing first for complex types
try:
converted_kwargs[param_name] = adapter.validate_json(
param_value
)
except (ValueError, TypeError, pydantic_core.ValidationError):
# Fallback to direct validation
converted_kwargs[param_name] = adapter.validate_python(
param_value
)
except (ValueError, TypeError, pydantic_core.ValidationError) as e:
# If conversion fails, provide informative error
raise PromptError(
f"Could not convert argument '{param_name}' with value '{param_value}' "
f"to expected type {param.annotation}. Error: {e}"
)
else:
# Parameter not in function signature, pass as-is
converted_kwargs[param_name] = param_value
return converted_kwargs
async def render(
self,
arguments: dict[str, Any] | None = None,
@ -222,6 +299,9 @@ class FunctionPrompt(Prompt):
if context_kwarg and context_kwarg not in kwargs:
kwargs[context_kwarg] = get_context()
# Convert string arguments to expected types when needed
kwargs = self._convert_string_arguments(kwargs)
# Call function and check if result is a coroutine
result = self.fn(**kwargs)
if inspect.iscoroutine(result):

View file

@ -17,7 +17,7 @@ from mcp.shared.auth import (
OAuthClientInformationFull,
OAuthToken,
)
from pydantic import SecretStr
from pydantic import AnyHttpUrl, SecretStr, ValidationError
from fastmcp.server.auth.auth import (
ClientRegistrationOptions,
@ -89,7 +89,7 @@ class RSAKeyPair:
self,
subject: str = "fastmcp-user",
issuer: str = "https://fastmcp.example.com",
audience: str | None = None,
audience: str | list[str] | None = None,
scopes: list[str] | None = None,
expires_in_seconds: int = 3600,
additional_claims: dict[str, Any] | None = None,
@ -102,7 +102,7 @@ class RSAKeyPair:
private_key_pem: RSA private key in PEM format
subject: Subject claim (usually user ID)
issuer: Issuer claim
audience: Audience claim (optional)
audience: Audience claim - can be a string or list of strings (optional)
scopes: List of scopes to include
expires_in_seconds: Token expiration time in seconds
additional_claims: Any additional claims to include
@ -161,7 +161,7 @@ class BearerAuthProvider(OAuthProvider):
public_key: str | None = None,
jwks_uri: str | None = None,
issuer: str | None = None,
audience: str | None = None,
audience: str | list[str] | None = None,
required_scopes: list[str] | None = None,
):
"""
@ -171,7 +171,7 @@ class BearerAuthProvider(OAuthProvider):
public_key: RSA public key in PEM format (for static key)
jwks_uri: URI to fetch keys from (for key rotation)
issuer: Expected issuer claim (optional)
audience: Expected audience claim (optional)
audience: Expected audience claim - can be a string or list of strings (optional)
required_scopes: List of required scopes for access (optional)
"""
if not (public_key or jwks_uri):
@ -179,8 +179,16 @@ class BearerAuthProvider(OAuthProvider):
if public_key and jwks_uri:
raise ValueError("Provide either public_key or jwks_uri, not both")
# Only pass issuer to parent if it's a valid URL, otherwise use default
# This allows the issuer claim validation to work with string issuers per RFC 7519
try:
issuer_url = AnyHttpUrl(issuer) if issuer else "https://fastmcp.example.com"
except ValidationError:
# Issuer is not a valid URL, use default for parent class
issuer_url = "https://fastmcp.example.com"
super().__init__(
issuer_url=issuer or "https://fastmcp.example.com",
issuer_url=issuer_url,
client_registration_options=ClientRegistrationOptions(enabled=False),
revocation_options=RevocationOptions(enabled=False),
required_scopes=required_scopes,
@ -304,11 +312,25 @@ class BearerAuthProvider(OAuthProvider):
# Validate audience if configured
if self.audience:
aud = claims.get("aud")
if isinstance(aud, list):
if self.audience not in aud:
# Handle different combinations of audience types
if isinstance(self.audience, list):
# self.audience is a list - check if any expected audience is present
if isinstance(aud, list):
# Both are lists - check for intersection
if not any(expected in aud for expected in self.audience):
return None
else:
# aud is a string - check if it's in our expected list
if aud not in self.audience:
return None
else:
# self.audience is a string - use original logic
if isinstance(aud, list):
if self.audience not in aud:
return None
elif aud != self.audience:
return None
elif aud != self.audience:
return None
# Extract claims - prefer client_id over sub for OAuth application identification
client_id = claims.get("client_id") or claims.get("sub") or "unknown"

View file

@ -158,6 +158,10 @@ def create_sse_app(
A Starlette application with RequestContextMiddleware
"""
# Ensure the message_path ends with a trailing slash to avoid automatic redirects
if not message_path.endswith("/"):
message_path = message_path + "/"
server_routes: list[BaseRoute] = []
server_middleware: list[Middleware] = []
@ -305,6 +309,10 @@ def create_streamable_http_app(
# Re-raise other RuntimeErrors if they don't match the specific message
raise
# Ensure the streamable_http_path ends with a trailing slash to avoid automatic redirects
if not streamable_http_path.endswith("/"):
streamable_http_path = streamable_http_path + "/"
# Add StreamableHTTP routes with or without auth
if auth:
auth_middleware, auth_routes, required_scopes = (

View file

@ -192,9 +192,9 @@ class Settings(BaseSettings):
# HTTP settings
host: str = "127.0.0.1"
port: int = 8000
sse_path: str = "/sse"
sse_path: str = "/sse/"
message_path: str = "/messages/"
streamable_http_path: str = "/mcp"
streamable_http_path: str = "/mcp/"
debug: bool = False
# error handling

View file

@ -0,0 +1,326 @@
"""Utilities for inspecting FastMCP instances."""
from __future__ import annotations
import importlib.metadata
from dataclasses import dataclass
from typing import Any
from mcp.server.fastmcp import FastMCP as FastMCP1x
import fastmcp
from fastmcp.server.server import FastMCP
@dataclass
class ToolInfo:
"""Information about a tool."""
key: str
name: str
description: str | None
input_schema: dict[str, Any]
annotations: dict[str, Any] | None = None
tags: list[str] | None = None
enabled: bool | None = None
@dataclass
class PromptInfo:
"""Information about a prompt."""
key: str
name: str
description: str | None
arguments: list[dict[str, Any]] | None = None
tags: list[str] | None = None
enabled: bool | None = None
@dataclass
class ResourceInfo:
"""Information about a resource."""
key: str
uri: str
name: str | None
description: str | None
mime_type: str | None = None
tags: list[str] | None = None
enabled: bool | None = None
@dataclass
class TemplateInfo:
"""Information about a resource template."""
key: str
uri_template: str
name: str | None
description: str | None
mime_type: str | None = None
tags: list[str] | None = None
enabled: bool | None = None
@dataclass
class FastMCPInfo:
"""Information extracted from a FastMCP instance."""
name: str
instructions: str | None
fastmcp_version: str
mcp_version: str
server_version: str
tools: list[ToolInfo]
prompts: list[PromptInfo]
resources: list[ResourceInfo]
templates: list[TemplateInfo]
capabilities: dict[str, Any]
async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
"""Extract information from a FastMCP v2.x instance.
Args:
mcp: The FastMCP v2.x instance to inspect
Returns:
FastMCPInfo dataclass containing the extracted information
"""
# Get all the components using FastMCP2's direct methods
tools_dict = await mcp.get_tools()
prompts_dict = await mcp.get_prompts()
resources_dict = await mcp.get_resources()
templates_dict = await mcp.get_resource_templates()
# Extract detailed tool information
tool_infos = []
for key, tool in tools_dict.items():
# Convert to MCP tool to get input schema
mcp_tool = tool.to_mcp_tool(name=key)
tool_infos.append(
ToolInfo(
key=key,
name=tool.name or key,
description=tool.description,
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
annotations=tool.annotations.model_dump() if tool.annotations else None,
tags=list(tool.tags) if tool.tags else None,
enabled=tool.enabled,
)
)
# Extract detailed prompt information
prompt_infos = []
for key, prompt in prompts_dict.items():
prompt_infos.append(
PromptInfo(
key=key,
name=prompt.name or key,
description=prompt.description,
arguments=[arg.model_dump() for arg in prompt.arguments]
if prompt.arguments
else None,
tags=list(prompt.tags) if prompt.tags else None,
enabled=prompt.enabled,
)
)
# Extract detailed resource information
resource_infos = []
for key, resource in resources_dict.items():
resource_infos.append(
ResourceInfo(
key=key,
uri=key, # For v2, key is the URI
name=resource.name,
description=resource.description,
mime_type=resource.mime_type,
tags=list(resource.tags) if resource.tags else None,
enabled=resource.enabled,
)
)
# Extract detailed template information
template_infos = []
for key, template in templates_dict.items():
template_infos.append(
TemplateInfo(
key=key,
uri_template=key, # For v2, key is the URI template
name=template.name,
description=template.description,
mime_type=template.mime_type,
tags=list(template.tags) if template.tags else None,
enabled=template.enabled,
)
)
# Basic MCP capabilities that FastMCP supports
capabilities = {
"tools": {"listChanged": True},
"resources": {"subscribe": False, "listChanged": False},
"prompts": {"listChanged": False},
"logging": {},
}
return FastMCPInfo(
name=mcp.name,
instructions=mcp.instructions,
fastmcp_version=fastmcp.__version__,
mcp_version=importlib.metadata.version("mcp"),
server_version=fastmcp.__version__, # v2.x uses FastMCP version
tools=tool_infos,
prompts=prompt_infos,
resources=resource_infos,
templates=template_infos,
capabilities=capabilities,
)
async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
"""Extract information from a FastMCP v1.x instance using a Client.
Args:
mcp: The FastMCP v1.x instance to inspect
Returns:
FastMCPInfo dataclass containing the extracted information
"""
from fastmcp import Client
# Use a client to interact with the FastMCP1x server
async with Client(mcp) as client:
# Get components via client calls (these return MCP objects)
mcp_tools = await client.list_tools()
mcp_prompts = await client.list_prompts()
mcp_resources = await client.list_resources()
# Try to get resource templates (FastMCP 1.x does have templates)
try:
mcp_templates = await client.list_resource_templates()
except Exception:
mcp_templates = []
# Extract detailed tool information from MCP Tool objects
tool_infos = []
for mcp_tool in mcp_tools:
# Extract annotations if they exist
annotations = None
if hasattr(mcp_tool, "annotations") and mcp_tool.annotations:
if hasattr(mcp_tool.annotations, "model_dump"):
annotations = mcp_tool.annotations.model_dump()
elif isinstance(mcp_tool.annotations, dict):
annotations = mcp_tool.annotations
else:
annotations = None
tool_infos.append(
ToolInfo(
key=mcp_tool.name, # For 1.x, key and name are the same
name=mcp_tool.name,
description=mcp_tool.description,
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
annotations=annotations,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
)
)
# Extract detailed prompt information from MCP Prompt objects
prompt_infos = []
for mcp_prompt in mcp_prompts:
# Convert arguments if they exist
arguments = None
if hasattr(mcp_prompt, "arguments") and mcp_prompt.arguments:
arguments = [arg.model_dump() for arg in mcp_prompt.arguments]
prompt_infos.append(
PromptInfo(
key=mcp_prompt.name, # For 1.x, key and name are the same
name=mcp_prompt.name,
description=mcp_prompt.description,
arguments=arguments,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
)
)
# Extract detailed resource information from MCP Resource objects
resource_infos = []
for mcp_resource in mcp_resources:
resource_infos.append(
ResourceInfo(
key=str(mcp_resource.uri), # For 1.x, key and uri are the same
uri=str(mcp_resource.uri),
name=mcp_resource.name,
description=mcp_resource.description,
mime_type=mcp_resource.mimeType,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
)
)
# Extract detailed template information from MCP ResourceTemplate objects
template_infos = []
for mcp_template in mcp_templates:
template_infos.append(
TemplateInfo(
key=str(
mcp_template.uriTemplate
), # For 1.x, key and uriTemplate are the same
uri_template=str(mcp_template.uriTemplate),
name=mcp_template.name,
description=mcp_template.description,
mime_type=mcp_template.mimeType,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
)
)
# Basic MCP capabilities
capabilities = {
"tools": {"listChanged": True},
"resources": {"subscribe": False, "listChanged": False},
"prompts": {"listChanged": False},
"logging": {},
}
return FastMCPInfo(
name=mcp.name,
instructions=getattr(mcp, "instructions", None),
fastmcp_version=fastmcp.__version__, # Report current fastmcp version
mcp_version=importlib.metadata.version("mcp"),
server_version="1.0", # FastMCP 1.x version
tools=tool_infos,
prompts=prompt_infos,
resources=resource_infos,
templates=template_infos, # FastMCP1x does have templates
capabilities=capabilities,
)
def _is_fastmcp_v1(mcp: Any) -> bool:
"""Check if the given instance is a FastMCP v1.x instance."""
# Check if it's an instance of FastMCP1x and not FastMCP2
return isinstance(mcp, FastMCP1x) and not isinstance(mcp, FastMCP)
async def inspect_fastmcp(mcp: FastMCP[Any] | Any) -> FastMCPInfo:
"""Extract information from a FastMCP instance into a dataclass.
This function automatically detects whether the instance is FastMCP v1.x or v2.x
and uses the appropriate extraction method.
Args:
mcp: The FastMCP instance to inspect (v1.x or v2.x)
Returns:
FastMCPInfo dataclass containing the extracted information
"""
if _is_fastmcp_v1(mcp):
return await inspect_fastmcp_v1(mcp)
else:
return await inspect_fastmcp_v2(mcp)

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import copy
from collections import defaultdict
def _prune_param(schema: dict, param: str) -> dict:
@ -24,25 +25,77 @@ def _prune_param(schema: dict, param: str) -> dict:
return schema
def _prune_unused_defs(schema: dict) -> dict:
"""Walk the schema and prune unused defs."""
root_defs: set[str] = set()
referenced_by: defaultdict[str, list] = defaultdict(list)
defs = schema.get("$defs")
if defs is None:
return schema
def walk(
node: object, current_def: str | None = None, skip_defs: bool = False
) -> None:
if isinstance(node, dict):
# Process $ref for definition tracking
ref = node.get("$ref")
if isinstance(ref, str) and ref.startswith("#/$defs/"):
def_name = ref.split("/")[-1]
if current_def:
referenced_by[def_name].append(current_def)
else:
root_defs.add(def_name)
# Walk children
for k, v in node.items():
if skip_defs and k == "$defs":
continue
walk(v, current_def=current_def)
elif isinstance(node, list):
for v in node:
walk(v)
# Traverse the schema once, skipping the $defs
walk(schema, skip_defs=True)
# Now figure out what defs reference other defs
for def_name, value in defs.items():
walk(value, current_def=def_name)
# Figure out what defs were referenced directly or recursively
def def_is_referenced(def_name):
if def_name in root_defs:
return True
references = referenced_by.get(def_name)
if references:
for reference in references:
if def_is_referenced(reference):
return True
return False
# Remove orphaned definitions if requested
for def_name in list(defs):
if not def_is_referenced(def_name):
defs.pop(def_name)
if not defs:
schema.pop("$defs", None)
return schema
def _walk_and_prune(
schema: dict,
prune_defs: bool = False,
prune_titles: bool = False,
prune_additional_properties: bool = False,
) -> dict:
"""Walk the schema and optionally prune titles, unused definitions, and additionalProperties: false."""
# Will only be used if prune_defs is True
used_defs: set[str] = set()
"""Walk the schema and optionally prune titles and additionalProperties: false."""
def walk(node: object) -> None:
if isinstance(node, dict):
# Process $ref for definition tracking
if prune_defs:
ref = node.get("$ref")
if isinstance(ref, str) and ref.startswith("#/$defs/"):
used_defs.add(ref.split("/")[-1])
# Remove title if requested
if prune_titles and "title" in node:
node.pop("title")
@ -62,18 +115,8 @@ def _walk_and_prune(
for v in node:
walk(v)
# Traverse the schema once
walk(schema)
# Remove orphaned definitions if requested
if prune_defs:
defs = schema.get("$defs", {})
for def_name in list(defs):
if def_name not in used_defs:
defs.pop(def_name)
if not defs:
schema.pop("$defs", None)
return schema
@ -109,12 +152,13 @@ def compress_schema(
schema = _prune_param(schema, param=param)
# Do a single walk to handle pruning operations
if prune_defs or prune_titles or prune_additional_properties:
if prune_titles or prune_additional_properties:
schema = _walk_and_prune(
schema,
prune_defs=prune_defs,
prune_titles=prune_titles,
prune_additional_properties=prune_additional_properties,
)
if prune_defs:
schema = _prune_unused_defs(schema)
return schema

View file

@ -1,9 +1,11 @@
from __future__ import annotations
import re
from typing import TYPE_CHECKING, Annotated, Any, Literal
from urllib.parse import urlparse
from pydantic import AnyUrl, Field
import httpx
from pydantic import AnyUrl, ConfigDict, Field
from fastmcp.utilities.types import FastMCPBaseModel
@ -28,7 +30,8 @@ def infer_transport_type_from_url(
parsed_url = urlparse(url)
path = parsed_url.path
if "/sse/" in path or path.rstrip("/").endswith("/sse"):
# Match /sse followed by /, ?, &, or end of string
if re.search(r"/sse(/|\?|&|$)", path):
return "sse"
else:
return "streamable-http"
@ -57,12 +60,14 @@ class RemoteMCPServer(FastMCPBaseModel):
headers: dict[str, str] = Field(default_factory=dict)
transport: Literal["streamable-http", "sse"] | None = None
auth: Annotated[
str | Literal["oauth"] | None,
str | Literal["oauth"] | httpx.Auth | None,
Field(
description='Either a string representing a Bearer token or the literal "oauth" to use OAuth authentication.'
description='Either a string representing a Bearer token, the literal "oauth" to use OAuth authentication, or an httpx.Auth instance for custom authentication.',
),
] = None
model_config = ConfigDict(arbitrary_types_allowed=True)
def to_transport(self) -> StreamableHttpTransport | SSETransport:
from fastmcp.client.transports import SSETransport, StreamableHttpTransport

View file

@ -262,16 +262,18 @@ class OpenAPIParser(
if isinstance(resolved_schema, (self.schema_cls)):
# Convert schema to dictionary
return resolved_schema.model_dump(
result = resolved_schema.model_dump(
mode="json", by_alias=True, exclude_none=True
)
elif isinstance(resolved_schema, dict):
return resolved_schema
result = resolved_schema
else:
logger.warning(
f"Expected Schema after resolving, got {type(resolved_schema)}. Returning empty dict."
)
return {}
result = {}
return _replace_ref_with_defs(result)
except Exception as e:
logger.error(f"Failed to extract schema as dict: {e}", exc_info=False)
return {}

View file

@ -67,7 +67,7 @@ def mcp_server_url(rsa_key_pair: RSAKeyPair) -> Generator[str]:
public_key=rsa_key_pair.public_key,
run_kwargs=dict(transport="streamable-http"),
) as url:
yield f"{url}/mcp"
yield f"{url}/mcp/"
class TestRSAKeyPair:
@ -446,6 +446,45 @@ class TestBearerToken:
access_token = await provider.load_access_token(token)
assert access_token is not None
async def test_provider_with_multiple_expected_audiences(
self, rsa_key_pair: RSAKeyPair
):
"""Test provider configured with multiple expected audiences."""
provider = BearerAuthProvider(
public_key=rsa_key_pair.public_key,
issuer="https://test.example.com",
audience=["https://api.example.com", "https://other-api.example.com"],
)
# Token with single audience that matches one of the expected
token1 = rsa_key_pair.create_token(
subject="test-user",
issuer="https://test.example.com",
audience="https://api.example.com",
)
access_token1 = await provider.load_access_token(token1)
assert access_token1 is not None
# Token with multiple audiences, one of which matches
token2 = rsa_key_pair.create_token(
subject="test-user",
issuer="https://test.example.com",
additional_claims={
"aud": ["https://api.example.com", "https://third-party.example.com"]
},
)
access_token2 = await provider.load_access_token(token2)
assert access_token2 is not None
# Token with audience that doesn't match any expected
token3 = rsa_key_pair.create_token(
subject="test-user",
issuer="https://test.example.com",
audience="https://wrong-api.example.com",
)
access_token3 = await provider.load_access_token(token3)
assert access_token3 is None
async def test_scope_extraction_string(
self, rsa_key_pair: RSAKeyPair, bearer_provider: BearerAuthProvider
):
@ -539,6 +578,59 @@ class TestBearerToken:
assert access_token is not None
assert access_token.client_id == "app456" # Should prefer client_id over sub
async def test_string_issuer_validation(self, rsa_key_pair: RSAKeyPair):
"""Test that string (non-URL) issuers are supported per RFC 7519."""
# Create provider with string issuer
provider = BearerAuthProvider(
public_key=rsa_key_pair.public_key,
issuer="my-service", # String issuer, not a URL
)
# Create token with matching string issuer
token = rsa_key_pair.create_token(
subject="test-user",
issuer="my-service", # Same string issuer
)
access_token = await provider.load_access_token(token)
assert access_token is not None
assert access_token.client_id == "test-user"
async def test_string_issuer_mismatch_rejection(self, rsa_key_pair: RSAKeyPair):
"""Test that mismatched string issuers are rejected."""
# Create provider with one string issuer
provider = BearerAuthProvider(
public_key=rsa_key_pair.public_key,
issuer="my-service",
)
# Create token with different string issuer
token = rsa_key_pair.create_token(
subject="test-user",
issuer="other-service", # Different string issuer
)
access_token = await provider.load_access_token(token)
assert access_token is None
async def test_url_issuer_still_works(self, rsa_key_pair: RSAKeyPair):
"""Test that URL issuers still work after the fix."""
# Create provider with URL issuer
provider = BearerAuthProvider(
public_key=rsa_key_pair.public_key,
issuer="https://my-auth-server.com", # URL issuer
)
# Create token with matching URL issuer
token = rsa_key_pair.create_token(
subject="test-user",
issuer="https://my-auth-server.com", # Same URL issuer
)
access_token = await provider.load_access_token(token)
assert access_token is not None
assert access_token.client_id == "test-user"
class TestFastMCPBearerAuth:
def test_bearer_auth(self):
@ -606,7 +698,7 @@ class TestFastMCPBearerAuth:
auth_kwargs=dict(required_scopes=["read", "write"]),
run_kwargs=dict(transport="streamable-http"),
) as url:
mcp_server_url = f"{url}/mcp"
mcp_server_url = f"{url}/mcp/"
with pytest.raises(httpx.HTTPStatusError) as exc_info:
async with Client(mcp_server_url, auth=BearerAuth(token)) as client:
tools = await client.list_tools() # noqa: F841
@ -629,7 +721,7 @@ class TestFastMCPBearerAuth:
auth_kwargs=dict(required_scopes=["read", "write"]),
run_kwargs=dict(transport="streamable-http"),
) as url:
mcp_server_url = f"{url}/mcp"
mcp_server_url = f"{url}/mcp/"
async with Client(mcp_server_url, auth=BearerAuth(token)) as client:
tools = await client.list_tools()
assert tools

View file

@ -44,7 +44,7 @@ def run_server(host: str, port: int, **kwargs) -> None:
@pytest.fixture(scope="module")
def streamable_http_server() -> Generator[str, None, None]:
with run_server_in_process(run_server, transport="streamable-http") as url:
yield f"{url}/mcp"
yield f"{url}/mcp/"
@pytest.fixture()

View file

@ -220,6 +220,101 @@ async def test_get_prompt_mcp(fastmcp_server):
assert result.description == "Example greeting prompt."
async def test_client_serializes_all_non_string_arguments():
"""Test that client always serializes non-string arguments to JSON, regardless of server types."""
server = FastMCP("TestServer")
@server.prompt
def echo_args(arg1: str, arg2: str, arg3: str) -> str:
"""Server accepts all string args but client sends mixed types."""
return f"arg1: {arg1}, arg2: {arg2}, arg3: {arg3}"
client = Client(transport=FastMCPTransport(server))
async with client:
result = await client.get_prompt(
"echo_args",
{
"arg1": "hello", # string - should pass through
"arg2": [1, 2, 3], # list - should be JSON serialized
"arg3": {"key": "value"}, # dict - should be JSON serialized
},
)
content = result.messages[0].content.text # type: ignore[attr-defined]
assert "arg1: hello" in content
assert "arg2: [1,2,3]" in content # JSON serialized list
assert 'arg3: {"key":"value"}' in content # JSON serialized dict
async def test_client_server_type_conversion_integration():
"""Test that client serialization works with server-side type conversion."""
server = FastMCP("TestServer")
@server.prompt
def typed_prompt(numbers: list[int], config: dict[str, str]) -> str:
"""Server expects typed args - will convert from JSON strings."""
return f"Got {len(numbers)} numbers and {len(config)} config items"
client = Client(transport=FastMCPTransport(server))
async with client:
result = await client.get_prompt(
"typed_prompt",
{"numbers": [1, 2, 3, 4], "config": {"theme": "dark", "lang": "en"}},
)
content = result.messages[0].content.text # type: ignore[attr-defined]
assert "Got 4 numbers and 2 config items" in content
async def test_client_serialization_error():
"""Test client error when object cannot be serialized."""
import pydantic_core
server = FastMCP("TestServer")
@server.prompt
def any_prompt(data: str) -> str:
return f"Got: {data}"
# Create an unserializable object
class UnserializableClass:
def __init__(self):
self.func = lambda x: x # functions can't be JSON serialized
client = Client(transport=FastMCPTransport(server))
async with client:
with pytest.raises(
pydantic_core.PydanticSerializationError, match="Unable to serialize"
):
await client.get_prompt("any_prompt", {"data": UnserializableClass()})
async def test_server_deserialization_error():
"""Test server error when JSON string cannot be converted to expected type."""
from mcp import McpError
server = FastMCP("TestServer")
@server.prompt
def strict_typed_prompt(numbers: list[int]) -> str:
"""Expects list of integers but will receive invalid JSON."""
return f"Got {len(numbers)} numbers"
client = Client(transport=FastMCPTransport(server))
async with client:
with pytest.raises(McpError, match="Error rendering prompt"):
await client.get_prompt(
"strict_typed_prompt",
{
"numbers": "not valid json" # This will fail server-side conversion
},
)
async def test_read_resource_invalid_uri(fastmcp_server):
"""Test reading a resource with an invalid URI."""
client = Client(transport=FastMCPTransport(fastmcp_server))
@ -735,7 +830,8 @@ class TestInferTransport:
"http://example.com/api/sse/stream",
"https://localhost:8080/mcp/sse/endpoint",
"http://example.com/api/sse",
"https://localhost:8080/mcp/sse",
"http://example.com/api/sse/",
"https://localhost:8080/mcp/sse/",
"http://example.com/api/sse?param=value",
"https://localhost:8080/mcp/sse/?param=value",
"https://localhost:8000/mcp/sse?x=1&y=2",
@ -744,6 +840,7 @@ class TestInferTransport:
"path_with_sse_directory",
"path_with_sse_subdirectory",
"path_ending_with_sse",
"path_ending_with_sse_slash",
"path_ending_with_sse_https",
"path_with_sse_and_query_params",
"path_with_sse_slash_and_query_params",
@ -758,7 +855,7 @@ class TestInferTransport:
"url",
[
"http://example.com/api",
"https://localhost:8080/mcp",
"https://localhost:8080/mcp/",
"http://example.com/asset/image.jpg",
"https://localhost:8080/sservice/endpoint",
"https://example.com/assets/file",
@ -779,7 +876,7 @@ class TestInferTransport:
config = {
"mcpServers": {
"test_server": {
"url": "http://localhost:8000/sse",
"url": "http://localhost:8000/sse/",
"headers": {"Authorization": "Bearer 123"},
},
}
@ -787,7 +884,7 @@ class TestInferTransport:
transport = infer_transport(config)
assert isinstance(transport, MCPConfigTransport)
assert isinstance(transport.transport, SSETransport)
assert transport.transport.url == "http://localhost:8000/sse"
assert transport.transport.url == "http://localhost:8000/sse/"
assert transport.transport.headers == {"Authorization": "Bearer 123"}
def test_infer_local_transport_from_config(self):
@ -825,7 +922,7 @@ class TestInferTransport:
"args": ["hello"],
},
"remote": {
"url": "http://localhost:8000/sse",
"url": "http://localhost:8000/sse/",
"headers": {"Authorization": "Bearer 123"},
},
}

View file

@ -57,12 +57,12 @@ class TestClientHeaders:
@pytest.fixture(scope="class")
def shttp_server(self) -> Generator[str, None, None]:
with run_server_in_process(run_server, transport="streamable-http") as url:
yield f"{url}/mcp"
yield f"{url}/mcp/"
@pytest.fixture(scope="class")
def sse_server(self) -> Generator[str, None, None]:
with run_server_in_process(run_server, transport="sse") as url:
yield f"{url}/sse"
yield f"{url}/sse/"
@pytest.fixture(scope="class")
def proxy_server(self, shttp_server: str) -> Generator[str, None, None]:
@ -71,7 +71,7 @@ class TestClientHeaders:
shttp_url=shttp_server,
transport="streamable-http",
) as url:
yield f"{url}/mcp"
yield f"{url}/mcp/"
async def test_client_headers_sse_resource(self, sse_server: str):
async with Client(

View file

@ -70,7 +70,7 @@ def run_server(host: str, port: int, **kwargs) -> None:
@pytest.fixture(autouse=True, scope="module")
def sse_server() -> Generator[str, None, None]:
with run_server_in_process(run_server, transport="sse") as url:
yield f"{url}/sse"
yield f"{url}/sse/"
async def test_ping(sse_server: str):
@ -92,7 +92,7 @@ async def test_http_headers(sse_server: str):
def run_nested_server(host: str, port: int) -> None:
app = fastmcp_server().sse_app(path="/mcp/sse", message_path="/mcp/messages")
app = fastmcp_server().sse_app(path="/mcp/sse/", message_path="/mcp/messages")
mount = Starlette(routes=[Mount("/nest-inner", app=app)])
mount2 = Starlette(routes=[Mount("/nest-outer", app=mount)])
server = uvicorn.Server(
@ -114,7 +114,7 @@ async def test_nested_sse_server_resolves_correctly():
with run_server_in_process(run_nested_server) as url:
async with Client(
transport=SSETransport(f"{url}/nest-outer/nest-inner/mcp/sse")
transport=SSETransport(f"{url}/nest-outer/nest-inner/mcp/sse/")
) as client:
result = await client.ping()
assert result is True

View file

@ -79,7 +79,7 @@ def run_server(host: str, port: int, stateless_http: bool = False, **kwargs) ->
def run_nested_server(host: str, port: int) -> None:
mcp_app = fastmcp_server().http_app(path="/final/mcp")
mcp_app = fastmcp_server().http_app(path="/final/mcp/")
mount = Starlette(routes=[Mount("/nest-inner", app=mcp_app)])
mount2 = Starlette(
@ -105,9 +105,9 @@ async def streamable_http_server(
with run_server_in_process(
run_server, stateless_http=stateless_http, transport="streamable-http"
) as url:
async with Client(transport=StreamableHttpTransport(f"{url}/mcp")) as client:
async with Client(transport=StreamableHttpTransport(f"{url}/mcp/")) as client:
assert await client.ping()
yield f"{url}/mcp"
yield f"{url}/mcp/"
async def test_ping(streamable_http_server: str):
@ -156,7 +156,7 @@ async def test_nested_streamable_http_server_resolves_correctly():
with run_server_in_process(run_nested_server) as url:
async with Client(
transport=StreamableHttpTransport(f"{url}/nest-outer/nest-inner/final/mcp")
transport=StreamableHttpTransport(f"{url}/nest-outer/nest-inner/final/mcp/")
) as client:
result = await client.ping()
assert result is True

View file

@ -123,7 +123,7 @@ class TestDeprecatedServerInitKwargs:
debug=False,
host="127.0.0.1",
port=9999,
sse_path="/sse",
sse_path="/sse/",
message_path="/msg",
streamable_http_path="/http",
json_response=False,
@ -162,7 +162,7 @@ class TestDeprecatedServerInitKwargs:
assert server._deprecated_settings.debug is False
assert server._deprecated_settings.host == "127.0.0.1"
assert server._deprecated_settings.port == 9999
assert server._deprecated_settings.sse_path == "/sse"
assert server._deprecated_settings.sse_path == "/sse/"
assert server._deprecated_settings.message_path == "/msg"
assert server._deprecated_settings.streamable_http_path == "/http"
assert server._deprecated_settings.json_response is False

Some files were not shown because too many files have changed in this diff Show more