diff --git a/docs/clients/auth/oauth.mdx b/docs/clients/auth/oauth.mdx index 9403d3063..1dff354ca 100644 --- a/docs/clients/auth/oauth.mdx +++ b/docs/clients/auth/oauth.mdx @@ -3,7 +3,6 @@ title: OAuth Authentication sidebarTitle: OAuth description: Authenticate your FastMCP client via OAuth 2.1. icon: window -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/clients/client.mdx b/docs/clients/client.mdx index 36f300030..fc5ddc263 100644 --- a/docs/clients/client.mdx +++ b/docs/clients/client.mdx @@ -9,24 +9,17 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -The central piece of MCP client applications is the `fastmcp.Client` class. This class provides a **programmatic interface** for interacting with any Model Context Protocol (MCP) server, handling protocol details and connection management automatically. - -The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for: - -- **Testing MCP servers** during development -- **Building deterministic applications** that need reliable MCP interactions -- **Creating the foundation for agentic or LLM-based clients** with structured, type-safe operations - -All client operations require using the `async with` context manager for proper connection lifecycle management. +The `fastmcp.Client` class provides a programmatic interface for interacting with any MCP server. It handles protocol details and connection management automatically, letting you focus on the operations you want to perform. +The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for testing MCP servers during development, building deterministic applications that need reliable MCP interactions, and creating the foundation for agentic or LLM-based clients with structured, type-safe operations. -This is not an agentic client - it requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems. +This is a programmatic client that requires explicit function calls and provides direct control over all MCP operations. Use it as a building block for higher-level systems. ## Creating a Client -Creating a client is straightforward. You provide a server source and the client automatically infers the appropriate transport mechanism. +You provide a server source and the client automatically infers the appropriate transport mechanism. ```python import asyncio @@ -46,12 +39,12 @@ async def main(): async with client: # Basic server interaction await client.ping() - + # List available operations tools = await client.list_tools() resources = await client.list_resources() prompts = await client.list_prompts() - + # Execute operations result = await client.call_tool("example_tool", {"param": "value"}) print(result) @@ -59,35 +52,42 @@ async def main(): asyncio.run(main()) ``` -## Client-Transport Architecture +All client operations require using the `async with` context manager for proper connection lifecycle management. -The FastMCP Client separates concerns between protocol and connection: +## Choosing a Transport -- **`Client`**: Handles MCP protocol operations (tools, resources, prompts) and manages callbacks -- **`Transport`**: Establishes and maintains the connection (WebSockets, HTTP, Stdio, in-memory) +The client automatically selects a transport based on what you pass to it, but different transports have different characteristics that matter for your use case. -### Transport Inference - -The client automatically infers the appropriate transport based on the input: - -1. **`FastMCP` instance** → In-memory transport (perfect for testing) -2. **File path ending in `.py`** → Python Stdio transport -3. **File path ending in `.js`** → Node.js Stdio transport -4. **URL starting with `http://` or `https://`** → HTTP transport -5. **`MCPConfig` dictionary** → Multi-server client +**In-memory transport** connects directly to a FastMCP server instance within the same Python process. Use this for testing and development where you want to eliminate subprocess and network complexity. The server shares your process's environment and memory space. ```python from fastmcp import Client, FastMCP -# Examples of transport inference -client_memory = Client(FastMCP("TestServer")) -client_script = Client("./server.py") -client_http = Client("https://api.example.com/mcp") +server = FastMCP("TestServer") +client = Client(server) # In-memory, no network or subprocess ``` - -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. - +**STDIO transport** launches a server as a subprocess and communicates through stdin/stdout pipes. This is the standard mechanism used by desktop clients like Claude Desktop. The subprocess runs in an isolated environment, so you must explicitly pass any environment variables the server needs. + +```python +from fastmcp import Client + +# Simple inference from file path +client = Client("my_server.py") + +# With explicit environment configuration +client = Client("my_server.py", env={"API_KEY": "secret"}) +``` + +**HTTP transport** connects to servers running as web services. Use this for production deployments where the server runs independently and manages its own lifecycle. + +```python +from fastmcp import Client + +client = Client("https://api.example.com/mcp") +``` + +See [Transports](/clients/transports) for detailed configuration options including authentication headers, session persistence, and multi-server configurations. ## Configuration-Based Clients @@ -95,39 +95,18 @@ For testing and development, always prefer the in-memory transport by passing a Create clients from MCP configuration dictionaries, which can include multiple servers. While there is no official standard for MCP configuration format, FastMCP follows established conventions used by tools like Claude Desktop. -### Configuration Format - ```python config = { "mcpServers": { - "server_name": { - # Remote HTTP/SSE server - "transport": "http", # or "sse" - "url": "https://api.example.com/mcp", - "headers": {"Authorization": "Bearer token"}, - "auth": "oauth" # or bearer token string + "weather": { + "url": "https://weather-api.example.com/mcp" }, - "local_server": { - # Local stdio server - "transport": "stdio", + "assistant": { "command": "python", - "args": ["./server.py", "--verbose"], - "env": {"DEBUG": "true"}, - "cwd": "/path/to/server", + "args": ["./assistant_server.py"] } } } -``` - -### Multi-Server Example - -```python -config = { - "mcpServers": { - "weather": {"url": "https://weather-api.example.com/mcp"}, - "assistant": {"command": "python", "args": ["./assistant_server.py"]} - } -} client = Client(config) @@ -135,97 +114,14 @@ async with client: # Tools are prefixed with server names weather_data = await client.call_tool("weather_get_forecast", {"city": "London"}) response = await client.call_tool("assistant_answer_question", {"question": "What's the capital of France?"}) - + # Resources use prefixed URIs icons = await client.read_resource("weather://weather/icons/sunny") - templates = await client.read_resource("resource://assistant/templates/list") ``` ## Connection Lifecycle -The client operates asynchronously and uses context managers for connection management: - -```python -async def example(): - client = Client("my_mcp_server.py") - - # Connection established here - async with client: - print(f"Connected: {client.is_connected()}") - - # Make multiple calls within the same session - tools = await client.list_tools() - result = await client.call_tool("greet", {"name": "World"}) - - # Connection closed automatically here - print(f"Connected: {client.is_connected()}") -``` - -## Operations - -FastMCP clients can interact with several types of server components: - -### Tools - -Tools are server-side functions that the client can execute with arguments. - -```python -async with client: - # List available tools - tools = await client.list_tools() - - # Execute a tool - result = await client.call_tool("multiply", {"a": 5, "b": 3}) - print(result.data) # 15 -``` - -See [Tools](/clients/tools) for detailed documentation. - -### Resources - -Resources are data sources that the client can read, either static or templated. - -```python -async with client: - # List available resources - resources = await client.list_resources() - - # Read a resource - content = await client.read_resource("file:///config/settings.json") - print(content[0].text) -``` - -See [Resources](/clients/resources) for detailed documentation. - -### Prompts - -Prompts are reusable message templates that can accept arguments. - -```python -async with client: - # List available prompts - prompts = await client.list_prompts() - - # Get a rendered prompt - messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]}) - print(messages.messages) -``` - -See [Prompts](/clients/prompts) for detailed documentation. - -### Server Connectivity - -Use `ping()` to verify the server is reachable: - -```python -async with client: - await client.ping() - print("Server is reachable") -``` - -### Initialization and Server Information - -When you enter the client context manager, the client automatically performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions. The result is available through the `initialize_result` property. +The client uses context managers for connection management. When you enter the context, the client establishes a connection and performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions. ```python from fastmcp import Client, FastMCP @@ -240,21 +136,15 @@ def greet(name: str) -> str: async with Client(mcp) as client: # Initialization already happened automatically print(f"Server: {client.initialize_result.serverInfo.name}") - print(f"Version: {client.initialize_result.serverInfo.version}") print(f"Instructions: {client.initialize_result.instructions}") print(f"Capabilities: {client.initialize_result.capabilities.tools}") ``` -#### Manual Initialization Control - -In advanced scenarios, you might want precise control over when initialization happens. For example, you may need custom error handling, want to defer initialization until after other setup, or need to measure initialization timing separately. - -Disable automatic initialization and call `initialize()` manually: +For advanced scenarios where you need precise control over when initialization happens, disable automatic initialization and call `initialize()` manually: ```python from fastmcp import Client -# Disable automatic initialization client = Client("my_mcp_server.py", auto_initialize=False) async with client: @@ -270,15 +160,46 @@ async with client: tools = await client.list_tools() ``` -The `initialize()` method is idempotent - calling it multiple times returns the cached result from the first successful call. +## Operations -## Client Configuration +FastMCP clients interact with three types of server components. -Clients can be configured with additional handlers and settings for specialized use cases. +**Tools** are server-side functions that the client can execute with arguments. Call them with `call_tool()` and receive structured results. -### Callback Handlers +```python +async with client: + tools = await client.list_tools() + result = await client.call_tool("multiply", {"a": 5, "b": 3}) + print(result.data) # 15 +``` -The client supports several callback handlers for advanced server interactions: +See [Tools](/clients/tools) for detailed documentation including version selection, error handling, and structured output. + +**Resources** are data sources that the client can read, either static or templated. Access them with `read_resource()` using URIs. + +```python +async with client: + resources = await client.list_resources() + content = await client.read_resource("file:///config/settings.json") + print(content[0].text) +``` + +See [Resources](/clients/resources) for detailed documentation including templates and binary content. + +**Prompts** are reusable message templates that can accept arguments. Retrieve rendered prompts with `get_prompt()`. + +```python +async with client: + prompts = await client.list_prompts() + messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]}) + print(messages.messages) +``` + +See [Prompts](/clients/prompts) for detailed documentation including argument serialization. + +## Callback Handlers + +The client supports callback handlers for advanced server interactions. These let you respond to server-initiated requests and receive notifications. ```python from fastmcp import Client @@ -303,38 +224,14 @@ client = Client( ) ``` -The `Client` constructor accepts several configuration options: +Each handler type has its own documentation: -- `transport`: Transport instance or source for automatic inference -- `log_handler`: Handle server log messages -- `progress_handler`: Monitor long-running operations -- `sampling_handler`: Respond to server LLM requests -- `roots`: Provide local context to servers -- `timeout`: Default timeout for requests (in seconds) - -### Transport Configuration - -For detailed transport configuration (headers, authentication, environment variables), see the [Transports](/clients/transports) documentation. - -## Next Steps - -Explore the detailed documentation for each operation type: - -### Core Operations -- **[Tools](/clients/tools)** - Execute server-side functions and handle results -- **[Resources](/clients/resources)** - Access static and templated resources -- **[Prompts](/clients/prompts)** - Work with message templates and argument serialization - -### Advanced Features -- **[Logging](/clients/logging)** - Handle server log messages -- **[Progress](/clients/progress)** - Monitor long-running operations - **[Sampling](/clients/sampling)** - Respond to server LLM requests +- **[Elicitation](/clients/elicitation)** - Handle server requests for user input +- **[Progress](/clients/progress)** - Monitor long-running operations +- **[Logging](/clients/logging)** - Handle server log messages - **[Roots](/clients/roots)** - Provide local context to servers -### Connection Details -- **[Transports](/clients/transports)** - Configure connection methods and parameters -- **[Authentication](/clients/auth/oauth)** - Set up OAuth and bearer token authentication - 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. - \ No newline at end of file + diff --git a/docs/clients/elicitation.mdx b/docs/clients/elicitation.mdx index 15b0e5030..33adbb6d6 100644 --- a/docs/clients/elicitation.mdx +++ b/docs/clients/elicitation.mdx @@ -1,7 +1,7 @@ --- title: User Elicitation sidebarTitle: Elicitation -description: Handle server-initiated user input requests with structured schemas. +description: Handle server requests for structured user input. icon: message-question --- @@ -9,44 +9,43 @@ import { VersionBadge } from "/snippets/version-badge.mdx"; -## What is Elicitation? +Use this when you need to respond to server requests for user input during tool execution. -Elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, servers can interactively ask users for information as needed - like prompting for missing parameters, requesting clarification, or gathering additional context. +Elicitation allows MCP servers to request structured input from users during operations. Instead of requiring all inputs upfront, servers can interactively ask for missing parameters, request clarification, or gather additional context. -For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?" - -## How FastMCP Makes Elicitation Easy - -FastMCP's client provides a helpful abstraction layer that: - -- **Converts JSON schemas to Python types**: The raw MCP protocol uses JSON schemas, but FastMCP automatically converts these to Python dataclasses -- **Provides structured constructors**: Instead of manually building dictionaries that match the schema, you get dataclass constructors that ensure correct structure -- **Handles type conversion**: FastMCP takes care of converting between JSON representations and Python objects -- **Runtime introspection**: You can inspect the generated dataclass fields to understand the expected structure - -When you implement an elicitation handler, FastMCP gives you a dataclass type that matches the server's schema, making it easy to create properly structured responses without having to manually parse JSON schemas. - -## Elicitation Handler - -Provide an `elicitation_handler` function when creating the client. FastMCP automatically converts the server's JSON schema into a Python dataclass type, making it easy to construct the response: +## Handler Template ```python from fastmcp import Client -from fastmcp.client.elicitation import ElicitResult +from fastmcp.client.elicitation import ElicitResult, ElicitRequestParams, RequestContext -async def elicitation_handler(message: str, response_type: type, params, context): - # Present the message to the user and collect input +async def elicitation_handler( + message: str, + response_type: type | None, + params: ElicitRequestParams, + context: RequestContext +) -> ElicitResult | object: + """ + Handle server requests for user input. + + Args: + message: The prompt to display to the user + response_type: Python dataclass type for the response (None if no data expected) + params: Original MCP elicitation parameters including raw JSON schema + context: Request context with metadata + + Returns: + - Data directly (implicitly accepts the elicitation) + - ElicitResult for explicit control over the action + """ + # Present the message and collect input user_input = input(f"{message}: ") - + + if not user_input: + return ElicitResult(action="decline") + # Create response using the provided dataclass type - # FastMCP converted the JSON schema to this Python type for you - response_data = response_type(value=user_input) - - # You can return data directly - FastMCP will implicitly accept the elicitation - return response_data - - # Or explicitly return an ElicitResult for more control - # return ElicitResult(action="accept", content=response_data) + return response_type(value=user_input) client = Client( "my_mcp_server.py", @@ -54,21 +53,23 @@ client = Client( ) ``` -### Handler Parameters +## How It Works -The elicitation handler receives four parameters: +When a server needs user input, it sends an elicitation request with a message prompt and a JSON schema describing the expected response structure. FastMCP automatically converts this schema into a Python dataclass type, making it easy to construct properly typed responses without manually parsing JSON schemas. - +The handler receives four parameters: + + The prompt message to display to the user - - A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing and IDE support. If the server requests an empty object (indicating no response), this will be `None`. + + A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing. If the server requests an empty object, this will be `None`. - The original MCP elicitation request parameters, including the raw JSON schema in `params.requestedSchema` if you need it + The original MCP elicitation parameters, including the raw JSON schema in `params.requestedSchema` @@ -76,50 +77,62 @@ The elicitation handler receives four parameters: -### Response Actions +## Response Actions -The handler can return data directly (which implicitly accepts the elicitation) or an `ElicitResult` object for more control over the response action: +You can return data directly, which implicitly accepts the elicitation: - - - How the user responded to the elicitation request - +```python +async def elicitation_handler(message, response_type, params, context): + user_input = input(f"{message}: ") + return response_type(value=user_input) # Implicit accept +``` - - The user's input data (required for "accept", omitted for "decline"/"cancel") - - +Or return an `ElicitResult` for explicit control over the action: -**Action Types:** -- **`accept`**: User provided valid input - include their data in the `content` field -- **`decline`**: User chose not to provide the requested information - omit `content` -- **`cancel`**: User cancelled the entire operation - omit `content` +```python +from fastmcp.client.elicitation import ElicitResult -## Basic Example +async def elicitation_handler(message, response_type, params, context): + user_input = input(f"{message}: ") + + if not user_input: + return ElicitResult(action="decline") # User declined + + if user_input == "cancel": + return ElicitResult(action="cancel") # Cancel entire operation + + return ElicitResult( + action="accept", + content=response_type(value=user_input) + ) +``` + +**Action types:** +- **`accept`**: User provided valid input. Include the data in the `content` field. +- **`decline`**: User chose not to provide the requested information. Omit `content`. +- **`cancel`**: User cancelled the entire operation. Omit `content`. + +## Example + +A file management tool might ask which directory to create: ```python from fastmcp import Client from fastmcp.client.elicitation import ElicitResult -async def basic_elicitation_handler(message: str, response_type: type, params, context): +async def elicitation_handler(message, response_type, params, context): print(f"Server asks: {message}") - - # Simple text input for demonstration + user_response = input("Your response: ") - + if not user_response: - # For non-acceptance, use ElicitResult explicitly return ElicitResult(action="decline") - + # Use the response_type dataclass to create a properly structured response - # FastMCP handles the conversion from JSON schema to Python type - # Return data directly - FastMCP will implicitly accept the elicitation return response_type(value=user_response) client = Client( - "my_mcp_server.py", - elicitation_handler=basic_elicitation_handler + "my_mcp_server.py", + elicitation_handler=elicitation_handler ) ``` - - diff --git a/docs/clients/logging.mdx b/docs/clients/logging.mdx index ffebdf87f..eea9ff322 100644 --- a/docs/clients/logging.mdx +++ b/docs/clients/logging.mdx @@ -9,82 +9,61 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -MCP servers can emit log messages to clients. The client can handle these logs through a log handler callback. +Use this when you need to capture or process log messages sent by the server. + +MCP servers can emit log messages to clients. The client handles these through a log handler callback. ## Log Handler -Provide a `log_handler` function when creating the client. For robust logging, the log messages can be integrated with Python's standard `logging` module. +Provide a `log_handler` function when creating the client: ```python import logging from fastmcp import Client from fastmcp.client.logging import LogMessage -# In a real app, you might configure this in your main entry point logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) -# Get a logger for the module where the client is used logger = logging.getLogger(__name__) - -# This mapping is useful for converting MCP level strings to Python's levels LOGGING_LEVEL_MAP = logging.getLevelNamesMapping() async def log_handler(message: LogMessage): - """ - Handles incoming logs from the MCP server and forwards them - to the standard Python logging system. - """ + """Forward MCP server logs to Python's logging system.""" msg = message.data.get('msg') extra = message.data.get('extra') - # Convert the MCP log level to a Python log level level = LOGGING_LEVEL_MAP.get(message.level.upper(), logging.INFO) - - # Log the message using the standard logging library logger.log(level, msg, extra=extra) - client = Client( "my_mcp_server.py", log_handler=log_handler, ) ``` -## Handling Structured Logs +The handler receives a `LogMessage` object: -The `message.data` attribute is a dictionary that contains the log payload from the server. This enables structured logging, allowing you to receive rich, contextual information. + + + The log level + -The dictionary contains two keys: -- `msg`: The string log message. -- `extra`: A dictionary containing any extra data sent from the server. + + The logger name (may be None) + -This structure is preserved even when logs are forwarded through a FastMCP proxy, making it a powerful tool for debugging complex, multi-server applications. - -### Handler Parameters - -The `log_handler` is called every time a log message is received. It receives a `LogMessage` object: - - - - - - The log level - - - - The logger name (optional, may be None) - - - - The log payload, containing `msg` and `extra` keys. - - + + The log payload, containing `msg` and `extra` keys +## Structured Logs + +The `message.data` attribute is a dictionary containing the log payload. This enables structured logging with rich contextual information. + ```python async def detailed_log_handler(message: LogMessage): msg = message.data.get('msg') @@ -98,14 +77,16 @@ async def detailed_log_handler(message: LogMessage): print(f"{message.level.upper()}: {msg}") ``` -## Default Log Handling +This structure is preserved even when logs are forwarded through a FastMCP proxy, making it useful for debugging multi-server applications. -If you don't provide a custom `log_handler`, FastMCP's default handler routes server logs to the appropriate Python logging levels. The MCP levels are mapped as follows: `notice` → INFO; `alert` and `emergency` → CRITICAL. If the server includes a logger name, it is prefixed in the message, and any `extra` data is forwarded via the logging `extra` parameter. +## Default Behavior + +If you do not provide a custom `log_handler`, FastMCP's default handler routes server logs to Python's logging system at the appropriate severity level. The MCP levels map as follows: `notice` becomes INFO; `alert` and `emergency` become CRITICAL. ```python client = Client("my_mcp_server.py") async with client: - # Server logs are forwarded at their proper severity (DEBUG/INFO/WARNING/ERROR/CRITICAL) + # Server logs are forwarded at proper severity automatically await client.call_tool("some_tool") ``` diff --git a/docs/clients/notifications.mdx b/docs/clients/notifications.mdx new file mode 100644 index 000000000..5e1b447aa --- /dev/null +++ b/docs/clients/notifications.mdx @@ -0,0 +1,155 @@ +--- +title: Notifications +sidebarTitle: Notifications +description: Handle server-sent notifications for list changes and other events. +icon: envelope +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +Use this when you need to react to server-side changes like tool list updates or resource modifications. + +MCP servers can send notifications to inform clients about state changes. The message handler provides a unified way to process these notifications. + +## Handling Notifications + +The simplest approach is a function that receives all messages and filters for the notifications you care about: + +```python +from fastmcp import Client + +async def message_handler(message): + """Handle MCP notifications from the server.""" + if hasattr(message, 'root'): + method = message.root.method + + if method == "notifications/tools/list_changed": + print("Tools have changed - refresh tool cache") + elif method == "notifications/resources/list_changed": + print("Resources have changed") + elif method == "notifications/prompts/list_changed": + print("Prompts have changed") + +client = Client( + "my_mcp_server.py", + message_handler=message_handler, +) +``` + +## MessageHandler Class + +For fine-grained targeting, subclass `MessageHandler` to use specific hooks: + +```python +from fastmcp import Client +from fastmcp.client.messages import MessageHandler +import mcp.types + +class MyMessageHandler(MessageHandler): + async def on_tool_list_changed( + self, notification: mcp.types.ToolListChangedNotification + ) -> None: + """Handle tool list changes.""" + print("Tool list changed - refreshing available tools") + + async def on_resource_list_changed( + self, notification: mcp.types.ResourceListChangedNotification + ) -> None: + """Handle resource list changes.""" + print("Resource list changed") + + async def on_prompt_list_changed( + self, notification: mcp.types.PromptListChangedNotification + ) -> None: + """Handle prompt list changes.""" + print("Prompt list changed") + +client = Client( + "my_mcp_server.py", + message_handler=MyMessageHandler(), +) +``` + +### Handler Template + +```python +from fastmcp.client.messages import MessageHandler +import mcp.types + +class MyMessageHandler(MessageHandler): + async def on_message(self, message) -> None: + """Called for ALL messages (requests and notifications).""" + pass + + async def on_notification( + self, notification: mcp.types.ServerNotification + ) -> None: + """Called for notifications (fire-and-forget).""" + pass + + async def on_tool_list_changed( + self, notification: mcp.types.ToolListChangedNotification + ) -> None: + """Called when the server's tool list changes.""" + pass + + async def on_resource_list_changed( + self, notification: mcp.types.ResourceListChangedNotification + ) -> None: + """Called when the server's resource list changes.""" + pass + + async def on_prompt_list_changed( + self, notification: mcp.types.PromptListChangedNotification + ) -> None: + """Called when the server's prompt list changes.""" + pass + + async def on_progress( + self, notification: mcp.types.ProgressNotification + ) -> None: + """Called for progress updates during long-running operations.""" + pass + + async def on_logging_message( + self, notification: mcp.types.LoggingMessageNotification + ) -> None: + """Called for log messages from the server.""" + pass +``` + +## List Change Notifications + +A practical example of maintaining a tool cache that refreshes when tools change: + +```python +from fastmcp import Client +from fastmcp.client.messages import MessageHandler +import mcp.types + +class ToolCacheHandler(MessageHandler): + def __init__(self): + self.cached_tools = [] + + async def on_tool_list_changed( + self, notification: mcp.types.ToolListChangedNotification + ) -> None: + """Clear tool cache when tools change.""" + print("Tools changed - clearing cache") + self.cached_tools = [] # Force refresh on next access + +client = Client("server.py", message_handler=ToolCacheHandler()) +``` + +## Server Requests + +While the message handler receives server-initiated requests, you should use dedicated callback parameters for most interactive scenarios: + +- **Sampling requests**: Use [`sampling_handler`](/clients/sampling) +- **Elicitation requests**: Use [`elicitation_handler`](/clients/elicitation) +- **Progress updates**: Use [`progress_handler`](/clients/progress) +- **Log messages**: Use [`log_handler`](/clients/logging) + +The message handler is primarily for monitoring and handling notifications rather than responding to requests. diff --git a/docs/clients/progress.mdx b/docs/clients/progress.mdx index ff3e0aa85..707ab8dac 100644 --- a/docs/clients/progress.mdx +++ b/docs/clients/progress.mdx @@ -9,18 +9,20 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -MCP servers can report progress during long-running operations. The client can receive these updates through a progress handler. +Use this when you need to track progress of long-running operations. + +MCP servers can report progress during operations. The client receives these updates through a progress handler. ## Progress Handler -Set a progress handler when creating the client: +Set a handler when creating the client: ```python from fastmcp import Client -async def my_progress_handler( - progress: float, - total: float | None, +async def progress_handler( + progress: float, + total: float | None, message: str | None ) -> None: if total is not None: @@ -31,40 +33,35 @@ async def my_progress_handler( client = Client( "my_mcp_server.py", - progress_handler=my_progress_handler + progress_handler=progress_handler ) ``` -### Handler Parameters +The handler receives three parameters: -The progress handler receives three parameters: - - - + Current progress value - Expected total value (may be None) + Expected total value (may be None if unknown) - Optional status message (may be None) + Optional status message +## Per-Call Handler -## Per-Call Progress Handler - -Override the progress handler for specific tool calls: +Override the client-level handler for specific tool calls: ```python async with client: - # Override with specific progress handler for this call result = await client.call_tool( - "long_running_task", - {"param": "value"}, + "long_running_task", + {"param": "value"}, progress_handler=my_progress_handler ) ``` diff --git a/docs/clients/prompts.mdx b/docs/clients/prompts.mdx index 53ce27738..2bf8c3c19 100644 --- a/docs/clients/prompts.mdx +++ b/docs/clients/prompts.mdx @@ -1,7 +1,7 @@ --- -title: Prompts +title: Getting Prompts sidebarTitle: Prompts -description: Use server-side prompt templates with automatic argument serialization. +description: Retrieve rendered message templates with automatic argument serialization. icon: message-lines --- @@ -9,112 +9,44 @@ import { VersionBadge } from '/snippets/version-badge.mdx' +Use this when you need to retrieve server-defined message templates for LLM interactions. + Prompts are reusable message templates exposed by MCP servers. They can accept arguments to generate personalized message sequences for LLM interactions. -## Listing Prompts +## Basic Usage -Use `list_prompts()` to retrieve all available prompt templates. When the server paginates results, the client automatically fetches all pages and returns the complete list. - -```python -async with client: - prompts = await client.list_prompts() - # prompts -> list[mcp.types.Prompt] - - for prompt in prompts: - print(f"Prompt: {prompt.name}") - print(f"Description: {prompt.description}") - if prompt.arguments: - print(f"Arguments: {[arg.name for arg in prompt.arguments]}") - # Access tags and other metadata - if prompt.meta: - fastmcp_meta = prompt.meta.get('fastmcp', {}) - print(f"Tags: {fastmcp_meta.get('tags', [])}") -``` - -For manual pagination control, use `list_prompts_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details. - -### Filtering by Tags - - - -You can use the `meta` field to filter prompts based on their tags: - -```python -async with client: - prompts = await client.list_prompts() - - # Filter prompts by tag - analysis_prompts = [ - prompt for prompt in prompts - if prompt.meta and - prompt.meta.get('fastmcp', {}) and - 'analysis' in prompt.meta.get('fastmcp', {}).get('tags', []) - ] - - print(f"Found {len(analysis_prompts)} analysis prompts") -``` - - -The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. Component versions are also included in the metadata when available (e.g., `meta.fastmcp.version`). Other MCP server implementations may not provide this metadata structure. - - -## Using Prompts - -### Basic Usage - -Request a rendered prompt using `get_prompt()` with the prompt name and arguments: +Request a rendered prompt with `get_prompt()`: ```python async with client: # Simple prompt without arguments result = await client.get_prompt("welcome_message") # result -> mcp.types.GetPromptResult - + # Access the generated messages for message in result.messages: print(f"Role: {message.role}") print(f"Content: {message.content}") ``` -### Prompts with Arguments - -Pass arguments as a dictionary to customize the prompt: +Pass arguments to customize the prompt: ```python async with client: - # Prompt with simple arguments result = await client.get_prompt("user_greeting", { "name": "Alice", "role": "administrator" }) - # Access the personalized messages for message in result.messages: print(f"Generated message: {message.content}") ``` -### Requesting Specific Versions - - - -When a server has multiple versions of a prompt, you can request a specific version instead of the default (highest) version. - -```python -async with client: - # Get the highest version (default) - result = await client.get_prompt("summarize", {"text": "..."}) - - # Get a specific version - result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0") -``` - -To discover available versions, check the `meta.fastmcp.versions` field when listing prompts. See [Version Selection](#version-selection) below for more details. - -## Automatic Argument Serialization +## Argument Serialization -FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. This allows you to pass typed objects directly: +FastMCP automatically serializes complex arguments to JSON strings as required by the MCP specification. You can pass typed objects directly: ```python from dataclasses import dataclass @@ -125,82 +57,31 @@ class UserData: age: int async with client: - # Complex arguments are automatically serialized result = await client.get_prompt("analyze_user", { - "user": UserData(name="Alice", age=30), # Automatically serialized to JSON - "preferences": {"theme": "dark"}, # Dict serialized to JSON string - "scores": [85, 92, 78], # List serialized to JSON string - "simple_name": "Bob" # Strings passed through unchanged + "user": UserData(name="Alice", age=30), # Automatically serialized + "preferences": {"theme": "dark"}, # Dict serialized + "scores": [85, 92, 78], # List serialized + "simple_name": "Bob" # Strings unchanged }) ``` -The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers can automatically deserialize these JSON strings back to the expected types. +The client handles serialization using `pydantic_core.to_json()` for consistent formatting. FastMCP servers automatically deserialize these JSON strings back to the expected types. -### Serialization Examples +## Working with Results -```python -async with client: - result = await client.get_prompt("data_analysis", { - # These will be automatically serialized to JSON strings: - "config": { - "format": "csv", - "include_headers": True, - "delimiter": "," - }, - "filters": [ - {"field": "age", "operator": ">", "value": 18}, - {"field": "status", "operator": "==", "value": "active"} - ], - # This remains a string: - "report_title": "Monthly Analytics Report" - }) -``` - -## Working with Prompt Results - -The `get_prompt()` method returns a `GetPromptResult` object containing a list of messages: +The `get_prompt()` method returns a `GetPromptResult` containing a list of messages: ```python async with client: result = await client.get_prompt("conversation_starter", {"topic": "climate"}) - - # Access individual messages + for i, message in enumerate(result.messages): print(f"Message {i + 1}:") print(f" Role: {message.role}") print(f" Content: {message.content.text if hasattr(message.content, 'text') else message.content}") ``` -## Raw MCP Protocol Access - -For access to the complete MCP protocol objects, use the `*_mcp` methods: - -```python -async with client: - # Raw MCP method returns full protocol object - prompts_result = await client.list_prompts_mcp() - # prompts_result -> mcp.types.ListPromptsResult - - prompt_result = await client.get_prompt_mcp("example_prompt", {"arg": "value"}) - # prompt_result -> mcp.types.GetPromptResult -``` - -## Multi-Server Clients - -When using multi-server clients, prompts are accessible without prefixing (unlike tools): - -```python -async with client: # Multi-server client - # Prompts from any server are directly accessible - result1 = await client.get_prompt("weather_prompt", {"city": "London"}) - result2 = await client.get_prompt("assistant_prompt", {"query": "help"}) -``` - -## Common Prompt Patterns - -### System Messages - -Many prompts generate system messages for LLM configuration: +Prompts can generate different message types. System messages configure LLM behavior: ```python async with client: @@ -208,15 +89,13 @@ async with client: "role": "helpful assistant", "expertise": "python programming" }) - + # Typically returns messages with role="system" system_message = result.messages[0] print(f"System prompt: {system_message.content}") ``` -### Conversation Templates - -Prompts can generate multi-turn conversation templates: +Conversation templates generate multi-turn flows: ```python async with client: @@ -224,56 +103,45 @@ async with client: "candidate_name": "Alice", "position": "Senior Developer" }) - + # Multiple messages for a conversation flow for message in result.messages: print(f"{message.role}: {message.content}") ``` - -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. - - ## Version Selection -FastMCP servers can expose multiple versions of the same prompt. By default, clients receive and request the highest version, but you can request a specific version when needed. - -### Discovering Versions - -When a server registers multiple versions of a prompt, the `list_prompts()` response includes version information in the metadata. The `meta.fastmcp.version` field shows which version is being returned, while `meta.fastmcp.versions` lists all available versions sorted from highest to lowest. - -```python -async with client: - prompts = await client.list_prompts() - - for prompt in prompts: - if prompt.meta: - fastmcp_meta = prompt.meta.get("fastmcp", {}) - version = fastmcp_meta.get("version") - all_versions = fastmcp_meta.get("versions") - if all_versions: - print(f"{prompt.name}: v{version} (available: {all_versions})") -``` - -Unversioned prompts omit these metadata fields entirely. - -### Getting Specific Versions - -Pass the `version` parameter to `get_prompt()` to render a specific version instead of the highest. +When a server exposes multiple versions of a prompt, you can request a specific version: ```python async with client: # Get the highest version (default) result = await client.get_prompt("summarize", {"text": "..."}) - # Get version 1.0 specifically + # Get a specific version result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0") ``` -If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version. +See [Metadata](/servers/versioning#version-discovery) for how to discover available versions. - -Version selection is a FastMCP extension to the MCP protocol. See [Versioning](/servers/versioning#requesting-specific-versions) for details on how this works at the protocol level for non-FastMCP clients. - \ No newline at end of file +## Multi-Server Clients + +When using multi-server clients, prompts are accessible directly without prefixing: + +```python +async with client: # Multi-server client + result1 = await client.get_prompt("weather_prompt", {"city": "London"}) + result2 = await client.get_prompt("assistant_prompt", {"query": "help"}) +``` + +## Raw Protocol Access + +For complete control, use `get_prompt_mcp()` which returns the full MCP protocol object: + +```python +async with client: + result = await client.get_prompt_mcp("example_prompt", {"arg": "value"}) + # result -> mcp.types.GetPromptResult +``` diff --git a/docs/clients/resources.mdx b/docs/clients/resources.mdx index 9eb99a265..a3e9300da 100644 --- a/docs/clients/resources.mdx +++ b/docs/clients/resources.mdx @@ -1,7 +1,7 @@ --- -title: Resource Operations +title: Reading Resources sidebarTitle: Resources -description: Access static and templated resources from MCP servers. +description: Access static and templated data sources from MCP servers. icon: folder-open --- @@ -9,226 +9,102 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -Resources are data sources exposed by MCP servers. They can be static files or dynamic templates that generate content based on parameters. +Use this when you need to read data from server-exposed resources like configuration files, generated content, or external data sources. -## Types of Resources - -MCP servers expose two types of resources: - -- **Static Resources**: Fixed content accessible via URI (e.g., configuration files, documentation) -- **Resource Templates**: Dynamic resources that accept parameters to generate content (e.g., API endpoints, database queries) - -## Listing Resources - -### Static Resources - -Use `list_resources()` to retrieve all static resources available on the server. When the server paginates results, the client automatically fetches all pages and returns the complete list. - -```python -async with client: - resources = await client.list_resources() - # resources -> list[mcp.types.Resource] - - for resource in resources: - print(f"Resource URI: {resource.uri}") - print(f"Name: {resource.name}") - print(f"Description: {resource.description}") - print(f"MIME Type: {resource.mimeType}") - # Access tags and other metadata - if resource.meta: - fastmcp_meta = resource.meta.get('fastmcp', {}) - print(f"Tags: {fastmcp_meta.get('tags', [])}") -``` - -For manual pagination control, use `list_resources_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details. - -### Resource Templates - -Use `list_resource_templates()` to retrieve available resource templates. When the server paginates results, the client automatically fetches all pages and returns the complete list. - -```python -async with client: - templates = await client.list_resource_templates() - # templates -> list[mcp.types.ResourceTemplate] - - for template in templates: - print(f"Template URI: {template.uriTemplate}") - print(f"Name: {template.name}") - print(f"Description: {template.description}") - # Access tags and other metadata - if template.meta: - fastmcp_meta = template.meta.get('fastmcp', {}) - print(f"Tags: {fastmcp_meta.get('tags', [])}") -``` - -For manual pagination control, use `list_resource_templates_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details. - -### Filtering by Tags - - - -You can use the `meta` field to filter resources based on their tags: - -```python -async with client: - resources = await client.list_resources() - - # Filter resources by tag - config_resources = [ - resource for resource in resources - if resource.meta and - resource.meta.get('fastmcp', {}) and - 'config' in resource.meta.get('fastmcp', {}).get('tags', []) - ] - - print(f"Found {len(config_resources)} config resources") -``` - - -The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. For versioned resources, `meta.fastmcp.version` shows the current version and `meta.fastmcp.versions` lists all available versions. Other MCP server implementations may not provide this metadata structure. - - -### Version Information - - - -When a server registers multiple versions of a resource, the metadata includes version information. - -```python -async with client: - resources = await client.list_resources() - - for resource in resources: - if resource.meta: - fastmcp_meta = resource.meta.get("fastmcp", {}) - version = fastmcp_meta.get("version") - all_versions = fastmcp_meta.get("versions") - if all_versions: - print(f"{resource.uri}: v{version} (available: {all_versions})") -``` - -To read a specific version, use the `version` parameter: - -```python -# Read a specific version -content = await client.read_resource("data://config", version="1.0") -``` +Resources are data sources exposed by MCP servers. They can be static files with fixed content, or dynamic templates that generate content based on parameters in the URI. ## Reading Resources -### Static Resources - -Read a static resource using its URI: +Read a resource using its URI: ```python async with client: - # Read a static resource content = await client.read_resource("file:///path/to/README.md") - # content -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents] - + # content -> list[TextResourceContents | BlobResourceContents] + # Access text content if hasattr(content[0], 'text'): print(content[0].text) - + # Access binary content if hasattr(content[0], 'blob'): print(f"Binary data: {len(content[0].blob)} bytes") ``` -### Resource Templates - -Read from a resource template by providing the URI with parameters: +Resource templates generate content based on URI parameters. The template defines a pattern like `weather://{{city}}/current`, and you fill in the parameters when reading: ```python async with client: - # Read a resource generated from a template - # For example, a template like "weather://{{city}}/current" + # Read from a resource template weather_content = await client.read_resource("weather://london/current") - - # Access the generated content - print(weather_content[0].text) # Assuming text JSON response + print(weather_content[0].text) ``` ## Content Types -Resources can return different content types: +Resources return different content types depending on what they expose. -### Text Resources +Text resources include configuration files, JSON data, and other human-readable content: ```python async with client: content = await client.read_resource("resource://config/settings.json") - + for item in content: if hasattr(item, 'text'): print(f"Text content: {item.text}") print(f"MIME type: {item.mimeType}") ``` -### Binary Resources +Binary resources include images, PDFs, and other non-text data: ```python async with client: content = await client.read_resource("resource://images/logo.png") - + for item in content: if hasattr(item, 'blob'): print(f"Binary content: {len(item.blob)} bytes") print(f"MIME type: {item.mimeType}") - + # Save to file with open("downloaded_logo.png", "wb") as f: f.write(item.blob) ``` -## Working with Multi-Server Clients +## Multi-Server Clients -When using multi-server clients, resource URIs are automatically prefixed with the server name: +When using multi-server clients, resource URIs are prefixed with the server name: ```python async with client: # Multi-server client - # Access resources from different servers weather_icons = await client.read_resource("weather://weather/icons/sunny") templates = await client.read_resource("resource://assistant/templates/list") - - print(f"Weather icon: {weather_icons[0].blob}") - print(f"Templates: {templates[0].text}") ``` -## Raw MCP Protocol Access +## Version Selection -For access to the complete MCP protocol objects, use the `*_mcp` methods: + + +When a server exposes multiple versions of a resource, you can request a specific version: ```python async with client: - # Raw MCP methods return full protocol objects - resources_result = await client.list_resources_mcp() - # resources_result -> mcp.types.ListResourcesResult - - templates_result = await client.list_resource_templates_mcp() - # templates_result -> mcp.types.ListResourceTemplatesResult - - content_result = await client.read_resource_mcp("resource://example") - # content_result -> mcp.types.ReadResourceResult + # Read the highest version (default) + content = await client.read_resource("data://config") + + # Read a specific version + content_v1 = await client.read_resource("data://config", version="1.0") ``` -## Common Resource URI Patterns +See [Metadata](/servers/versioning#version-discovery) for how to discover available versions. -Different MCP servers may use various URI schemes: +## Raw Protocol Access + +For complete control, use `read_resource_mcp()` which returns the full MCP protocol object: ```python -# File system resources -"file:///path/to/file.txt" - -# Custom protocol resources -"weather://london/current" -"database://users/123" - -# Generic resource protocol -"resource://config/settings" -"resource://templates/email" +async with client: + result = await client.read_resource_mcp("resource://example") + # result -> mcp.types.ReadResourceResult ``` - - -Resource URIs and their formats depend on the specific MCP server implementation. Check the server's documentation for available resources and their URI patterns. - \ No newline at end of file diff --git a/docs/clients/roots.mdx b/docs/clients/roots.mdx index 2a8d8c1f9..0370c119a 100644 --- a/docs/clients/roots.mdx +++ b/docs/clients/roots.mdx @@ -9,23 +9,28 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -Roots are a way for clients to inform servers about the resources they have access to. Servers can use this information to adjust behavior or provide more relevant responses. +Use this when you need to tell servers what local resources the client has access to. -## Setting Static Roots +Roots inform servers about resources the client can provide. Servers can use this information to adjust behavior or provide more relevant responses. + +## Static Roots Provide a list of roots when creating the client: - -```python Static Roots +```python from fastmcp import Client client = Client( - "my_mcp_server.py", + "my_mcp_server.py", roots=["/path/to/root1", "/path/to/root2"] ) ``` -```python Dynamic Roots Callback +## Dynamic Roots + +Use a callback to compute roots dynamically when the server requests them: + +```python from fastmcp import Client from fastmcp.client.roots import RequestContext @@ -34,9 +39,7 @@ async def roots_callback(context: RequestContext) -> list[str]: return ["/path/to/root1", "/path/to/root2"] client = Client( - "my_mcp_server.py", + "my_mcp_server.py", roots=roots_callback ) ``` - - diff --git a/docs/clients/sampling.mdx b/docs/clients/sampling.mdx index af1b65656..76c71be2a 100644 --- a/docs/clients/sampling.mdx +++ b/docs/clients/sampling.mdx @@ -1,7 +1,7 @@ --- title: LLM Sampling sidebarTitle: Sampling -description: Handle server-initiated LLM sampling requests. +description: Handle server-initiated LLM completion requests. icon: robot --- @@ -9,137 +9,32 @@ import { VersionBadge } from "/snippets/version-badge.mdx"; -MCP servers can request LLM completions from clients. The client handles these requests through a sampling handler callback. +Use this when you need to respond to server requests for LLM completions. -## Sampling Handler +MCP servers can request LLM completions from clients during tool execution. This enables servers to delegate AI reasoning to the client, which controls which LLM is used and how requests are made. -Provide a `sampling_handler` function when creating the client: +## Handler Template ```python from fastmcp import Client -from fastmcp.client.sampling import ( - SamplingMessage, - SamplingParams, - RequestContext, -) +from fastmcp.client.sampling import SamplingMessage, SamplingParams, RequestContext async def sampling_handler( messages: list[SamplingMessage], params: SamplingParams, context: RequestContext ) -> str: - # Your LLM integration logic here - # Extract text from messages and generate a response - return "Generated response based on the messages" + """ + Handle server requests for LLM completions. -client = Client( - "my_mcp_server.py", - sampling_handler=sampling_handler, -) -``` + Args: + messages: Conversation messages to send to the LLM + params: Sampling parameters (temperature, max_tokens, etc.) + context: Request context with metadata -### Handler Parameters - -The sampling handler receives three parameters: - - - - - - The role of the message. - - - - The content of the message. - - TextContent is most common, and has a `.text` attribute. - - - - - - - - The messages to sample from - - - - The server's preferences for which model to select. The client MAY ignore - these preferences. - - - The hints to use for model selection. - - - - The cost priority for model selection. - - - - The speed priority for model selection. - - - - The intelligence priority for model selection. - - - - - - An optional system prompt the server wants to use for sampling. - - - - A request to include context from one or more MCP servers (including the caller), to - be attached to the prompt. - - - - The sampling temperature. - - - - The maximum number of tokens to sample. - - - - The stop sequences to use for sampling. - - - - Optional metadata to pass through to the LLM provider. - - - - Optional list of tools the LLM can use during sampling. See [Using the OpenAI Handler](#using-the-openai-handler). - - - - Optional control over tool usage behavior (`auto`, `required`, or `none`). - - - - - - - - Unique identifier for the MCP 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: + Returns: + Generated text response from your LLM + """ # Extract message content conversation = [] for message in messages: @@ -149,44 +44,65 @@ async def basic_sampling_handler( # Use the system prompt if provided system_prompt = params.systemPrompt or "You are a helpful assistant." - # Here you would integrate with your preferred LLM service - # This is just a placeholder response - return f"Response based on conversation: {' | '.join(conversation)}" + # Integrate with your LLM service here + return "Generated response based on the messages" client = Client( "my_mcp_server.py", - sampling_handler=basic_sampling_handler + sampling_handler=sampling_handler, ) ``` - -If the client doesn't provide a sampling handler, servers can optionally configure a fallback handler. See [Server Sampling](/servers/sampling#sampling-fallback-handler) for details. - +## Handler Parameters -## Sampling Capabilities + + + The role of the message + -When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support (for simpler handlers that don't support tools), pass `sampling_capabilities` explicitly: + + The content of the message. TextContent has a `.text` attribute. + + -```python -from mcp.types import SamplingCapability + + + Optional system prompt the server wants to use + -client = Client( - "my_mcp_server.py", - sampling_handler=basic_handler, - sampling_capabilities=SamplingCapability(), # No tool support -) -``` + + Server preferences for model selection (hints, cost/speed/intelligence priorities) + + + + Sampling temperature + + + + Maximum tokens to generate + + + + Stop sequences for sampling + + + + Tools the LLM can use during sampling + + + + Tool usage behavior (`auto`, `required`, or `none`) + + ## Built-in Handlers -FastMCP provides built-in sampling handlers for OpenAI and Anthropic APIs. These handlers support the full sampling API including tool use, handling message conversion and response formatting automatically. +FastMCP provides built-in handlers for OpenAI and Anthropic APIs that support the full sampling API including tool use. ### OpenAI Handler -The OpenAI handler works with OpenAI's API and any OpenAI-compatible provider: - ```python from fastmcp import Client from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler @@ -197,7 +113,7 @@ client = Client( ) ``` -For OpenAI-compatible APIs (like local models), pass a custom client: +For OpenAI-compatible APIs (like local models): ```python from openai import AsyncOpenAI @@ -219,8 +135,6 @@ Install the OpenAI handler with `pip install fastmcp[openai]`. -The Anthropic handler uses Claude models via the Anthropic API: - ```python from fastmcp import Client from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler @@ -231,28 +145,28 @@ client = Client( ) ``` -You can pass a custom client for advanced configuration: - -```python -from anthropic import AsyncAnthropic - -client = Client( - "my_mcp_server.py", - sampling_handler=AnthropicSamplingHandler( - default_model="claude-sonnet-4-5", - client=AsyncAnthropic(), # Uses ANTHROPIC_API_KEY env var - ), -) -``` - Install the Anthropic handler with `pip install fastmcp[anthropic]`. -### Tool Execution +## Sampling Capabilities + +When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support for simpler handlers: + +```python +from mcp.types import SamplingCapability + +client = Client( + "my_mcp_server.py", + sampling_handler=basic_handler, + sampling_capabilities=SamplingCapability(), # No tool support +) +``` + +## Tool Execution Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results. To implement a custom sampling handler, see the [handler source code](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/client/sampling/handlers) as a reference. - \ No newline at end of file + diff --git a/docs/clients/tasks.mdx b/docs/clients/tasks.mdx index afac69e29..ce27520e4 100644 --- a/docs/clients/tasks.mdx +++ b/docs/clients/tasks.mdx @@ -1,7 +1,7 @@ --- title: Background Tasks -sidebarTitle: Background Tasks -description: Execute operations asynchronously and track their progress +sidebarTitle: Tasks +description: Execute operations asynchronously and track their progress. icon: clock tag: "NEW" --- @@ -10,13 +10,13 @@ import { VersionBadge } from "/snippets/version-badge.mdx" -The [MCP task protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) lets you request operations to run asynchronously. This returns a Task object immediately, letting you track progress, cancel operations, or await results. +Use this when you need to run long operations asynchronously while doing other work. -See [Server Background Tasks](/servers/tasks) for how to enable this on the server side. +The MCP task protocol lets you request operations to run in the background. The call returns a Task object immediately, letting you track progress, cancel operations, or await results. ## Requesting Background Execution -Pass `task=True` to run an operation as a background task. The call returns immediately with a Task object while the work executes on the server. +Pass `task=True` to run an operation as a background task: ```python from fastmcp import Client @@ -41,24 +41,35 @@ resource_task = await client.read_resource("file://large.txt", task=True) prompt_task = await client.get_prompt("my_prompt", args, task=True) ``` -## Working with Task Objects +## Task API -All task types share a common interface for retrieving results, checking status, and receiving updates. +All task types share a common interface. -To get the result, call `await task.result()` or simply `await task`. This blocks until the task completes and returns the result. You can also check status without blocking using `await task.status()`, which returns the current state (`"working"`, `"completed"`, `"failed"`, or `"cancelled"`) along with any progress message from the server. +### Getting Results + +Call `await task.result()` or simply `await task` to block until the task completes: ```python task = await client.call_tool("analyze", {"text": "hello"}, task=True) -# Check current status (non-blocking) -status = await task.status() -print(f"{status.status}: {status.statusMessage}") - # Wait for result (blocking) result = await task.result() +# or: result = await task ``` -For more control over waiting, use `task.wait()` with an optional timeout or target state: +### Checking Status + +Check the current status without blocking: + +```python +status = await task.status() +print(f"{status.status}: {status.statusMessage}") +# status.status is "working", "completed", "failed", or "cancelled" +``` + +### Waiting with Control + +Use `task.wait()` for more control over waiting: ```python # Wait up to 30 seconds for completion @@ -68,11 +79,17 @@ status = await task.wait(timeout=30.0) status = await task.wait(state="completed", timeout=30.0) ``` -To cancel a running task, call `await task.cancel()`. +### Cancellation -### Real-Time Status Updates +Cancel a running task: -Register callbacks to receive status updates as the server reports progress. Both sync and async callbacks are supported. +```python +await task.cancel() +``` + +## Status Updates + +Register callbacks to receive real-time status updates as the server reports progress: ```python def on_status_change(status): @@ -87,9 +104,34 @@ async def on_status_async(status): task.on_status_change(on_status_async) ``` +### Handler Template + +```python +from fastmcp import Client + +def status_handler(status): + """ + Handle task status updates. + + Args: + status: Task status object with: + - taskId: Unique task identifier + - status: "working", "completed", "failed", or "cancelled" + - statusMessage: Optional progress message from server + """ + if status.status == "working": + print(f"Progress: {status.statusMessage}") + elif status.status == "completed": + print("Task completed") + elif status.status == "failed": + print(f"Task failed: {status.statusMessage}") + +task.on_status_change(status_handler) +``` + ## Graceful Degradation -You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline. The Task API provides a consistent interface either way. +You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline. ```python task = await client.call_tool("my_tool", args, task=True) @@ -103,9 +145,9 @@ else: result = await task.result() ``` -This means you can write task-aware client code without worrying about server capabilities. +This lets you write task-aware client code without worrying about server capabilities. -## Complete Example +## Example ```python import asyncio @@ -136,3 +178,5 @@ async def main(): asyncio.run(main()) ``` + +See [Server Background Tasks](/servers/tasks) for how to enable background task support on the server side. diff --git a/docs/clients/tools.mdx b/docs/clients/tools.mdx index 7fad067e3..1541f593e 100644 --- a/docs/clients/tools.mdx +++ b/docs/clients/tools.mdx @@ -1,7 +1,7 @@ --- -title: Tool Operations +title: Calling Tools sidebarTitle: Tools -description: Discover and execute server-side tools with the FastMCP client. +description: Execute server-side tools and handle structured results. icon: wrench --- @@ -9,88 +9,40 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -Tools are executable functions exposed by MCP servers. The FastMCP client provides methods to discover available tools and execute them with arguments. +Use this when you need to execute server-side functions and process their results. -## Discovering Tools +Tools are executable functions exposed by MCP servers. The client's `call_tool()` method executes a tool by name with arguments and returns structured results. -Use `list_tools()` to retrieve all tools available on the server. When the server paginates results, the client automatically fetches all pages and returns the complete list. +## Basic Execution ```python async with client: - tools = await client.list_tools() - # tools -> list[mcp.types.Tool] - - for tool in tools: - print(f"Tool: {tool.name}") - print(f"Description: {tool.description}") - if tool.inputSchema: - print(f"Parameters: {tool.inputSchema}") - # Access tags and other metadata - if tool.meta: - fastmcp_meta = tool.meta.get('fastmcp', {}) - print(f"Tags: {fastmcp_meta.get('tags', [])}") -``` - -For manual pagination control, use `list_tools_mcp()` with the `cursor` parameter. See [Pagination](/servers/pagination#manual-pagination) for details. - -### Filtering by Tags - - - -You can use the `meta` field to filter tools based on their tags: - -```python -async with client: - tools = await client.list_tools() - - # Filter tools by tag - analysis_tools = [ - tool for tool in tools - if tool.meta and - tool.meta.get('fastmcp', {}) and - 'analysis' in tool.meta.get('fastmcp', {}).get('tags', []) - ] - - print(f"Found {len(analysis_tools)} analysis tools") -``` - - -The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. Component versions are also included in the metadata when available (e.g., `meta.fastmcp.version`). Other MCP server implementations may not provide this metadata structure. - - -## Executing Tools - -### Basic Execution - -Execute a tool using `call_tool()` with the tool name and arguments: - -```python -async with client: - # Simple tool call result = await client.call_tool("add", {"a": 5, "b": 3}) # result -> CallToolResult with structured and unstructured data - + # Access structured data (automatically deserialized) - print(result.data) # 8 (int) or {"result": 8} for primitive types - - # Access traditional content blocks - print(result.content[0].text) # "8" (TextContent) + print(result.data) # 8 + + # Access traditional content blocks + print(result.content[0].text) # "8" ``` -### Advanced Execution Options +Arguments are passed as a dictionary. For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server). -The `call_tool()` method supports additional parameters for timeout control and progress monitoring: +## Execution Options + +The `call_tool()` method supports timeout control and progress monitoring: ```python async with client: # With timeout (aborts if execution takes longer than 2 seconds) result = await client.call_tool( - "long_running_task", - {"param": "value"}, + "long_running_task", + {"param": "value"}, timeout=2.0 ) - - # With progress handler (to track execution progress) + + # With progress handler result = await client.call_tool( "long_running_task", {"param": "value"}, @@ -98,19 +50,103 @@ async with client: ) ``` -**Parameters:** -- `name`: The tool name (string) -- `arguments`: Dictionary of arguments to pass to the tool (optional) -- `version`: Specific tool version to call (optional, see [Version Selection](#version-selection) below) -- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout) -- `progress_handler`: Progress callback function (optional, overrides client-level handler) -- `meta`: Dictionary of metadata to send with the request (optional, see below) +## Structured Results + + + +Tool execution returns a `CallToolResult` object. The `.data` property provides fully hydrated Python objects including complex types like datetimes and UUIDs, reconstructed from the server's output schema. + +```python +from datetime import datetime +from uuid import UUID + +async with client: + result = await client.call_tool("get_weather", {"city": "London"}) + + # FastMCP reconstructs complete Python objects + weather = result.data + print(f"Temperature: {weather.temperature}C at {weather.timestamp}") + + # Complex types are properly deserialized + assert isinstance(weather.timestamp, datetime) + assert isinstance(weather.station_id, UUID) + + # Raw structured JSON is also available + print(f"Raw JSON: {result.structured_content}") +``` + + + + Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). FastMCP exclusive. + + + + Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.). + + + + Standard MCP structured JSON data as sent by the server. + + + + Boolean indicating if the tool execution failed. + + + +For tools without output schemas or when deserialization fails, `.data` will be `None`. Fall back to content blocks in that case: + +```python +async with client: + result = await client.call_tool("legacy_tool", {"param": "value"}) + + if result.data is not None: + print(f"Structured: {result.data}") + else: + for content in result.content: + if hasattr(content, 'text'): + print(f"Text result: {content.text}") +``` + + +FastMCP servers automatically wrap primitive results (like `int`, `str`, `bool`) in a `{"result": value}` structure. FastMCP clients automatically unwrap this, so you get the original value in `.data`. + + +## Error Handling + +By default, `call_tool()` raises a `ToolError` if the tool execution fails: + +```python +from fastmcp.exceptions import ToolError + +async with client: + try: + result = await client.call_tool("potentially_failing_tool", {"param": "value"}) + print("Tool succeeded:", result.data) + except ToolError as e: + print(f"Tool failed: {e}") +``` + +To handle errors manually instead of catching exceptions, disable automatic error raising: + +```python +async with client: + result = await client.call_tool( + "potentially_failing_tool", + {"param": "value"}, + raise_on_error=False + ) + + if result.is_error: + print(f"Tool failed: {result.content[0].text}") + else: + print(f"Tool succeeded: {result.data}") +``` ## Sending Metadata -The `meta` parameter sends ancillary information alongside tool calls. This can be used for various purposes like observability, debugging, client identification, or any context the server may need beyond the tool's primary arguments. +The `meta` parameter sends ancillary information alongside tool calls for observability, debugging, or client identification: ```python async with client: @@ -128,215 +164,20 @@ async with client: ) ``` -The structure and usage of `meta` is determined by your application. See [Client Metadata](/servers/context#client-metadata) in the server documentation to learn how to access this data in your tool implementations. +See [Client Metadata](/servers/context#client-metadata) to learn how servers access this data. -## Handling Results - - - -Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes. - -### CallToolResult Properties - - - - **FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas. - - - - Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.) available from all MCP servers. - - - - Standard MCP structured JSON data as sent by the server, available from all MCP servers that support structured outputs. - - - - Boolean indicating if the tool execution failed. - - - -### Structured Data Access - -FastMCP's `.data` property provides fully hydrated Python objects, not just JSON dictionaries. This includes complex type reconstruction: - -```python -from datetime import datetime -from uuid import UUID - -async with client: - result = await client.call_tool("get_weather", {"city": "London"}) - - # FastMCP reconstructs complete Python objects from the server's output schema - weather = result.data # Server-defined WeatherReport object - print(f"Temperature: {weather.temperature}°C at {weather.timestamp}") - print(f"Station: {weather.station_id}") - print(f"Humidity: {weather.humidity}%") - - # The timestamp is a real datetime object, not a string! - assert isinstance(weather.timestamp, datetime) - assert isinstance(weather.station_id, UUID) - - # Compare with raw structured JSON (standard MCP) - print(f"Raw JSON: {result.structured_content}") - # {"temperature": 20, "timestamp": "2024-01-15T14:30:00Z", "station_id": "123e4567-..."} - - # Traditional content blocks (standard MCP) - print(f"Text content: {result.content[0].text}") -``` - -### Fallback Behavior - -For tools without output schemas or when deserialization fails, `.data` will be `None`: - -```python -async with client: - result = await client.call_tool("legacy_tool", {"param": "value"}) - - if result.data is not None: - # Structured output available and successfully deserialized - print(f"Structured: {result.data}") - else: - # No structured output or deserialization failed - use content blocks - for content in result.content: - if hasattr(content, 'text'): - print(f"Text result: {content.text}") - elif hasattr(content, 'data'): - print(f"Binary data: {len(content.data)} bytes") -``` - -### Primitive Type Unwrapping - - -FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object. - - -```python -async with client: - result = await client.call_tool("calculate_sum", {"a": 5, "b": 3}) - - # FastMCP client automatically unwraps for convenience - print(result.data) # 8 (int) - the original value - - # Raw structured content shows the server-side wrapping - print(result.structured_content) # {"result": 8} - - # Other MCP clients would need to manually access ["result"] - # value = result.structured_content["result"] # Not needed with FastMCP! -``` - -## Error Handling - -### Exception-Based Error Handling - -By default, `call_tool()` raises a `ToolError` if the tool execution fails: - -```python -from fastmcp.exceptions import ToolError - -async with client: - try: - result = await client.call_tool("potentially_failing_tool", {"param": "value"}) - print("Tool succeeded:", result.data) - except ToolError as e: - print(f"Tool failed: {e}") -``` - -### Manual Error Checking - -You can disable automatic error raising and manually check the result: - -```python -async with client: - result = await client.call_tool( - "potentially_failing_tool", - {"param": "value"}, - raise_on_error=False - ) - - if result.is_error: - print(f"Tool failed: {result.content[0].text}") - else: - print(f"Tool succeeded: {result.data}") -``` - -### Raw MCP Protocol Access +## Raw Protocol Access For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object: ```python async with client: - result = await client.call_tool_mcp("potentially_failing_tool", {"param": "value"}) + result = await client.call_tool_mcp("my_tool", {"param": "value"}) # result -> mcp.types.CallToolResult - + if result.isError: print(f"Tool failed: {result.content}") else: print(f"Tool succeeded: {result.content}") # Note: No automatic deserialization with call_tool_mcp() ``` - -## Argument Handling - -Arguments are passed as a dictionary to the tool: - -```python -async with client: - # Simple arguments - result = await client.call_tool("greet", {"name": "World"}) - - # Complex arguments - result = await client.call_tool("process_data", { - "config": {"format": "json", "validate": True}, - "items": [1, 2, 3, 4, 5], - "metadata": {"source": "api", "version": "1.0"} - }) -``` - - -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). - - -## Version Selection - - - -FastMCP servers can expose multiple versions of the same tool. By default, clients receive and call the highest version, but you can request a specific version when needed. - -### Discovering Versions - -When a server registers multiple versions of a tool, the `list_tools()` response includes version information in the metadata. The `meta.fastmcp.version` field shows which version is being returned, while `meta.fastmcp.versions` lists all available versions sorted from highest to lowest. - -```python -async with client: - tools = await client.list_tools() - - for tool in tools: - if tool.meta: - fastmcp_meta = tool.meta.get("fastmcp", {}) - version = fastmcp_meta.get("version") - all_versions = fastmcp_meta.get("versions") - if all_versions: - print(f"{tool.name}: v{version} (available: {all_versions})") -``` - -Unversioned tools omit these metadata fields entirely. - -### Calling Specific Versions - -Pass the `version` parameter to `call_tool()` to execute a specific version instead of the highest. - -```python -async with client: - # Call the highest version (default) - result = await client.call_tool("calculate", {"x": 1, "y": 2}) - - # Call version 1.0 specifically - result_v1 = await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0") -``` - -If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version. - - -Version selection is a FastMCP extension to the MCP protocol. See [Versioning](/servers/versioning#requesting-specific-versions) for details on how this works at the protocol level for non-FastMCP clients. - \ No newline at end of file diff --git a/docs/clients/transports.mdx b/docs/clients/transports.mdx index b7129acc2..7e3eb03db 100644 --- a/docs/clients/transports.mdx +++ b/docs/clients/transports.mdx @@ -1,7 +1,7 @@ --- title: Client Transports sidebarTitle: Transports -description: Configure how FastMCP Clients connect to and communicate with servers. +description: Configure how clients connect to and communicate with MCP servers. icon: link --- @@ -9,88 +9,30 @@ import { VersionBadge } from "/snippets/version-badge.mdx" -The FastMCP `Client` communicates with MCP servers through transport objects that handle the underlying connection mechanics. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration—environment variables, authentication, session management, and more. - -Think of transports as configurable adapters between your client code and MCP servers. Each transport type handles a different communication pattern: subprocesses with pipes, HTTP connections, or direct in-memory calls. - -## Choosing the Right Transport - -- **Use [STDIO Transport](#stdio-transport)** when you need to run local MCP servers with full control over their environment and lifecycle -- **Use [Remote Transports](#remote-transports)** when connecting to production services or shared MCP servers running independently -- **Use [In-Memory Transport](#in-memory-transport)** for testing FastMCP servers without subprocess or network overhead -- **Use [MCP JSON Configuration](#mcp-json-configuration-transport)** when you need to connect to multiple servers defined in configuration files +Transports handle the underlying connection between your client and MCP servers. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration. ## STDIO Transport -STDIO (Standard Input/Output) transport communicates with MCP servers through subprocess pipes. This is the standard mechanism used by desktop clients like Claude Desktop and is the primary way to run local MCP servers. - -### The Client Runs the Server +STDIO transport communicates with MCP servers through subprocess pipes. When using STDIO, your client launches and manages the server process, controlling its lifecycle and environment. -**Critical Concept**: When using STDIO transport, your client actually launches and manages the server process. This is fundamentally different from network transports where you connect to an already-running server. Understanding this relationship is key to using STDIO effectively. +STDIO servers run in isolated environments by default. They do not inherit your shell's environment variables. You must explicitly pass any configuration the server needs. -With STDIO transport, your client: -- Starts the server as a subprocess when you connect -- Manages the server's lifecycle (start, stop, restart) -- Controls the server's environment and configuration -- Communicates through stdin/stdout pipes - -This architecture enables powerful local integrations but requires understanding environment isolation and process management. - -### Environment Isolation - -STDIO servers run in isolated environments by default. This is a security feature enforced by the MCP protocol to prevent accidental exposure of sensitive data. - -When your client launches an MCP server: -- The server does NOT inherit your shell's environment variables -- API keys, paths, and other configuration must be explicitly passed -- The working directory and system paths may differ from your shell - -To pass environment variables to your server, use the `env` parameter: - ```python from fastmcp import Client - -# If your server needs environment variables (like API keys), -# you must explicitly pass them: -client = Client( - "my_server.py", - env={"API_KEY": "secret", "DEBUG": "true"} -) - -# This won't work - the server runs in isolation: -# export API_KEY="secret" # in your shell -# client = Client("my_server.py") # server can't see API_KEY -``` - -### Basic Usage - -To use STDIO transport, you create a transport instance with the command and arguments needed to run your server: - -```python from fastmcp.client.transports import StdioTransport -transport = StdioTransport( - command="python", - args=["my_server.py"] -) -client = Client(transport) -``` - -You can configure additional settings like environment variables, working directory, or command arguments: - -```python transport = StdioTransport( command="python", args=["my_server.py", "--verbose"], - env={"LOG_LEVEL": "DEBUG"}, + env={"API_KEY": "secret", "LOG_LEVEL": "DEBUG"}, cwd="/path/to/server" ) client = Client(transport) ``` -For convenience, the client can also infer STDIO transport from file paths, but this doesn't allow configuration: +For convenience, the client can infer STDIO transport from file paths, though this limits configuration options: ```python from fastmcp import Client @@ -100,26 +42,18 @@ client = Client("my_server.py") # Limited - no configuration options ### Environment Variables -Since STDIO servers don't inherit your environment, you need strategies for passing configuration. Here are two common approaches: +Since STDIO servers do not inherit your environment, you need strategies for passing configuration. -**Selective forwarding** passes only the variables your server actually needs: +**Selective forwarding** passes only the variables your server needs: ```python import os from fastmcp.client.transports import StdioTransport required_vars = ["API_KEY", "DATABASE_URL", "REDIS_HOST"] -env = { - var: os.environ[var] - for var in required_vars - if var in os.environ -} +env = {var: os.environ[var] for var in required_vars if var in os.environ} -transport = StdioTransport( - command="python", - args=["server.py"], - env=env -) +transport = StdioTransport(command="python", args=["server.py"], env=env) client = Client(transport) ``` @@ -130,33 +64,24 @@ from dotenv import dotenv_values from fastmcp.client.transports import StdioTransport env = dotenv_values(".env") -transport = StdioTransport( - command="python", - args=["server.py"], - env=env -) +transport = StdioTransport(command="python", args=["server.py"], env=env) client = Client(transport) ``` ### Session Persistence -STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This improves performance by reusing the same subprocess for multiple connections, but can be controlled when you need isolation. - -By default, the subprocess persists between connections: +STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This reuses the same subprocess for multiple connections, improving performance. ```python from fastmcp.client.transports import StdioTransport -transport = StdioTransport( - command="python", - args=["server.py"] -) +transport = StdioTransport(command="python", args=["server.py"]) client = Client(transport) async def efficient_multiple_operations(): async with client: await client.ping() - + async with client: # Reuses the same subprocess await client.call_tool("process_data", {"file": "data.csv"}) ``` @@ -164,51 +89,19 @@ async def efficient_multiple_operations(): For complete isolation between connections, disable session persistence: ```python -transport = StdioTransport( - command="python", - args=["server.py"], - keep_alive=False -) -client = Client(transport) +transport = StdioTransport(command="python", args=["server.py"], keep_alive=False) ``` -Use `keep_alive=False` when you need complete isolation (e.g., in test suites) or when server state could cause issues between connections. - -### Specialized STDIO Transports - -FastMCP provides convenience transports that are thin wrappers around `StdioTransport` with pre-configured commands: - -- **`PythonStdioTransport`** - Uses `python` command for `.py` files -- **`NodeStdioTransport`** - Uses `node` command for `.js` files -- **`UvStdioTransport`** - Uses `uv` for Python packages (uses `env_vars` parameter) -- **`UvxStdioTransport`** - Uses `uvx` for Python packages (uses `env_vars` parameter) -- **`NpxStdioTransport`** - Uses `npx` for Node packages (uses `env_vars` parameter) - -For most use cases, instantiate `StdioTransport` directly with your desired command. These specialized transports are primarily useful for client inference shortcuts. - -## Remote Transports - -Remote transports connect to MCP servers running as web services. This is a fundamentally different model from STDIO transports—instead of your client launching and managing a server process, you connect to an already-running service that manages its own environment and lifecycle. - -### Streamable HTTP Transport +## HTTP Transport -Streamable HTTP is the recommended transport for production deployments, providing efficient bidirectional streaming over HTTP connections. - -- **Class:** `StreamableHttpTransport` -- **Server compatibility:** FastMCP servers running with `mcp run --transport http` - -The transport requires a URL and optionally supports custom headers for authentication and configuration: +HTTP transport connects to MCP servers running as web services. This is the recommended transport for production deployments. ```python +from fastmcp import Client from fastmcp.client.transports import StreamableHttpTransport -# Basic connection -transport = StreamableHttpTransport(url="https://api.example.com/mcp") -client = Client(transport) - -# With custom headers for authentication transport = StreamableHttpTransport( url="https://api.example.com/mcp", headers={ @@ -219,9 +112,10 @@ transport = StreamableHttpTransport( client = Client(transport) ``` -For convenience, FastMCP also provides authentication helpers: +FastMCP also provides authentication helpers: ```python +from fastmcp import Client from fastmcp.client.auth import BearerAuth client = Client( @@ -230,14 +124,9 @@ client = Client( ) ``` -### SSE Transport (Legacy) +### SSE Transport -Server-Sent Events transport is maintained for backward compatibility but is superseded by Streamable HTTP for new deployments. - -- **Class:** `SSETransport` -- **Server compatibility:** FastMCP servers running with `mcp run --transport sse` - -SSE transport supports the same configuration options as Streamable HTTP: +Server-Sent Events transport is maintained for backward compatibility. Use Streamable HTTP for new deployments unless you have specific infrastructure requirements. ```python from fastmcp.client.transports import SSETransport @@ -249,17 +138,9 @@ transport = SSETransport( client = Client(transport) ``` -Use Streamable HTTP for new deployments unless you have specific infrastructure requirements for SSE. - ## In-Memory Transport -In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing and development. - -- **Class:** `FastMCPTransport` - - -Unlike STDIO transports, in-memory servers have full access to your Python process's environment. They share the same memory space and environment variables as your client code—no isolation or explicit environment passing required. - +In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing. ```python from fastmcp import FastMCP, Client @@ -278,15 +159,19 @@ async with client: result = await client.call_tool("greet", {"name": "World"}) ``` -## MCP JSON Configuration Transport + +Unlike STDIO transports, in-memory servers share the same memory space and environment variables as your client code. + + +## Multi-Server Configuration -This transport supports the emerging MCP JSON configuration standard for defining multiple servers: - -- **Class:** `MCPConfigTransport` +Connect to multiple servers defined in a configuration dictionary: ```python +from fastmcp import Client + config = { "mcpServers": { "weather": { @@ -309,9 +194,9 @@ async with client: answer = await client.call_tool("assistant_ask", {"question": "What?"}) ``` -### Tool Transformation with FastMCP and MCPConfig +### Tool Transformations -FastMCP supports basic tool transformations to be defined alongside the MCP Servers in the MCPConfig file. +FastMCP supports tool transformations within the configuration. You can change names, descriptions, tags, and arguments for tools from a server. ```python config = { @@ -319,65 +204,32 @@ config = { "weather": { "url": "https://weather.example.com/mcp", "transport": "http", - "tools": { } # <--- This is the tool transformation section - } - } -} -``` - -With these transformations, you can transform (change) the name, title, description, tags, enablement, and arguments of a tool. - -For each argument the tool takes, you can transform (change) the name, description, default, visibility, whether it's required, and you can provide example values. - -In the following example, we're transforming the `weather_get_forecast` tool to only retrieve the weather for `Miami` and hiding the `city` argument from the client. - -```python -tool_transformations = { - "weather_get_forecast": { - "name": "miami_weather", - "description": "Get the weather for Miami", - "arguments": { - "city": { - "name": "city", - "default": "Miami", - "hide": True, + "tools": { + "weather_get_forecast": { + "name": "miami_weather", + "description": "Get the weather for Miami", + "arguments": { + "city": { + "default": "Miami", + "hide": True, + } + } + } } } } } +``` +To filter tools by tag, use `include_tags` or `exclude_tags` at the server level: + +```python config = { "mcpServers": { "weather": { "url": "https://weather.example.com/mcp", - "transport": "http", - "tools": tool_transformations + "include_tags": ["forecast"] # Only tools with this tag } } } ``` - -#### Allowlisting and Blocklisting Tools - -Tools can be allowlisted or blocklisted from the client by applying `tags` to the tools on the server. In the following example, we're allowlisting only tools marked with the `forecast` tag, all other tools will be unavailable to the client. - -```python -tool_transformations = { - "weather_get_forecast": { - "enabled": True, - "tags": ["forecast"] - } -} - - -config = { - "mcpServers": { - "weather": { - "url": "https://weather.example.com/mcp", - "transport": "http", - "tools": tool_transformations, - "include_tags": ["forecast"] - } - } -} -``` \ No newline at end of file diff --git a/docs/css/style.css b/docs/css/style.css index 8b36b42cd..8c484ce96 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -17,4 +17,43 @@ h6 code:not(pre code) { background-color: rgba(247, 37, 133, 0.09); } +/* V2 banner - inside content-container, breaks out of padding with negative margins */ +#v2-banner { + display: block; + background: linear-gradient(135deg, #4cc9f0 0%, #2d00f7 100%); + color: white; + text-align: center; + padding: 10px 16px; + font-size: 0.875rem; + font-weight: 600; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin: -2rem -2rem 1.5rem -2rem; + width: calc(100% + 4rem); + border-radius: 8px 8px 0 0; +} + +#v2-banner a { + color: white; + text-decoration: underline; + font-weight: 700; +} + +#v2-banner a:hover { + opacity: 0.9; +} + +@media (min-width: 1024px) { + #v2-banner { + margin: -3rem -4rem 1.5rem -4rem; + width: calc(100% + 8rem); + } +} + +.dark #v2-banner { + background: linear-gradient(135deg, #2d00f7 0%, #4cc9f0 100%); +} + + + + diff --git a/docs/deployment/http.mdx b/docs/deployment/http.mdx index ca636cb17..962c2e013 100644 --- a/docs/deployment/http.mdx +++ b/docs/deployment/http.mdx @@ -3,7 +3,6 @@ title: HTTP Deployment sidebarTitle: HTTP Deployment description: Deploy your FastMCP server over HTTP for remote access icon: server -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx"; diff --git a/docs/docs.json b/docs/docs.json index fe8935a37..ff58e4a57 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -77,583 +77,747 @@ } }, "navigation": { - "dropdowns": [ + "versions": [ { - "dropdown": "Documentation", - "groups": [ + "version": "v3", + "dropdowns": [ { - "group": "Get Started", - "pages": [ - "getting-started/welcome", - "getting-started/installation", - "getting-started/quickstart", - "updates" - ] - }, - { - "group": "Servers", - "pages": [ - "servers/server", + "dropdown": "Documentation", + "icon": "book", + "groups": [ { - "group": "Components", - "icon": "toolbox", + "group": "Get Started", "pages": [ - "servers/tools", - "servers/resources", - "servers/prompts" + "getting-started/welcome", + "getting-started/installation", + "getting-started/quickstart", + "updates" ] }, { - "group": "Providers", - "icon": "layer-group", + "group": "Servers", + "pages": [ + "servers/server", + { + "group": "Core Components", + "icon": "toolbox", + "pages": [ + "servers/tools", + "servers/resources", + "servers/prompts", + "servers/context" + ] + }, + { + "group": "Features", + "icon": "stars", + "pages": [ + "servers/tasks", + "servers/elicitation", + "servers/icons", + "servers/lifespan", + "servers/logging", + "servers/middleware", + "servers/pagination", + "servers/progress", + "servers/sampling", + "servers/storage-backends", + "servers/telemetry", + "servers/versioning" + ] + }, + { + "group": "Authentication", + "icon": "key", + "pages": [ + "servers/auth/authentication", + "servers/auth/token-verification", + "servers/auth/remote-oauth", + "servers/auth/oauth-proxy", + "servers/auth/oidc-proxy", + "servers/auth/full-oauth-server" + ] + }, + "servers/authorization" + ] + }, + { + "group": "MCP Providers", "pages": [ "servers/providers/overview", - "servers/providers/transforms", - "servers/providers/resources-as-tools", - "servers/providers/prompts-as-tools", "servers/providers/local", "servers/providers/filesystem", - "servers/providers/skills", - "servers/providers/mounting", "servers/providers/proxy", - "servers/providers/custom" + "servers/providers/skills", + "servers/providers/custom", + "servers/providers/mounting" ] }, { - "group": "Features", - "icon": "stars", + "group": "MCP Transforms", "pages": [ - "servers/authorization", - "servers/context", - "servers/elicitation", - "servers/icons", - "servers/lifespan", - "servers/logging", - "servers/middleware", - "servers/pagination", - "servers/progress", - "servers/sampling", - "servers/storage-backends", - "servers/tasks", - "servers/telemetry", - "servers/versioning", - "servers/visibility" + "servers/providers/transforms", + "servers/providers/namespace", + "servers/providers/tool-transformation", + "servers/visibility", + "servers/providers/resources-as-tools", + "servers/providers/prompts-as-tools" ] }, { - "group": "Authentication", - "icon": "shield-check", - "pages": [ - "servers/auth/authentication", - "servers/auth/token-verification", - "servers/auth/remote-oauth", - "servers/auth/oauth-proxy", - "servers/auth/oidc-proxy", - "servers/auth/full-oauth-server" - ] - }, - { - "group": "Deployment", - "icon": "rocket", - "pages": [ - "deployment/running-server", - "deployment/http", - "deployment/fastmcp-cloud", - "deployment/server-configuration" - ] - } - ] - }, - { - "group": "Clients", - "pages": [ - { - "group": "Essentials", - "icon": "cube", + "group": "Clients", "pages": [ "clients/client", - "clients/transports" - ] - }, - { - "group": "Core Operations", - "icon": "handshake", - "pages": [ - "clients/tools", - "clients/resources", - "clients/prompts" - ] - }, - { - "group": "Advanced Features", - "icon": "stars", - "pages": [ - "clients/elicitation", - "clients/logging", - "clients/progress", - "clients/sampling", - "clients/tasks", - "clients/messages", - "clients/roots" - ] - }, - { - "group": "Authentication", - "icon": "user-shield", - "pages": [ - "clients/auth/oauth", - "clients/auth/bearer" - ] - } - ] - }, - { - "group": "Integrations", - "pages": [ - { - "group": "Providers", - "icon": "globe", - "pages": [ - "integrations/fastapi", - "integrations/openapi" - ] - }, - { - "group": "Authentication", - "icon": "key", - "pages": [ - "integrations/auth0", - "integrations/authkit", - "integrations/aws-cognito", - "integrations/azure", - "integrations/descope", - "integrations/discord", - "integrations/github", - "integrations/google", - "integrations/oci", - "integrations/scalekit", - "integrations/supabase", - "integrations/workos" - ] - }, - { - "group": "Authorization", - "icon": "shield-check", - "pages": [ - "integrations/eunomia-authorization", - "integrations/permit" - ] - }, - { - "group": "AI Assistants", - "icon": "robot", - "pages": [ - "integrations/chatgpt", - "integrations/claude-code", - "integrations/claude-desktop", - "integrations/cursor", - "integrations/gemini-cli", - "integrations/mcp-json-configuration" - ] - }, - { - "group": "AI SDKs", - "icon": "code", - "pages": [ - "integrations/anthropic", - "integrations/gemini", - "integrations/openai" - ] - } - ] - }, - { - "group": "Patterns", - "pages": [ - "patterns/decorating-methods", - "patterns/cli", - "patterns/contrib", - "patterns/testing" - ] - }, - { - "group": "Development", - "pages": [ - "development/contributing", - "development/tests", - "development/releases", - "development/upgrade-guide", - "changelog" - ] - } - ], - "icon": "book" - }, - { - "anchors": [ - { - "anchor": "Python SDK", - "icon": "python", - "pages": [ - "python-sdk/fastmcp-decorators", - "python-sdk/fastmcp-dependencies", - "python-sdk/fastmcp-exceptions", - "python-sdk/fastmcp-mcp_config", - "python-sdk/fastmcp-settings", - "python-sdk/fastmcp-telemetry", - { - "group": "fastmcp.cli", - "pages": [ - "python-sdk/fastmcp-cli-__init__", - "python-sdk/fastmcp-cli-cli", + "clients/transports", { - "group": "install", + "group": "Core Operations", + "icon": "toolbox", "pages": [ - "python-sdk/fastmcp-cli-install-__init__", - "python-sdk/fastmcp-cli-install-claude_code", - "python-sdk/fastmcp-cli-install-claude_desktop", - "python-sdk/fastmcp-cli-install-cursor", - "python-sdk/fastmcp-cli-install-gemini_cli", - "python-sdk/fastmcp-cli-install-mcp_json", - "python-sdk/fastmcp-cli-install-shared" + "clients/tools", + "clients/resources", + "clients/prompts" ] }, - "python-sdk/fastmcp-cli-run", - "python-sdk/fastmcp-cli-tasks" - ] - }, - { - "group": "fastmcp.client", - "pages": [ - "python-sdk/fastmcp-client-__init__", { - "group": "auth", + "group": "Handlers", + "icon": "hand", "pages": [ - "python-sdk/fastmcp-client-auth-__init__", - "python-sdk/fastmcp-client-auth-bearer", - "python-sdk/fastmcp-client-auth-oauth" + "clients/notifications", + "clients/sampling", + "clients/elicitation", + "clients/tasks", + "clients/progress", + "clients/logging", + "clients/roots" ] }, - "python-sdk/fastmcp-client-client", - "python-sdk/fastmcp-client-elicitation", - "python-sdk/fastmcp-client-logging", - "python-sdk/fastmcp-client-messages", { - "group": "mixins", + "group": "Authentication", + "icon": "key", "pages": [ - "python-sdk/fastmcp-client-mixins-__init__", - "python-sdk/fastmcp-client-mixins-prompts", - "python-sdk/fastmcp-client-mixins-resources", - "python-sdk/fastmcp-client-mixins-task_management", - "python-sdk/fastmcp-client-mixins-tools" - ] - }, - "python-sdk/fastmcp-client-oauth_callback", - "python-sdk/fastmcp-client-progress", - "python-sdk/fastmcp-client-roots", - { - "group": "sampling", - "pages": [ - "python-sdk/fastmcp-client-sampling-__init__", - { - "group": "handlers", - "pages": [ - "python-sdk/fastmcp-client-sampling-handlers-__init__", - "python-sdk/fastmcp-client-sampling-handlers-anthropic", - "python-sdk/fastmcp-client-sampling-handlers-openai" - ] - } - ] - }, - "python-sdk/fastmcp-client-tasks", - "python-sdk/fastmcp-client-telemetry", - { - "group": "transports", - "pages": [ - "python-sdk/fastmcp-client-transports-__init__", - "python-sdk/fastmcp-client-transports-base", - "python-sdk/fastmcp-client-transports-config", - "python-sdk/fastmcp-client-transports-http", - "python-sdk/fastmcp-client-transports-inference", - "python-sdk/fastmcp-client-transports-memory", - "python-sdk/fastmcp-client-transports-sse", - "python-sdk/fastmcp-client-transports-stdio" + "clients/auth/oauth", + "clients/auth/bearer" ] } ] }, { - "group": "fastmcp.prompts", + "group": "Deployment", "pages": [ - "python-sdk/fastmcp-prompts-__init__", - "python-sdk/fastmcp-prompts-function_prompt", - "python-sdk/fastmcp-prompts-prompt" + "deployment/running-server", + "deployment/http", + "deployment/fastmcp-cloud", + "deployment/server-configuration", + "patterns/cli", + "patterns/testing" ] }, { - "group": "fastmcp.resources", + "group": "Integrations", "pages": [ - "python-sdk/fastmcp-resources-__init__", - "python-sdk/fastmcp-resources-function_resource", - "python-sdk/fastmcp-resources-resource", - "python-sdk/fastmcp-resources-template", - "python-sdk/fastmcp-resources-types" + { + "collapsed": true, + "group": "Auth", + "icon": "key", + "pages": [ + "integrations/auth0", + "integrations/authkit", + "integrations/aws-cognito", + "integrations/azure", + "integrations/descope", + "integrations/discord", + "integrations/github", + "integrations/google", + "integrations/oci", + "integrations/scalekit", + "integrations/supabase", + "integrations/workos", + "integrations/eunomia-authorization", + "integrations/permit" + ] + }, + { + "group": "Web Frameworks", + "icon": "code", + "pages": [ + "integrations/fastapi", + "integrations/openapi" + ] + }, + { + "collapsed": true, + "group": "AI Assistants", + "icon": "robot", + "pages": [ + "integrations/chatgpt", + "integrations/claude-code", + "integrations/claude-desktop", + "integrations/cursor", + "integrations/gemini-cli", + "integrations/mcp-json-configuration" + ] + }, + { + "collapsed": true, + "group": "AI SDKs", + "icon": "microchip", + "pages": [ + "integrations/anthropic", + "integrations/gemini", + "integrations/openai" + ] + } ] }, { - "group": "fastmcp.server", + "group": "Development", "pages": [ - "python-sdk/fastmcp-server-__init__", + "development/contributing", + "development/tests", + "development/releases", + "development/upgrade-guide", + "changelog", + "patterns/contrib" + ] + } + ] + }, + { + "anchors": [ + { + "anchor": "Python SDK", + "icon": "python", + "pages": [ + "python-sdk/fastmcp-decorators", + "python-sdk/fastmcp-dependencies", + "python-sdk/fastmcp-exceptions", + "python-sdk/fastmcp-mcp_config", + "python-sdk/fastmcp-settings", + "python-sdk/fastmcp-telemetry", { - "group": "auth", + "group": "fastmcp.cli", "pages": [ - "python-sdk/fastmcp-server-auth-__init__", - "python-sdk/fastmcp-server-auth-auth", - "python-sdk/fastmcp-server-auth-authorization", - "python-sdk/fastmcp-server-auth-jwt_issuer", - "python-sdk/fastmcp-server-auth-middleware", + "python-sdk/fastmcp-cli-__init__", + "python-sdk/fastmcp-cli-cli", { - "group": "oauth_proxy", + "group": "install", "pages": [ - "python-sdk/fastmcp-server-auth-oauth_proxy-__init__", - "python-sdk/fastmcp-server-auth-oauth_proxy-consent", - "python-sdk/fastmcp-server-auth-oauth_proxy-models", - "python-sdk/fastmcp-server-auth-oauth_proxy-proxy", - "python-sdk/fastmcp-server-auth-oauth_proxy-ui" + "python-sdk/fastmcp-cli-install-__init__", + "python-sdk/fastmcp-cli-install-claude_code", + "python-sdk/fastmcp-cli-install-claude_desktop", + "python-sdk/fastmcp-cli-install-cursor", + "python-sdk/fastmcp-cli-install-gemini_cli", + "python-sdk/fastmcp-cli-install-mcp_json", + "python-sdk/fastmcp-cli-install-shared" ] }, - "python-sdk/fastmcp-server-auth-oidc_proxy", + "python-sdk/fastmcp-cli-run", + "python-sdk/fastmcp-cli-tasks" + ] + }, + { + "group": "fastmcp.client", + "pages": [ + "python-sdk/fastmcp-client-__init__", { - "group": "providers", + "group": "auth", "pages": [ - "python-sdk/fastmcp-server-auth-providers-__init__", - "python-sdk/fastmcp-server-auth-providers-auth0", - "python-sdk/fastmcp-server-auth-providers-aws", - "python-sdk/fastmcp-server-auth-providers-azure", - "python-sdk/fastmcp-server-auth-providers-debug", - "python-sdk/fastmcp-server-auth-providers-descope", - "python-sdk/fastmcp-server-auth-providers-discord", - "python-sdk/fastmcp-server-auth-providers-github", - "python-sdk/fastmcp-server-auth-providers-google", - "python-sdk/fastmcp-server-auth-providers-in_memory", - "python-sdk/fastmcp-server-auth-providers-introspection", - "python-sdk/fastmcp-server-auth-providers-jwt", - "python-sdk/fastmcp-server-auth-providers-oci", - "python-sdk/fastmcp-server-auth-providers-scalekit", - "python-sdk/fastmcp-server-auth-providers-supabase", - "python-sdk/fastmcp-server-auth-providers-workos" + "python-sdk/fastmcp-client-auth-__init__", + "python-sdk/fastmcp-client-auth-bearer", + "python-sdk/fastmcp-client-auth-oauth" ] }, - "python-sdk/fastmcp-server-auth-redirect_validation" - ] - }, - "python-sdk/fastmcp-server-context", - "python-sdk/fastmcp-server-dependencies", - "python-sdk/fastmcp-server-elicitation", - "python-sdk/fastmcp-server-event_store", - "python-sdk/fastmcp-server-http", - "python-sdk/fastmcp-server-lifespan", - "python-sdk/fastmcp-server-low_level", - { - "group": "middleware", - "pages": [ - "python-sdk/fastmcp-server-middleware-__init__", - "python-sdk/fastmcp-server-middleware-authorization", - "python-sdk/fastmcp-server-middleware-caching", - "python-sdk/fastmcp-server-middleware-error_handling", - "python-sdk/fastmcp-server-middleware-logging", - "python-sdk/fastmcp-server-middleware-middleware", - "python-sdk/fastmcp-server-middleware-ping", - "python-sdk/fastmcp-server-middleware-rate_limiting", - "python-sdk/fastmcp-server-middleware-timing", - "python-sdk/fastmcp-server-middleware-tool_injection" - ] - }, - { - "group": "mixins", - "pages": [ - "python-sdk/fastmcp-server-mixins-__init__", - "python-sdk/fastmcp-server-mixins-lifespan", - "python-sdk/fastmcp-server-mixins-mcp_operations", - "python-sdk/fastmcp-server-mixins-transport" - ] - }, - { - "group": "openapi", - "pages": [ - "python-sdk/fastmcp-server-openapi-__init__", - "python-sdk/fastmcp-server-openapi-components", - "python-sdk/fastmcp-server-openapi-routing", - "python-sdk/fastmcp-server-openapi-server" - ] - }, - { - "group": "providers", - "pages": [ - "python-sdk/fastmcp-server-providers-__init__", - "python-sdk/fastmcp-server-providers-aggregate", - "python-sdk/fastmcp-server-providers-base", - "python-sdk/fastmcp-server-providers-fastmcp_provider", - "python-sdk/fastmcp-server-providers-filesystem", - "python-sdk/fastmcp-server-providers-filesystem_discovery", + "python-sdk/fastmcp-client-client", + "python-sdk/fastmcp-client-elicitation", + "python-sdk/fastmcp-client-logging", + "python-sdk/fastmcp-client-messages", + "python-sdk/fastmcp-client-oauth_callback", + "python-sdk/fastmcp-client-progress", + "python-sdk/fastmcp-client-prompts", + "python-sdk/fastmcp-client-resources", + "python-sdk/fastmcp-client-roots", { - "group": "local_provider", + "group": "sampling", "pages": [ - "python-sdk/fastmcp-server-providers-local_provider-__init__", + "python-sdk/fastmcp-client-sampling-__init__", { - "group": "decorators", + "group": "handlers", "pages": [ - "python-sdk/fastmcp-server-providers-local_provider-decorators-__init__", - "python-sdk/fastmcp-server-providers-local_provider-decorators-prompts", - "python-sdk/fastmcp-server-providers-local_provider-decorators-resources", - "python-sdk/fastmcp-server-providers-local_provider-decorators-tools" + "python-sdk/fastmcp-client-sampling-handlers-__init__", + "python-sdk/fastmcp-client-sampling-handlers-anthropic", + "python-sdk/fastmcp-client-sampling-handlers-openai" + ] + } + ] + }, + "python-sdk/fastmcp-client-task_management", + "python-sdk/fastmcp-client-tasks", + "python-sdk/fastmcp-client-telemetry", + "python-sdk/fastmcp-client-tools_client", + { + "group": "transports", + "pages": [ + "python-sdk/fastmcp-client-transports-__init__", + "python-sdk/fastmcp-client-transports-base", + "python-sdk/fastmcp-client-transports-config", + "python-sdk/fastmcp-client-transports-http", + "python-sdk/fastmcp-client-transports-inference", + "python-sdk/fastmcp-client-transports-memory", + "python-sdk/fastmcp-client-transports-sse", + "python-sdk/fastmcp-client-transports-stdio" + ] + } + ] + }, + { + "group": "fastmcp.prompts", + "pages": [ + "python-sdk/fastmcp-prompts-__init__", + "python-sdk/fastmcp-prompts-function_prompt", + "python-sdk/fastmcp-prompts-prompt" + ] + }, + { + "group": "fastmcp.resources", + "pages": [ + "python-sdk/fastmcp-resources-__init__", + "python-sdk/fastmcp-resources-function_resource", + "python-sdk/fastmcp-resources-resource", + "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", + "python-sdk/fastmcp-server-auth-authorization", + "python-sdk/fastmcp-server-auth-jwt_issuer", + "python-sdk/fastmcp-server-auth-middleware", + { + "group": "oauth_proxy", + "pages": [ + "python-sdk/fastmcp-server-auth-oauth_proxy-__init__", + "python-sdk/fastmcp-server-auth-oauth_proxy-consent", + "python-sdk/fastmcp-server-auth-oauth_proxy-models", + "python-sdk/fastmcp-server-auth-oauth_proxy-proxy", + "python-sdk/fastmcp-server-auth-oauth_proxy-ui" ] }, - "python-sdk/fastmcp-server-providers-local_provider-local_provider" + "python-sdk/fastmcp-server-auth-oidc_proxy", + { + "group": "providers", + "pages": [ + "python-sdk/fastmcp-server-auth-providers-__init__", + "python-sdk/fastmcp-server-auth-providers-auth0", + "python-sdk/fastmcp-server-auth-providers-aws", + "python-sdk/fastmcp-server-auth-providers-azure", + "python-sdk/fastmcp-server-auth-providers-debug", + "python-sdk/fastmcp-server-auth-providers-descope", + "python-sdk/fastmcp-server-auth-providers-discord", + "python-sdk/fastmcp-server-auth-providers-github", + "python-sdk/fastmcp-server-auth-providers-google", + "python-sdk/fastmcp-server-auth-providers-in_memory", + "python-sdk/fastmcp-server-auth-providers-introspection", + "python-sdk/fastmcp-server-auth-providers-jwt", + "python-sdk/fastmcp-server-auth-providers-oci", + "python-sdk/fastmcp-server-auth-providers-scalekit", + "python-sdk/fastmcp-server-auth-providers-supabase", + "python-sdk/fastmcp-server-auth-providers-workos" + ] + }, + "python-sdk/fastmcp-server-auth-redirect_validation" + ] + }, + "python-sdk/fastmcp-server-context", + "python-sdk/fastmcp-server-dependencies", + "python-sdk/fastmcp-server-elicitation", + "python-sdk/fastmcp-server-event_store", + "python-sdk/fastmcp-server-http", + "python-sdk/fastmcp-server-lifespan", + "python-sdk/fastmcp-server-low_level", + { + "group": "middleware", + "pages": [ + "python-sdk/fastmcp-server-middleware-__init__", + "python-sdk/fastmcp-server-middleware-authorization", + "python-sdk/fastmcp-server-middleware-caching", + "python-sdk/fastmcp-server-middleware-error_handling", + "python-sdk/fastmcp-server-middleware-logging", + "python-sdk/fastmcp-server-middleware-middleware", + "python-sdk/fastmcp-server-middleware-ping", + "python-sdk/fastmcp-server-middleware-rate_limiting", + "python-sdk/fastmcp-server-middleware-timing", + "python-sdk/fastmcp-server-middleware-tool_injection" ] }, { "group": "openapi", "pages": [ - "python-sdk/fastmcp-server-providers-openapi-__init__", - "python-sdk/fastmcp-server-providers-openapi-components", - "python-sdk/fastmcp-server-providers-openapi-provider", - "python-sdk/fastmcp-server-providers-openapi-routing" + "python-sdk/fastmcp-server-openapi-__init__", + "python-sdk/fastmcp-server-openapi-components", + "python-sdk/fastmcp-server-openapi-routing", + "python-sdk/fastmcp-server-openapi-server" ] }, - "python-sdk/fastmcp-server-providers-proxy", { - "group": "skills", + "group": "providers", "pages": [ - "python-sdk/fastmcp-server-providers-skills-__init__", - "python-sdk/fastmcp-server-providers-skills-claude_provider", - "python-sdk/fastmcp-server-providers-skills-directory_provider", - "python-sdk/fastmcp-server-providers-skills-skill_provider", - "python-sdk/fastmcp-server-providers-skills-vendor_providers" - ] - }, - "python-sdk/fastmcp-server-providers-wrapped_provider" - ] - }, - "python-sdk/fastmcp-server-proxy", - { - "group": "sampling", - "pages": [ - "python-sdk/fastmcp-server-sampling-__init__", - "python-sdk/fastmcp-server-sampling-run", - "python-sdk/fastmcp-server-sampling-sampling_tool" - ] - }, - "python-sdk/fastmcp-server-server", - { - "group": "tasks", - "pages": [ - "python-sdk/fastmcp-server-tasks-__init__", - "python-sdk/fastmcp-server-tasks-capabilities", - "python-sdk/fastmcp-server-tasks-config", - "python-sdk/fastmcp-server-tasks-handlers", - "python-sdk/fastmcp-server-tasks-keys", - "python-sdk/fastmcp-server-tasks-requests", - "python-sdk/fastmcp-server-tasks-routing", - "python-sdk/fastmcp-server-tasks-subscriptions" - ] - }, - "python-sdk/fastmcp-server-telemetry", - { - "group": "transforms", - "pages": [ - "python-sdk/fastmcp-server-transforms-__init__", - "python-sdk/fastmcp-server-transforms-namespace", - "python-sdk/fastmcp-server-transforms-prompts_as_tools", - "python-sdk/fastmcp-server-transforms-resources_as_tools", - "python-sdk/fastmcp-server-transforms-tool_transform", - "python-sdk/fastmcp-server-transforms-version_filter", - "python-sdk/fastmcp-server-transforms-visibility" - ] - } - ] - }, - { - "group": "fastmcp.tools", - "pages": [ - "python-sdk/fastmcp-tools-__init__", - "python-sdk/fastmcp-tools-function_parsing", - "python-sdk/fastmcp-tools-function_tool", - "python-sdk/fastmcp-tools-tool", - "python-sdk/fastmcp-tools-tool_transform" - ] - }, - { - "group": "fastmcp.utilities", - "pages": [ - "python-sdk/fastmcp-utilities-__init__", - "python-sdk/fastmcp-utilities-async_utils", - "python-sdk/fastmcp-utilities-auth", - "python-sdk/fastmcp-utilities-cli", - "python-sdk/fastmcp-utilities-components", - "python-sdk/fastmcp-utilities-exceptions", - "python-sdk/fastmcp-utilities-http", - "python-sdk/fastmcp-utilities-inspect", - "python-sdk/fastmcp-utilities-json_schema", - "python-sdk/fastmcp-utilities-json_schema_type", - "python-sdk/fastmcp-utilities-lifespan", - "python-sdk/fastmcp-utilities-logging", - { - "group": "mcp_server_config", - "pages": [ - "python-sdk/fastmcp-utilities-mcp_server_config-__init__", - { - "group": "v1", - "pages": [ - "python-sdk/fastmcp-utilities-mcp_server_config-v1-__init__", + "python-sdk/fastmcp-server-providers-__init__", + "python-sdk/fastmcp-server-providers-aggregate", + "python-sdk/fastmcp-server-providers-base", + "python-sdk/fastmcp-server-providers-fastmcp_provider", + "python-sdk/fastmcp-server-providers-filesystem", + "python-sdk/fastmcp-server-providers-filesystem_discovery", { - "group": "environments", + "group": "local_provider", "pages": [ - "python-sdk/fastmcp-utilities-mcp_server_config-v1-environments-__init__", - "python-sdk/fastmcp-utilities-mcp_server_config-v1-environments-base", - "python-sdk/fastmcp-utilities-mcp_server_config-v1-environments-uv" + "python-sdk/fastmcp-server-providers-local_provider-__init__", + { + "group": "decorators", + "pages": [ + "python-sdk/fastmcp-server-providers-local_provider-decorators-__init__", + "python-sdk/fastmcp-server-providers-local_provider-decorators-prompts", + "python-sdk/fastmcp-server-providers-local_provider-decorators-resources", + "python-sdk/fastmcp-server-providers-local_provider-decorators-tools" + ] + }, + "python-sdk/fastmcp-server-providers-local_provider-local_provider" ] }, - "python-sdk/fastmcp-utilities-mcp_server_config-v1-mcp_server_config", { - "group": "sources", + "group": "openapi", "pages": [ - "python-sdk/fastmcp-utilities-mcp_server_config-v1-sources-__init__", - "python-sdk/fastmcp-utilities-mcp_server_config-v1-sources-base", - "python-sdk/fastmcp-utilities-mcp_server_config-v1-sources-filesystem" + "python-sdk/fastmcp-server-providers-openapi-__init__", + "python-sdk/fastmcp-server-providers-openapi-components", + "python-sdk/fastmcp-server-providers-openapi-provider", + "python-sdk/fastmcp-server-providers-openapi-routing" ] - } + }, + "python-sdk/fastmcp-server-providers-proxy", + "python-sdk/fastmcp-server-providers-wrapped_provider" + ] + }, + "python-sdk/fastmcp-server-proxy", + { + "group": "sampling", + "pages": [ + "python-sdk/fastmcp-server-sampling-__init__", + "python-sdk/fastmcp-server-sampling-run", + "python-sdk/fastmcp-server-sampling-sampling_tool" + ] + }, + "python-sdk/fastmcp-server-server", + { + "group": "tasks", + "pages": [ + "python-sdk/fastmcp-server-tasks-__init__", + "python-sdk/fastmcp-server-tasks-capabilities", + "python-sdk/fastmcp-server-tasks-config", + "python-sdk/fastmcp-server-tasks-handlers", + "python-sdk/fastmcp-server-tasks-keys", + "python-sdk/fastmcp-server-tasks-requests", + "python-sdk/fastmcp-server-tasks-routing", + "python-sdk/fastmcp-server-tasks-subscriptions" + ] + }, + "python-sdk/fastmcp-server-telemetry", + { + "group": "transforms", + "pages": [ + "python-sdk/fastmcp-server-transforms-__init__", + "python-sdk/fastmcp-server-transforms-enabled", + "python-sdk/fastmcp-server-transforms-namespace", + "python-sdk/fastmcp-server-transforms-tool_transform", + "python-sdk/fastmcp-server-transforms-version_filter" ] } ] }, { - "group": "openapi", + "group": "fastmcp.tools", "pages": [ - "python-sdk/fastmcp-utilities-openapi-__init__", - "python-sdk/fastmcp-utilities-openapi-director", - "python-sdk/fastmcp-utilities-openapi-formatters", - "python-sdk/fastmcp-utilities-openapi-json_schema_converter", - "python-sdk/fastmcp-utilities-openapi-models", - "python-sdk/fastmcp-utilities-openapi-parser", - "python-sdk/fastmcp-utilities-openapi-schemas" + "python-sdk/fastmcp-tools-__init__", + "python-sdk/fastmcp-tools-function_parsing", + "python-sdk/fastmcp-tools-function_tool", + "python-sdk/fastmcp-tools-tool", + "python-sdk/fastmcp-tools-tool_transform" ] }, - "python-sdk/fastmcp-utilities-pagination", - "python-sdk/fastmcp-utilities-skills", - "python-sdk/fastmcp-utilities-tests", - "python-sdk/fastmcp-utilities-timeout", - "python-sdk/fastmcp-utilities-types", - "python-sdk/fastmcp-utilities-ui", - "python-sdk/fastmcp-utilities-version_check", - "python-sdk/fastmcp-utilities-versions" + { + "group": "fastmcp.utilities", + "pages": [ + "python-sdk/fastmcp-utilities-__init__", + "python-sdk/fastmcp-utilities-async_utils", + "python-sdk/fastmcp-utilities-auth", + "python-sdk/fastmcp-utilities-cli", + "python-sdk/fastmcp-utilities-components", + "python-sdk/fastmcp-utilities-exceptions", + "python-sdk/fastmcp-utilities-http", + "python-sdk/fastmcp-utilities-inspect", + "python-sdk/fastmcp-utilities-json_schema", + "python-sdk/fastmcp-utilities-json_schema_type", + "python-sdk/fastmcp-utilities-lifespan", + "python-sdk/fastmcp-utilities-logging", + { + "group": "mcp_server_config", + "pages": [ + "python-sdk/fastmcp-utilities-mcp_server_config-__init__", + { + "group": "v1", + "pages": [ + "python-sdk/fastmcp-utilities-mcp_server_config-v1-__init__", + { + "group": "environments", + "pages": [ + "python-sdk/fastmcp-utilities-mcp_server_config-v1-environments-__init__", + "python-sdk/fastmcp-utilities-mcp_server_config-v1-environments-base", + "python-sdk/fastmcp-utilities-mcp_server_config-v1-environments-uv" + ] + }, + "python-sdk/fastmcp-utilities-mcp_server_config-v1-mcp_server_config", + { + "group": "sources", + "pages": [ + "python-sdk/fastmcp-utilities-mcp_server_config-v1-sources-__init__", + "python-sdk/fastmcp-utilities-mcp_server_config-v1-sources-base", + "python-sdk/fastmcp-utilities-mcp_server_config-v1-sources-filesystem" + ] + } + ] + } + ] + }, + { + "group": "openapi", + "pages": [ + "python-sdk/fastmcp-utilities-openapi-__init__", + "python-sdk/fastmcp-utilities-openapi-director", + "python-sdk/fastmcp-utilities-openapi-formatters", + "python-sdk/fastmcp-utilities-openapi-json_schema_converter", + "python-sdk/fastmcp-utilities-openapi-models", + "python-sdk/fastmcp-utilities-openapi-parser", + "python-sdk/fastmcp-utilities-openapi-schemas" + ] + }, + "python-sdk/fastmcp-utilities-pagination", + "python-sdk/fastmcp-utilities-tests", + "python-sdk/fastmcp-utilities-timeout", + "python-sdk/fastmcp-utilities-types", + "python-sdk/fastmcp-utilities-ui", + "python-sdk/fastmcp-utilities-version_check", + "python-sdk/fastmcp-utilities-versions" + ] + } + ] + } + ], + "dropdown": "SDK Reference", + "icon": "code" + } + ] + }, + { + "version": "v2", + "dropdowns": [ + { + "dropdown": "Documentation", + "icon": "book", + "groups": [ + { + "group": "Get Started", + "pages": [ + "v2/getting-started/welcome", + "v2/getting-started/installation", + "v2/getting-started/quickstart", + "v2/updates" + ] + }, + { + "group": "Servers", + "pages": [ + "v2/servers/server", + { + "group": "Core Components", + "icon": "toolbox", + "pages": [ + "v2/servers/tools", + "v2/servers/resources", + "v2/servers/prompts" + ] + }, + { + "group": "Advanced Features", + "icon": "stars", + "pages": [ + "v2/servers/composition", + "v2/servers/context", + "v2/servers/elicitation", + "v2/servers/icons", + "v2/servers/logging", + "v2/servers/middleware", + "v2/servers/progress", + "v2/servers/proxy", + "v2/servers/sampling", + "v2/servers/storage-backends", + "v2/servers/tasks" + ] + }, + { + "group": "Authentication", + "icon": "shield-check", + "pages": [ + "v2/servers/auth/authentication", + "v2/servers/auth/token-verification", + "v2/servers/auth/remote-oauth", + "v2/servers/auth/oauth-proxy", + "v2/servers/auth/oidc-proxy", + "v2/servers/auth/full-oauth-server" + ] + }, + { + "group": "Deployment", + "icon": "rocket", + "pages": [ + "v2/deployment/running-server", + "v2/deployment/http", + "v2/deployment/fastmcp-cloud", + "v2/deployment/server-configuration" + ] + } + ] + }, + { + "group": "Clients", + "pages": [ + { + "group": "Essentials", + "icon": "cube", + "pages": [ + "v2/clients/client", + "v2/clients/transports" + ] + }, + { + "group": "Core Operations", + "icon": "handshake", + "pages": [ + "v2/clients/tools", + "v2/clients/resources", + "v2/clients/prompts" + ] + }, + { + "group": "Advanced Features", + "icon": "stars", + "pages": [ + "v2/clients/elicitation", + "v2/clients/logging", + "v2/clients/progress", + "v2/clients/sampling", + "v2/clients/tasks", + "v2/clients/messages", + "v2/clients/roots" + ] + }, + { + "group": "Authentication", + "icon": "user-shield", + "pages": [ + "v2/clients/auth/oauth", + "v2/clients/auth/bearer" + ] + } + ] + }, + { + "group": "Integrations", + "pages": [ + { + "group": "Authentication", + "icon": "key", + "pages": [ + "v2/integrations/auth0", + "v2/integrations/authkit", + "v2/integrations/aws-cognito", + "v2/integrations/azure", + "v2/integrations/descope", + "v2/integrations/discord", + "v2/integrations/github", + "v2/integrations/google", + "v2/integrations/oci", + "v2/integrations/scalekit", + "v2/integrations/supabase", + "v2/integrations/workos" + ] + }, + { + "group": "Authorization", + "icon": "shield-check", + "pages": [ + "v2/integrations/eunomia-authorization", + "v2/integrations/permit" + ] + }, + { + "group": "AI Assistants", + "icon": "robot", + "pages": [ + "v2/integrations/chatgpt", + "v2/integrations/claude-code", + "v2/integrations/claude-desktop", + "v2/integrations/cursor", + "v2/integrations/gemini-cli", + "v2/integrations/mcp-json-configuration" + ] + }, + { + "group": "AI SDKs", + "icon": "code", + "pages": [ + "v2/integrations/anthropic", + "v2/integrations/gemini", + "v2/integrations/openai" + ] + }, + { + "group": "API Integration", + "icon": "globe", + "pages": [ + "v2/integrations/fastapi", + "v2/integrations/openapi" + ] + } + ] + }, + { + "group": "Patterns", + "pages": [ + "v2/patterns/tool-transformation", + "v2/patterns/decorating-methods", + "v2/patterns/cli", + "v2/patterns/contrib", + "v2/patterns/testing" + ] + }, + { + "group": "Development", + "pages": [ + "v2/development/contributing", + "v2/development/tests", + "v2/development/releases", + "v2/development/upgrade-guide", + "v2/changelog" ] } ] } - ], - "dropdown": "SDK Reference", - "icon": "code" + ] } ] }, @@ -699,4 +863,4 @@ "appearance": "light", "background": "/assets/brand/thumbnail-background.png" } -} +} \ No newline at end of file diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index 862c95de1..78ef9db43 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -47,7 +47,7 @@ You should see output like the following: ```bash $ fastmcp version -FastMCP version: 2.11.3 +FastMCP version: 3.0.0 MCP version: 1.12.4 Python version: 3.12.2 Platform: macOS-15.3.1-arm64-arm-64bit @@ -69,7 +69,7 @@ Alternatively, wait for the stable v5 release. See [this issue](https://github.c ## Upgrading from the Official MCP SDK -Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient. +Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 3.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient. ```python {5} @@ -83,7 +83,7 @@ mcp = FastMCP("My MCP Server") ``` -Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities. +Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 3.x. Please refer to this documentation for details on new capabilities. ## Versioning Policy @@ -92,8 +92,8 @@ FastMCP follows semantic versioning with pragmatic adaptations for the rapidly e For production use, always pin to exact versions: ``` -fastmcp==2.11.0 # Good -fastmcp>=2.11.0 # Bad - will install breaking changes +fastmcp==3.0.0 # Good +fastmcp>=3.0.0 # Bad - may install breaking changes ``` See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy. diff --git a/docs/getting-started/welcome.mdx b/docs/getting-started/welcome.mdx index f424497ee..a6df5dc31 100644 --- a/docs/getting-started/welcome.mdx +++ b/docs/getting-started/welcome.mdx @@ -1,18 +1,18 @@ --- -title: "Welcome to FastMCP 2.0!" +title: "Welcome to FastMCP 3.0!" sidebarTitle: "Welcome!" description: The fast, Pythonic way to build MCP servers and clients. icon: hand-wave --- -'F' logo on a watercolor background -'F' logo on a watercolor background +**This documentation is for FastMCP 3.0**, which is currently in beta. For the 2.x release, see the [FastMCP 2.0 documentation](/v2/getting-started/welcome). + -## Beyond Basic MCP +## Production-Ready MCP -FastMCP pioneered Python MCP development, and FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024. +FastMCP pioneered Python MCP development—FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024. Today, roughly 70% of MCP servers run on some version of FastMCP. -**This is FastMCP 2.0,** the actively maintained version that extends far beyond basic protocol implementation. While the SDK provides core functionality, FastMCP 2.0 delivers everything needed for production: advanced MCP patterns (server composition, proxying, OpenAPI/FastAPI generation, tool transformation), enterprise auth (Google, GitHub, Azure, Auth0, WorkOS, and more), deployment tools, testing frameworks, and comprehensive client libraries. +**FastMCP 3.0 is built for enterprise MCP applications.** When you need to compose multiple servers, proxy remote APIs, add enterprise auth, or deploy multi-tenant systems, FastMCP provides the right abstractions: + +- **[Providers](/servers/providers/overview)** let you source components from anywhere—decorated functions, filesystem discovery, remote servers, OpenAPI specs, or your own custom sources. +- **[Transforms](/servers/providers/transforms)** control what clients see—namespace for composition, filter by version or user permissions, convert resources to tools for compatibility. +- **[Servers](/servers/server)** expose everything over stdio, HTTP, or WebSocket with session management and protocol handling built in. +- **[Middleware](/servers/middleware)** handles cross-cutting concerns—authentication, authorization, logging, rate limiting, custom business logic. + +Mount a weather API and a calendar server together with automatic namespacing. Proxy your company's internal MCP servers through a single authenticated gateway. Build a SaaS product where each tenant gets their own isolated component set. These aren't edge cases—they're what production MCP looks like. Ready to build? Start with our [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart). @@ -59,7 +69,7 @@ FastMCP provides a high-level, Pythonic interface for building, managing, and in ## Why FastMCP? -FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need — FastMCP handles the rest. +FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need—FastMCP handles the rest. 🚀 **Fast**: High-level interface means less code and faster development @@ -67,12 +77,14 @@ FastMCP handles all the complex protocol details so you can focus on building. I 🐍 **Pythonic**: Feels natural to Python developers -🔍 **Complete**: Everything for production — enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more +🔍 **Complete**: Everything for production—enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more + +🔧 **Composable**: The v3 architecture scales from a single tool to complex multi-server deployments FastMCP provides the shortest path from idea to production. Deploy locally, to the cloud with [FastMCP Cloud](https://fastmcp.cloud) (free for personal servers), or to your own infrastructure. -**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 2.13.1`) to indicate when they were introduced. Note that this may include features that are not yet released. +**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 3.0.0`) to indicate when they were introduced. Note that this may include features that are not yet released. ## LLM-Friendly Docs diff --git a/docs/integrations/auth0.mdx b/docs/integrations/auth0.mdx index cb8b22cfb..65f9d3873 100644 --- a/docs/integrations/auth0.mdx +++ b/docs/integrations/auth0.mdx @@ -3,7 +3,6 @@ title: Auth0 OAuth 🤝 FastMCP sidebarTitle: Auth0 description: Secure your FastMCP server with Auth0 OAuth icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/authkit.mdx b/docs/integrations/authkit.mdx index 1242df2bb..d16a4db3b 100644 --- a/docs/integrations/authkit.mdx +++ b/docs/integrations/authkit.mdx @@ -3,7 +3,6 @@ title: AuthKit 🤝 FastMCP sidebarTitle: AuthKit description: Secure your FastMCP server with AuthKit by WorkOS icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/aws-cognito.mdx b/docs/integrations/aws-cognito.mdx index 4c906995a..b7df29222 100644 --- a/docs/integrations/aws-cognito.mdx +++ b/docs/integrations/aws-cognito.mdx @@ -3,7 +3,6 @@ title: AWS Cognito OAuth 🤝 FastMCP sidebarTitle: AWS Cognito description: Secure your FastMCP server with AWS Cognito user pools icon: aws -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/azure.mdx b/docs/integrations/azure.mdx index 68d389e44..4f6a67341 100644 --- a/docs/integrations/azure.mdx +++ b/docs/integrations/azure.mdx @@ -3,7 +3,6 @@ title: Azure (Microsoft Entra ID) OAuth 🤝 FastMCP sidebarTitle: Azure (Entra ID) description: Secure your FastMCP server with Azure/Microsoft Entra OAuth icon: microsoft -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/chatgpt.mdx b/docs/integrations/chatgpt.mdx index 18028c69d..d674a21e2 100644 --- a/docs/integrations/chatgpt.mdx +++ b/docs/integrations/chatgpt.mdx @@ -3,7 +3,6 @@ title: ChatGPT 🤝 FastMCP sidebarTitle: ChatGPT description: Connect FastMCP servers to ChatGPT in Chat and Deep Research modes icon: message-smile -tag: NEW --- ChatGPT supports MCP servers through remote HTTP connections in two modes: **Chat mode** for interactive conversations and **Deep Research mode** for comprehensive information retrieval. diff --git a/docs/integrations/descope.mdx b/docs/integrations/descope.mdx index bd569aed6..dba9b3509 100644 --- a/docs/integrations/descope.mdx +++ b/docs/integrations/descope.mdx @@ -3,7 +3,6 @@ title: Descope 🤝 FastMCP sidebarTitle: Descope description: Secure your FastMCP server with Descope icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx"; diff --git a/docs/integrations/discord.mdx b/docs/integrations/discord.mdx index 6864339a8..5d6c643b7 100644 --- a/docs/integrations/discord.mdx +++ b/docs/integrations/discord.mdx @@ -3,7 +3,6 @@ title: Discord OAuth 🤝 FastMCP sidebarTitle: Discord description: Secure your FastMCP server with Discord OAuth icon: discord -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/gemini-cli.mdx b/docs/integrations/gemini-cli.mdx index af63a5d32..591ab4d5d 100644 --- a/docs/integrations/gemini-cli.mdx +++ b/docs/integrations/gemini-cli.mdx @@ -3,7 +3,6 @@ title: Gemini CLI 🤝 FastMCP sidebarTitle: Gemini CLI description: Install and use FastMCP servers in Gemini CLI icon: message-smile -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/github.mdx b/docs/integrations/github.mdx index a49862cb0..72e65d3ce 100644 --- a/docs/integrations/github.mdx +++ b/docs/integrations/github.mdx @@ -3,7 +3,6 @@ title: GitHub OAuth 🤝 FastMCP sidebarTitle: GitHub description: Secure your FastMCP server with GitHub OAuth icon: github -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/google.mdx b/docs/integrations/google.mdx index fcaf6dc12..17d49d12f 100644 --- a/docs/integrations/google.mdx +++ b/docs/integrations/google.mdx @@ -3,7 +3,6 @@ title: Google OAuth 🤝 FastMCP sidebarTitle: Google description: Secure your FastMCP server with Google OAuth icon: google -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/oci.mdx b/docs/integrations/oci.mdx index fec9cd38f..02fa36dae 100644 --- a/docs/integrations/oci.mdx +++ b/docs/integrations/oci.mdx @@ -3,7 +3,6 @@ title: OCI IAM OAuth 🤝 FastMCP sidebarTitle: Oracle description: Secure your FastMCP server with OCI IAM OAuth icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/scalekit.mdx b/docs/integrations/scalekit.mdx index 07d9b3b61..191b81ca2 100644 --- a/docs/integrations/scalekit.mdx +++ b/docs/integrations/scalekit.mdx @@ -3,7 +3,6 @@ title: Scalekit 🤝 FastMCP sidebarTitle: Scalekit description: Secure your FastMCP server with Scalekit icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/supabase.mdx b/docs/integrations/supabase.mdx index c4cb9afd3..68b23c89d 100644 --- a/docs/integrations/supabase.mdx +++ b/docs/integrations/supabase.mdx @@ -3,7 +3,6 @@ title: Supabase 🤝 FastMCP sidebarTitle: Supabase description: Secure your FastMCP server with Supabase Auth icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/integrations/workos.mdx b/docs/integrations/workos.mdx index 3e795515c..4f13a5512 100644 --- a/docs/integrations/workos.mdx +++ b/docs/integrations/workos.mdx @@ -3,7 +3,6 @@ title: WorkOS 🤝 FastMCP sidebarTitle: WorkOS description: Authenticate FastMCP servers with WorkOS Connect icon: shield-check -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/servers/auth/oidc-proxy.mdx b/docs/servers/auth/oidc-proxy.mdx index 3bd75db20..6941c7f79 100644 --- a/docs/servers/auth/oidc-proxy.mdx +++ b/docs/servers/auth/oidc-proxy.mdx @@ -3,7 +3,6 @@ title: OIDC Proxy sidebarTitle: OIDC Proxy description: Bridge OIDC providers to work seamlessly with MCP's authentication flow. icon: share -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx"; diff --git a/docs/servers/auth/remote-oauth.mdx b/docs/servers/auth/remote-oauth.mdx index c1438cfd3..5b894a7ea 100644 --- a/docs/servers/auth/remote-oauth.mdx +++ b/docs/servers/auth/remote-oauth.mdx @@ -3,7 +3,6 @@ title: Remote OAuth sidebarTitle: Remote OAuth description: Integrate your FastMCP server with external identity providers like Descope, WorkOS, Auth0, and corporate SSO systems. icon: camera-cctv -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" diff --git a/docs/servers/context.mdx b/docs/servers/context.mdx index e020db32e..3d6686cee 100644 --- a/docs/servers/context.mdx +++ b/docs/servers/context.mdx @@ -3,6 +3,7 @@ title: MCP Context sidebarTitle: Context description: Access MCP capabilities like logging, progress, and resources within your MCP objects. icon: rectangle-code +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/elicitation.mdx b/docs/servers/elicitation.mdx index 18e615d0a..600a3d2fe 100644 --- a/docs/servers/elicitation.mdx +++ b/docs/servers/elicitation.mdx @@ -3,7 +3,6 @@ title: User Elicitation sidebarTitle: Elicitation description: Request structured input from users during tool execution through the MCP context. icon: message-question -tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' @@ -12,26 +11,20 @@ import { VersionBadge } from '/snippets/version-badge.mdx' User elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, tools can interactively ask for missing parameters, clarification, or additional context as needed. - -Most of the examples in this document assume you have a FastMCP server instance named `mcp` and show how to use the `ctx.elicit` method to request user input from an `@mcp.tool`-decorated function. - - -## What is Elicitation? - -Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for: +Elicitation enables tools to pause execution and request specific information from users: - **Missing parameters**: Ask for required information not provided initially -- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios +- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios - **Progressive disclosure**: Collect complex information step-by-step - **Dynamic workflows**: Adapt tool behavior based on user responses For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?" -### Basic Usage +## Overview -Use the `ctx.elicit()` method within any tool function to request user input: +Use the `ctx.elicit()` method within any tool function to request user input. Specify the message to display and the type of response you expect. -```python {14-17} +```python from fastmcp import FastMCP, Context from dataclasses import dataclass @@ -49,7 +42,7 @@ async def collect_user_info(ctx: Context) -> str: message="Please provide your information", response_type=UserInfo ) - + if result.action == "accept": user = result.data return f"Hello {user.name}, you are {user.age} years old" @@ -59,72 +52,27 @@ async def collect_user_info(ctx: Context) -> str: return "Operation cancelled" ``` -## Method Signature - - - - - - The prompt message to display to the user - - - - The Python type defining the expected response structure (dataclass, primitive type, etc.) Note that elicitation responses are subject to a restricted subset of JSON Schema types. See [Supported Response Types](#supported-response-types) for more details. - - - - - - Result object containing the user's response - - - - How the user responded to the request - - - - The user's input data (only present when action is "accept") - - - - - - - -## Elicitation Actions - The elicitation result contains an `action` field indicating how the user responded: -- **`accept`**: User provided valid input - data is available in the `data` field -- **`decline`**: User chose not to provide the requested information and the data field is `None` -- **`cancel`**: User cancelled the entire operation and the data field is `None` +| Action | Description | +|--------|-------------| +| `accept` | User provided valid input—data is available in the `data` field | +| `decline` | User chose not to provide the requested information | +| `cancel` | User cancelled the entire operation | -```python {5, 7} -@mcp.tool -async def my_tool(ctx: Context) -> str: - result = await ctx.elicit("Choose an action") +FastMCP also provides typed result classes for pattern matching: - if result.action == "accept": - return "Accepted!" - elif result.action == "decline": - return "Declined!" - else: - return "Cancelled!" -``` - -FastMCP also provides typed result classes for pattern matching on the `action` field: - -```python {1-5, 12, 14, 16} +```python from fastmcp.server.elicitation import ( - AcceptedElicitation, - DeclinedElicitation, + AcceptedElicitation, + DeclinedElicitation, CancelledElicitation, ) @mcp.tool async def pattern_example(ctx: Context) -> str: result = await ctx.elicit("Enter your name:", response_type=str) - + match result: case AcceptedElicitation(data=name): return f"Hello {name}!" @@ -134,50 +82,74 @@ async def pattern_example(ctx: Context) -> str: return "Operation cancelled" ``` -## Response Types +### Multi-Turn Elicitation -The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. If the request is `accept`-ed, the client must send a response that matches the schema. +Tools can make multiple elicitation calls to gather information progressively: -The MCP spec only supports a limited subset of JSON Schema types for elicitation responses. Specifically, it only supports JSON **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean` and `enum` fields. +```python +@mcp.tool +async def plan_meeting(ctx: Context) -> str: + """Plan a meeting by gathering details step by step.""" + + title_result = await ctx.elicit("What's the meeting title?", response_type=str) + if title_result.action != "accept": + return "Meeting planning cancelled" + + duration_result = await ctx.elicit("Duration in minutes?", response_type=int) + if duration_result.action != "accept": + return "Meeting planning cancelled" + + priority_result = await ctx.elicit( + "Is this urgent?", + response_type=["yes", "no"] + ) + if priority_result.action != "accept": + return "Meeting planning cancelled" + + urgent = priority_result.data == "yes" + return f"Meeting '{title_result.data}' for {duration_result.data} minutes (Urgent: {urgent})" +``` + +### Client Requirements + +Elicitation requires the client to implement an elicitation handler. If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported. + +See [Client Elicitation](/clients/elicitation) for details on how clients handle these requests. + +## Schema and Response Types + +The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. The MCP spec only supports a limited subset of JSON Schema types for elicitation responses—specifically JSON **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean`, and `enum` fields. FastMCP makes it easy to request a broader range of types, including scalars (e.g. `str`) or no response at all, by automatically wrapping them in MCP-compatible object schemas. - ### Scalar Types -You can request simple scalar data types for basic input, such as a string, integer, or boolean. - -When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a corresponding schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned to your tool function as the `data` field of the `ElicitationResult` object. - -As a developer, this means you do not have to worry about creating or accessing a structured object when you only need a scalar value. +You can request simple scalar data types for basic input, such as a string, integer, or boolean. When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned directly in the `data` field. -```python {4} title="Request a string" +```python title="String" @mcp.tool async def get_user_name(ctx: Context) -> str: - """Get the user's name.""" result = await ctx.elicit("What's your name?", response_type=str) - + if result.action == "accept": return f"Hello, {result.data}!" return "No name provided" ``` -```python {4} title="Request an integer" +```python title="Integer" @mcp.tool async def pick_a_number(ctx: Context) -> str: - """Pick a number.""" result = await ctx.elicit("Pick a number!", response_type=int) - + if result.action == "accept": return f"You picked {result.data}" return "No number provided" ``` -```python {4} title="Request a boolean" +```python title="Boolean" @mcp.tool async def pick_a_boolean(ctx: Context) -> str: - """Pick a boolean.""" result = await ctx.elicit("True or false?", response_type=bool) - + if result.action == "accept": return f"You picked {result.data}" return "No boolean provided" @@ -186,12 +158,11 @@ async def pick_a_boolean(ctx: Context) -> str: ### No Response -Sometimes, the goal of an elicitation is to simply get a user to approve or reject an action. In this case, you can pass `None` as the response type to indicate that no response is expected. In order to comply with the MCP spec, the client will see a schema requesting an empty object in response. In this case, the `data` field of the `ElicitationResult` object will be `None` when the user accepts the elicitation. +Sometimes, the goal of an elicitation is to simply get a user to approve or reject an action. Pass `None` as the response type to indicate that no data is expected. The `data` field will be `None` when the user accepts. -```python {4} title="No response" +```python @mcp.tool async def approve_action(ctx: Context) -> str: - """Approve an action.""" result = await ctx.elicit("Approve this action?", response_type=None) if result.action == "accept": @@ -202,13 +173,12 @@ async def approve_action(ctx: Context) -> str: ### Constrained Options -Often you'll want to constrain the user's response to a specific set of values. You can do this by using a `Literal` type or a Python enum as the response type, or by passing a list of strings to the `response_type` parameter as a convenient shortcut. +Constrain the user's response to a specific set of values using a `Literal` type, Python enum, or a list of strings as a convenient shortcut. -```python {6} title="Using a list of strings" +```python title="List of strings" @mcp.tool async def set_priority(ctx: Context) -> str: - """Set task priority level.""" result = await ctx.elicit( "What priority level?", response_type=["low", "medium", "high"], @@ -217,12 +187,11 @@ async def set_priority(ctx: Context) -> str: if result.action == "accept": return f"Priority set to: {result.data}" ``` -```python {1, 8} title="Using a Literal type" +```python title="Literal type" from typing import Literal @mcp.tool async def set_priority(ctx: Context) -> str: - """Set task priority level.""" result = await ctx.elicit( "What priority level?", response_type=Literal["low", "medium", "high"] @@ -232,7 +201,7 @@ async def set_priority(ctx: Context) -> str: return f"Priority set to: {result.data}" return "No priority set" ``` -```python {1, 11} title="Using a Python enum" +```python title="Python enum" from enum import Enum class Priority(Enum): @@ -242,7 +211,6 @@ class Priority(Enum): @mcp.tool async def set_priority(ctx: Context) -> str: - """Set task priority level.""" result = await ctx.elicit("What priority level?", response_type=Priority) if result.action == "accept": @@ -251,28 +219,26 @@ async def set_priority(ctx: Context) -> str: ``` -#### Multi-Select +### Multi-Select Enable multi-select by wrapping your choices in an additional list level. This allows users to select multiple values from the available options. -```python {6-8} title="List of a list of strings" +```python title="List of strings" @mcp.tool async def select_tags(ctx: Context) -> str: - """Select multiple tags.""" result = await ctx.elicit( "Choose tags", response_type=[["bug", "feature", "documentation"]] # Note: list of a list ) if result.action == "accept": - tags = result.data # List of selected strings + tags = result.data return f"Selected tags: {', '.join(tags)}" ``` - -```python {1, 3-6, 11-14} title="list[Enum] type annotation" +```python title="list[Enum] type" from enum import Enum class Tag(Enum): @@ -284,7 +250,7 @@ class Tag(Enum): async def select_tags(ctx: Context) -> str: result = await ctx.elicit( "Choose tags", - response_type=list[Tag] # Type annotation for multi-select + response_type=list[Tag] ) if result.action == "accept": tags = [tag.value for tag in result.data] @@ -292,38 +258,15 @@ async def select_tags(ctx: Context) -> str: ``` -For titled multi-select, wrap a dict in a list (see [Titled Options](#titled-options) for dict syntax): - -```python {6-12} -@mcp.tool -async def select_priorities(ctx: Context) -> str: - """Select multiple priorities.""" - result = await ctx.elicit( - "Choose priorities", - response_type=[{ # Note: list containing a dict - "low": {"title": "Low Priority"}, - "medium": {"title": "Medium Priority"}, - "high": {"title": "High Priority"} - }] - ) - - if result.action == "accept": - priorities = result.data # List of selected strings - return f"Selected: {', '.join(priorities)}" -``` - -#### Titled Options +### Titled Options -For better UI display, you can provide human-readable titles for enum options. FastMCP generates SEP-1330 compliant schemas using the `oneOf` pattern with `const` and `title` fields. +For better UI display, provide human-readable titles for enum options. FastMCP generates SEP-1330 compliant schemas using the `oneOf` pattern with `const` and `title` fields. -Use a dict to specify titles for enum values: - -```python {6-10} +```python @mcp.tool async def set_priority(ctx: Context) -> str: - """Set task priority level.""" result = await ctx.elicit( "What priority level?", response_type={ @@ -339,13 +282,12 @@ async def set_priority(ctx: Context) -> str: For multi-select with titles, wrap the dict in a list: -```python {6-12} +```python @mcp.tool async def select_priorities(ctx: Context) -> str: - """Select multiple priorities.""" result = await ctx.elicit( "Choose priorities", - response_type=[{ # List containing a dict for multi-select + response_type=[{ "low": {"title": "Low Priority"}, "medium": {"title": "Medium Priority"}, "high": {"title": "High Priority"} @@ -353,15 +295,14 @@ async def select_priorities(ctx: Context) -> str: ) if result.action == "accept": - priorities = result.data # List of selected strings - return f"Selected: {', '.join(priorities)}" + return f"Selected: {', '.join(result.data)}" ``` ### Structured Responses -You can request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties. +Request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties. -```python {1, 16, 20} +```python from dataclasses import dataclass from typing import Literal @@ -374,12 +315,11 @@ class TaskDetails: @mcp.tool async def create_task(ctx: Context) -> str: - """Create a new task with user-provided details.""" result = await ctx.elicit( "Please provide task details", response_type=TaskDetails ) - + if result.action == "accept": task = result.data return f"Created task: {task.title} (Priority: {task.priority})" @@ -390,16 +330,7 @@ async def create_task(ctx: Context) -> str: -You can provide default values for elicitation fields using Pydantic's `Field(default=...)`. Clients will pre-populate form fields with these defaults, making it easier for users to provide input. - -Default values are supported for all primitive types: -- Strings: `Field(default="[email protected]")` -- Integers: `Field(default=50)` -- Numbers: `Field(default=3.14)` -- Booleans: `Field(default=False)` -- Enums: `Field(default=EnumValue.A)` - -Fields with default values are automatically marked as optional (not included in the `required` list), so users can accept the default or provide their own value. +Provide default values for elicitation fields using Pydantic's `Field(default=...)`. Clients will pre-populate form fields with these defaults. Fields with default values are automatically marked as optional. ```python from pydantic import BaseModel, Field @@ -423,40 +354,4 @@ async def create_task(ctx: Context) -> str: return "Task creation cancelled" ``` -## Multi-Turn Elicitation - -Tools can make multiple elicitation calls to gather information progressively: - -```python {6, 11, 16-19} -@mcp.tool -async def plan_meeting(ctx: Context) -> str: - """Plan a meeting by gathering details step by step.""" - - # Get meeting title - title_result = await ctx.elicit("What's the meeting title?", response_type=str) - if title_result.action != "accept": - return "Meeting planning cancelled" - - # Get duration - duration_result = await ctx.elicit("Duration in minutes?", response_type=int) - if duration_result.action != "accept": - return "Meeting planning cancelled" - - # Get priority - priority_result = await ctx.elicit( - "Is this urgent?", - response_type=Literal["yes", "no"] - ) - if priority_result.action != "accept": - return "Meeting planning cancelled" - - urgent = priority_result.data == "yes" - return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})" -``` - - -## Client Requirements - -Elicitation requires the client to implement an elicitation handler. See [Client Elicitation](/clients/elicitation) for details on how clients can handle these requests. - -If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported. \ No newline at end of file +Default values are supported for strings, integers, numbers, booleans, and enums. diff --git a/docs/servers/icons.mdx b/docs/servers/icons.mdx index a7d038f6a..c9b558094 100644 --- a/docs/servers/icons.mdx +++ b/docs/servers/icons.mdx @@ -2,7 +2,6 @@ title: Icons description: Add visual icons to your servers, tools, resources, and prompts icon: image -tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' @@ -13,11 +12,7 @@ Icons provide visual representations for your MCP servers and components, helpin ## Icon Format -Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies: - -- **src**: URL or data URI pointing to the icon image -- **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml") -- **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"]) +Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies a source URL or data URI, and optionally includes MIME type and size information. ```python from mcp.types import Icon @@ -29,9 +24,15 @@ icon = Icon( ) ``` +The fields serve different purposes: + +- **src**: URL or data URI pointing to the icon image +- **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml") +- **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"]) + ## Server Icons -Add icons and a website URL to your server for display in client applications: +Add icons and a website URL to your server for display in client applications. Multiple icons at different sizes help clients choose the best resolution for their display context. ```python from fastmcp import FastMCP @@ -59,7 +60,7 @@ Server icons appear in MCP client interfaces to help users identify your server ## Component Icons -Icons can be added to individual tools, resources, resource templates, and prompts: +Icons can be added to individual tools, resources, resource templates, and prompts. This helps users visually distinguish between different component types and purposes. ### Tool Icons @@ -111,7 +112,7 @@ def analyze_code(code: str): ## Using Data URIs -For small icons or when you want to embed the icon directly, use data URIs: +For small icons or when you want to embed the icon directly without external dependencies, use data URIs. This approach eliminates the need for hosting and ensures the icon is always available. ```python from mcp.types import Icon @@ -127,8 +128,17 @@ svg_icon = Icon( def my_tool() -> str: """A tool with an embedded SVG icon.""" return "result" +``` -# Generating a data URI from a local image file. +### Generating Data URIs from Files + +FastMCP provides the `Image` utility class to convert local image files into data URIs. + +```python +from mcp.types import Icon +from fastmcp.utilities.types import Image + +# Generate a data URI from a local image file img = Image(path="./assets/brand/favicon.png") icon = Icon(src=img.to_data_uri()) @@ -137,3 +147,5 @@ def file_icon_tool() -> str: """A tool with an icon generated from a local file.""" return "result" ``` + +This approach is useful when you have local image assets and want to embed them directly in your server definition. diff --git a/docs/servers/lifespan.mdx b/docs/servers/lifespan.mdx index 0e8f07809..822e4f8a1 100644 --- a/docs/servers/lifespan.mdx +++ b/docs/servers/lifespan.mdx @@ -1,5 +1,6 @@ --- title: Lifespans +sidebarTitle: Lifespan description: Server-level setup and teardown with composable lifespans icon: heart-pulse tag: NEW diff --git a/docs/servers/logging.mdx b/docs/servers/logging.mdx index 23283b083..e01fdd875 100644 --- a/docs/servers/logging.mdx +++ b/docs/servers/logging.mdx @@ -8,27 +8,16 @@ icon: receipt import { VersionBadge } from '/snippets/version-badge.mdx' -This documentation covers **MCP client logging** - sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module. +This documentation covers **MCP client logging**—sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module. -Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. This provides visibility into function execution and helps with debugging during development and operation. +Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs. -## Why Use Server Logging? - -Server logging is essential for: - -- **Debugging**: Send detailed execution information to help diagnose issues -- **Progress visibility**: Keep users informed about what the tool is doing -- **Error reporting**: Communicate problems and their context to clients -- **Audit trails**: Create records of tool execution for compliance or analysis - -Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs. - -### Basic Usage +## Basic Usage Use the context logging methods within any tool function: -```python {8-9, 13, 17, 21} +```python from fastmcp import FastMCP, Context mcp = FastMCP("LoggingDemo") @@ -38,24 +27,33 @@ async def analyze_data(data: list[float], ctx: Context) -> dict: """Analyze numerical data with comprehensive logging.""" await ctx.debug("Starting analysis of numerical data") await ctx.info(f"Analyzing {len(data)} data points") - + try: if not data: await ctx.warning("Empty data list provided") return {"error": "Empty data list"} - + result = sum(data) / len(data) await ctx.info(f"Analysis complete, average: {result}") return {"average": result, "count": len(data)} - + except Exception as e: await ctx.error(f"Analysis failed: {str(e)}") raise ``` -## Structured Logging with `extra` +## Log Levels -All logging methods (`debug`, `info`, `warning`, `error`, `log`) now accept an `extra` parameter, which is a dictionary of arbitrary data. This allows you to send structured data to the client, which is useful for creating rich, queryable logs. +| Level | Use Case | +|-------|----------| +| `ctx.debug()` | Detailed execution information for diagnosing problems | +| `ctx.info()` | General information about normal program execution | +| `ctx.warning()` | Potentially harmful situations that don't prevent execution | +| `ctx.error()` | Error events that might still allow the application to continue | + +## Structured Logging + +All logging methods accept an `extra` parameter for sending structured data to the client. This is useful for creating rich, queryable logs. ```python @mcp.tool @@ -68,182 +66,22 @@ async def process_transaction(transaction_id: str, amount: float, ctx: Context): "currency": "USD" } ) - # ... processing logic ... ``` -## Server Logs +## Server-Side Logs -Client Logging in the form of `ctx.log()` and its convenience methods (`debug`, `info`, `warning`, `error`) are meant for sending messages to the MCP clients. Messages sent to clients are also logged to the server's log at `DEBUG` level. Enable debug logging on the server or enable debug logging on the `fastmcp.server.context.to_client` logger to see these messages in the server's log. +Messages sent to clients via `ctx.log()` and its convenience methods are also logged to the server's log at `DEBUG` level. Enable debug logging on the `fastmcp.server.context.to_client` logger to see these messages: ```python import logging - from fastmcp.utilities.logging import get_logger to_client_logger = get_logger(name="fastmcp.server.context.to_client") to_client_logger.setLevel(level=logging.DEBUG) ``` -## Logging Methods - - - - Send debug-level messages for detailed execution information - - - - The debug message to send to the client - - - Optional dictionary for structured logging data - - - - - - Send informational messages about normal execution - - - - The information message to send to the client - - - Optional dictionary for structured logging data - - - - - - Send warning messages for potential issues that didn't prevent execution - - - - The warning message to send to the client - - - Optional dictionary for structured logging data - - - - - - Send error messages for problems that occurred during execution - - - - The error message to send to the client - - - Optional dictionary for structured logging data - - - - - - Generic logging method with custom level and logger name - - - - The log level for the message - - - - The message to send to the client - - - - Optional custom logger name for categorizing messages - - - Optional dictionary for structured logging data - - - - - -## Log Levels - -### Debug -Use for detailed information that's typically only useful when diagnosing problems: - -```python -@mcp.tool -async def process_file(file_path: str, ctx: Context) -> str: - """Process a file with detailed debug logging.""" - await ctx.debug(f"Starting to process file: {file_path}") - await ctx.debug("Checking file permissions") - - # File processing logic - await ctx.debug("File processing completed successfully") - return "File processed" -``` - -### Info -Use for general information about normal program execution: - -```python -@mcp.tool -async def backup_database(ctx: Context) -> str: - """Backup database with progress information.""" - await ctx.info("Starting database backup") - await ctx.info("Connecting to database") - await ctx.info("Backup completed successfully") - return "Database backed up" -``` - -### Warning -Use for potentially harmful situations that don't prevent execution: - -```python -@mcp.tool -async def validate_config(config: dict, ctx: Context) -> dict: - """Validate configuration with warnings for deprecated options.""" - if "old_api_key" in config: - await ctx.warning( - "Using deprecated 'old_api_key' field. Please use 'api_key' instead", - extra={"deprecated_field": "old_api_key"} - ) - - if config.get("timeout", 30) > 300: - await ctx.warning( - "Timeout value is very high (>5 minutes), this may cause issues", - extra={"timeout_value": config.get("timeout")} - ) - - return {"status": "valid", "warnings": "see logs"} -``` - -### Error -Use for error events that might still allow the application to continue: - -```python -@mcp.tool -async def batch_process(items: list[str], ctx: Context) -> dict: - """Process multiple items, logging errors for failed items.""" - successful = 0 - failed = 0 - - for item in items: - try: - # Process item - successful += 1 - except Exception as e: - await ctx.error( - f"Failed to process item '{item}': {str(e)}", - extra={"failed_item": item} - ) - failed += 1 - - return {"successful": successful, "failed": failed} -``` - - ## Client Handling -Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation: +Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation—development clients may display logs in real-time, production clients may store them for analysis, and integration clients may forward them to external logging systems. -- **Development clients**: May display logs in real-time for debugging -- **Production clients**: May store logs for later analysis or display to users -- **Integration clients**: May forward logs to external logging systems - -See [Client Logging](/clients/logging) for details on how clients can handle server log messages. +See [Client Logging](/clients/logging) for details on how clients handle server log messages. diff --git a/docs/servers/middleware.mdx b/docs/servers/middleware.mdx index 3ce35ce95..63af9c539 100644 --- a/docs/servers/middleware.mdx +++ b/docs/servers/middleware.mdx @@ -1,627 +1,521 @@ --- -title: MCP Middleware +title: Middleware sidebarTitle: Middleware -description: Add cross-cutting functionality to your MCP server with middleware that can inspect, modify, and respond to all MCP requests and responses. +description: Add cross-cutting functionality to your MCP server with middleware that intercepts and modifies requests and responses. icon: layer-group -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx" -MCP middleware is a powerful concept that allows you to add cross-cutting functionality to your FastMCP server. Unlike traditional web middleware, MCP middleware is designed specifically for the Model Context Protocol, providing hooks for different types of MCP operations like tool calls, resource reads, and prompt requests. +Middleware adds behavior that applies across multiple operations—authentication, logging, rate limiting, or request transformation—without modifying individual tools or resources. -MCP middleware is a FastMCP-specific concept and is not part of the official MCP protocol specification. This middleware system is designed to work with FastMCP servers and may not be compatible with other MCP implementations. +MCP middleware is a FastMCP-specific concept and is not part of the official MCP protocol specification. - -MCP middleware is a brand new concept and may be subject to breaking changes in future versions. - +## Overview -## What is MCP Middleware? +MCP middleware forms a pipeline around your server's operations. When a request arrives, it flows through each middleware in order—each can inspect, modify, or reject the request before passing it along. After the operation completes, the response flows back through the same middleware in reverse order. -MCP middleware lets you intercept and modify MCP requests and responses as they flow through your server. Think of it as a pipeline where each piece of middleware can inspect what's happening, make changes, and then pass control to the next middleware in the chain. - -Common use cases for MCP middleware include: -- **Authentication and Authorization**: Verify client permissions before executing operations -- **Logging and Monitoring**: Track usage patterns and performance metrics -- **Rate Limiting**: Control request frequency per client or operation type -- **Request/Response Transformation**: Modify data before it reaches tools or after it leaves -- **Caching**: Store frequently requested data to improve performance -- **Error Handling**: Provide consistent error responses across your server - -## How Middleware Works - -FastMCP middleware operates on a pipeline model. When a request comes in, it flows through your middleware in the order they were added to the server. Each middleware can: - -1. **Inspect the incoming request** and its context -2. **Modify the request** before passing it to the next middleware or handler -3. **Execute the next middleware/handler** in the chain by calling `call_next()` -4. **Inspect and modify the response** before returning it -5. **Handle errors** that occur during processing - -The key insight is that middleware forms a chain where each piece decides whether to continue processing or stop the chain entirely. - -If you're familiar with ASGI middleware, the basic structure of FastMCP middleware will feel familiar. At its core, middleware is a callable class that receives a context object containing information about the current JSON-RPC message and a handler function to continue the middleware chain. - -It's important to understand that MCP operates on the [JSON-RPC specification](https://spec.modelcontextprotocol.io/specification/basic/transports/). While FastMCP presents requests and responses in a familiar way, these are fundamentally JSON-RPC messages, not HTTP request/response pairs like you might be used to in web applications. FastMCP middleware works with all [transport types](/clients/transports), including local stdio transport and HTTP transports, though not all middleware implementations are compatible across all transports (e.g., middleware that inspects HTTP headers won't work with stdio transport). - -The most fundamental way to implement middleware is by overriding the `__call__` method on the `Middleware` base class: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext - -class RawMiddleware(Middleware): - async def __call__(self, context: MiddlewareContext, call_next): - # This method receives ALL messages regardless of type - print(f"Raw middleware processing: {context.method}") - result = await call_next(context) - print(f"Raw middleware completed: {context.method}") - return result +``` +Request → Middleware A → Middleware B → Handler → Middleware B → Middleware A → Response ``` -This gives you complete control over every message that flows through your server, but requires you to handle all message types manually. +This bidirectional flow means middleware can: +- **Pre-process**: Validate authentication, log incoming requests, check rate limits +- **Post-process**: Transform responses, record timing metrics, handle errors consistently -## Middleware Hooks - -To make it easier for users to target specific types of messages, FastMCP middleware provides a variety of specialized hooks. Instead of implementing the raw `__call__` method, you can override specific hook methods that are called only for certain types of operations, allowing you to target exactly the level of specificity you need for your middleware logic. - -### Hook Hierarchy and Execution Order - -FastMCP provides multiple hooks that are called with varying levels of specificity. Understanding this hierarchy is crucial for effective middleware design. - -When a request comes in, **multiple hooks may be called for the same request**, going from general to specific: - -1. **`on_message`** - Called for ALL MCP messages (both requests and notifications) -2. **`on_request` or `on_notification`** - Called based on the message type -3. **Operation-specific hooks** - Called for specific MCP operations like `on_call_tool` - -For example, when a client calls a tool, your middleware will receive **multiple hook calls**: -1. `on_message` and `on_request` for any initial tool discovery operations (list_tools) -2. `on_message` (because it's any MCP message) for the tool call itself -3. `on_request` (because tool calls expect responses) for the tool call itself -4. `on_call_tool` (because it's specifically a tool execution) for the tool call itself - -Note that the MCP SDK may perform additional operations like listing tools for caching purposes, which will trigger additional middleware calls beyond just the direct tool execution. - -This hierarchy allows you to target your middleware logic with the right level of specificity. Use `on_message` for broad concerns like logging, `on_request` for authentication, and `on_call_tool` for tool-specific logic like performance monitoring. - -### Available Hooks - - -- `on_message`: Called for all MCP messages (requests and notifications) -- `on_request`: Called specifically for MCP requests (that expect responses) -- `on_notification`: Called specifically for MCP notifications (fire-and-forget) - -- `on_call_tool`: Called when tools are being executed -- `on_read_resource`: Called when resources are being read -- `on_get_prompt`: Called when prompts are being retrieved -- `on_list_tools`: Called when listing available tools -- `on_list_resources`: Called when listing available resources -- `on_list_resource_templates`: Called when listing resource templates -- `on_list_prompts`: Called when listing available prompts - -- `on_initialize`: Called when a client connects and initializes the session (returns `None`) - -The `on_initialize` hook receives the client's initialization request but **returns `None`** rather than a result. The initialization response is handled internally by the MCP protocol and cannot be modified by middleware. This hook is useful for client detection, logging connections, or initializing session state, but not for modifying the initialization handshake itself. - - -**Example:** - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext -from mcp import McpError -from mcp.types import ErrorData - -class InitializationMiddleware(Middleware): - async def on_initialize(self, context: MiddlewareContext, call_next): - # Check client capabilities before initialization - client_info = context.message.params.get("clientInfo", {}) - client_name = client_info.get("name", "unknown") - - # Reject unsupported clients BEFORE call_next - if client_name == "unsupported-client": - raise McpError(ErrorData(code=-32000, message="This client is not supported")) - - # Log successful initialization - await call_next(context) - print(f"Client {client_name} initialized successfully") -``` - - -If you raise `McpError` in `on_initialize` **after** calling `call_next()`, the error will only be logged and will not be sent to the client. The initialization response has already been sent at that point. Always raise `McpError` **before** `call_next()` if you want to reject the initialization. - - -### MCP Session Availability in Middleware - - - -The MCP session and request context are not available during certain phases like initialization. When middleware runs during these phases, `context.fastmcp_context.request_context` returns `None` rather than the full MCP request context. - -This typically occurs when: -- The `on_request` hook fires during client initialization -- The MCP handshake hasn't completed yet - -To handle this in middleware, check if the MCP request context is available before accessing MCP-specific attributes. Note that the MCP request context is distinct from the HTTP request - for HTTP transports, you can use HTTP helpers to access request data even when the MCP session is not available: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext - -class SessionAwareMiddleware(Middleware): - async def on_request(self, context: MiddlewareContext, call_next): - ctx = context.fastmcp_context - - if ctx.request_context: - # MCP session available - can access session-specific attributes - session_id = ctx.session_id - request_id = ctx.request_id - else: - # MCP session not available yet - use HTTP helpers for request data (if using HTTP transport) - from fastmcp.server.dependencies import get_http_headers - headers = get_http_headers() - # Access HTTP data for auth, logging, etc. - - return await call_next(context) -``` - -For HTTP request data (headers, client IP, etc.) when using HTTP transports, use `get_http_request()` or `get_http_headers()` from `fastmcp.server.dependencies`, which work regardless of MCP session availability. See [HTTP Requests](/servers/context#http-requests) for details. - -## Component Access in Middleware - -Understanding how to access component information (tools, resources, prompts) in middleware is crucial for building powerful middleware functionality. The access patterns differ significantly between listing operations and execution operations. - -### Listing Operations vs Execution Operations - -FastMCP middleware handles two types of operations differently: - -**Listing Operations** (`on_list_tools`, `on_list_resources`, `on_list_prompts`, etc.): -- Middleware receives **FastMCP component objects** with full metadata -- These objects include FastMCP-specific properties like `tags` that can be accessed directly from the component -- The result contains complete component information before it's converted to MCP format -- Tags are included in the component's `meta` field in the listing response returned to MCP clients - -**Execution Operations** (`on_call_tool`, `on_read_resource`, `on_get_prompt`): -- Middleware runs **before** the component is executed -- The middleware result is either the execution result or an error if the component wasn't found -- Component metadata isn't directly available in the hook parameters - -### Accessing Component Metadata During Execution - -If you need to check component properties (like tags) during execution operations, use the FastMCP server instance available through the context: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext -from fastmcp.exceptions import ToolError - -class TagBasedMiddleware(Middleware): - async def on_call_tool(self, context: MiddlewareContext, call_next): - # Access the tool object to check its metadata - if context.fastmcp_context: - try: - tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name) - - # Check if this tool has a "private" tag - if "private" in tool.tags: - raise ToolError("Access denied: private tool") - - # Check if tool is enabled - if not tool.enabled: - raise ToolError("Tool is currently disabled") - - except Exception: - # Tool not found or other error - let execution continue - # and handle the error naturally - pass - - return await call_next(context) -``` - -The same pattern works for resources and prompts: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext -from fastmcp.exceptions import ResourceError, PromptError - -class ComponentAccessMiddleware(Middleware): - async def on_read_resource(self, context: MiddlewareContext, call_next): - if context.fastmcp_context: - try: - resource = await context.fastmcp_context.fastmcp.get_resource(context.message.uri) - if "restricted" in resource.tags: - raise ResourceError("Access denied: restricted resource") - except Exception: - pass - return await call_next(context) - - async def on_get_prompt(self, context: MiddlewareContext, call_next): - if context.fastmcp_context: - try: - prompt = await context.fastmcp_context.fastmcp.get_prompt(context.message.name) - if not prompt.enabled: - raise PromptError("Prompt is currently disabled") - except Exception: - pass - return await call_next(context) -``` - -### Working with Listing Results - -For listing operations, the middleware `call_next` function returns a list of FastMCP components prior to being converted to MCP format. You can filter or modify this list and return it to the client. For example: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext - -class ListingFilterMiddleware(Middleware): - async def on_list_tools(self, context: MiddlewareContext, call_next): - result = await call_next(context) - - # Filter out tools with "private" tag - filtered_tools = [ - tool for tool in result - if "private" not in tool.tags - ] - - # Return modified list - return filtered_tools -``` - -This filtering happens before the components are converted to MCP format and returned to the client. Tags are accessible both during filtering and are included in the component's `meta` field in the final listing response. - - -When filtering components in listing operations, ensure you also prevent execution of filtered components in the corresponding execution hooks (`on_call_tool`, `on_read_resource`, `on_get_prompt`) to maintain consistency. - - -### Tool Call Denial - -You can deny access to specific tools by raising a `ToolError` in your middleware. This is the correct way to block tool execution, as it integrates properly with the FastMCP error handling system. - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext -from fastmcp.exceptions import ToolError - -class AuthMiddleware(Middleware): - async def on_call_tool(self, context: MiddlewareContext, call_next): - tool_name = context.message.name - - # Deny access to restricted tools - if tool_name.lower() in ["delete", "admin_config"]: - raise ToolError("Access denied: tool requires admin privileges") - - # Allow other tools to proceed - return await call_next(context) -``` - - -When denying tool calls, always raise `ToolError` rather than returning `ToolResult` objects or other values. `ToolError` ensures proper error propagation through the middleware chain and converts to the correct MCP error response format. - - -### Tool Call Modification - -For execution operations like tool calls, you can modify arguments before execution or transform results afterward: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext - -class ToolCallMiddleware(Middleware): - async def on_call_tool(self, context: MiddlewareContext, call_next): - # Modify arguments before execution - if context.message.name == "calculate": - # Ensure positive inputs - if context.message.arguments.get("value", 0) < 0: - context.message.arguments["value"] = abs(context.message.arguments["value"]) - - result = await call_next(context) - - # Transform result after execution - if context.message.name == "get_data": - # Add metadata to result - if result.structured_content: - result.structured_content["processed_at"] = "2024-01-01T00:00:00Z" - - return result -``` - - -For more complex tool rewriting scenarios, consider using [Transforms](/servers/providers/transforms) which provide a more structured approach to creating modified tool variants. - - -### Anatomy of a Hook - -Every middleware hook follows the same pattern. Let's examine the `on_message` hook to understand the structure: - -```python -async def on_message(self, context: MiddlewareContext, call_next): - # 1. Pre-processing: Inspect and optionally modify the request - print(f"Processing {context.method}") - - # 2. Chain continuation: Call the next middleware/handler - result = await call_next(context) - - # 3. Post-processing: Inspect and optionally modify the response - print(f"Completed {context.method}") - - # 4. Return the result (potentially modified) - return result -``` - -### Hook Parameters - -Every hook receives two parameters: - -1. **`context: MiddlewareContext`** - Contains information about the current request: - - `context.method` - The MCP method name (e.g., "tools/call") - - `context.source` - Where the request came from ("client" or "server") - - `context.type` - Message type ("request" or "notification") - - `context.message` - The MCP message data - - `context.timestamp` - When the request was received - - `context.fastmcp_context` - FastMCP Context object (if available) - -2. **`call_next`** - A function that continues the middleware chain. You **must** call this to proceed, unless you want to stop processing entirely. - -### Control Flow - -You have complete control over the request flow: -- **Continue processing**: Call `await call_next(context)` to proceed -- **Modify the request**: Change the context before calling `call_next` -- **Modify the response**: Change the result after calling `call_next` -- **Stop the chain**: Don't call `call_next` (rarely needed) -- **Handle errors**: Wrap `call_next` in try/catch blocks - -#### State Management - - - -In addition to modifying the request and response, you can also store state data that your tools can (optionally) access later. To do so, use the FastMCP Context to either `set_state` or `get_state` as appropriate. For more information, see the [Context State Management](/servers/context#state-management) docs. - -## Creating Middleware - -FastMCP middleware is implemented by subclassing the `Middleware` base class and overriding the hooks you need. You only need to implement the hooks that are relevant to your use case. +The key decision point is `call_next(context)`. Calling it continues the chain; not calling it stops processing entirely. ```python from fastmcp import FastMCP from fastmcp.server.middleware import Middleware, MiddlewareContext class LoggingMiddleware(Middleware): - """Middleware that logs all MCP operations.""" - async def on_message(self, context: MiddlewareContext, call_next): - """Called for all MCP messages.""" - print(f"Processing {context.method} from {context.source}") - + print(f"→ {context.method}") result = await call_next(context) - - print(f"Completed {context.method}") + print(f"← {context.method}") return result -# Add middleware to your server mcp = FastMCP("MyServer") mcp.add_middleware(LoggingMiddleware()) ``` -This creates a basic logging middleware that will print information about every request that flows through your server. +### Execution Order -## Adding Middleware to Your Server - -### Single Middleware - -Adding middleware to your server is straightforward: +Middleware executes in the order added to the server. The first middleware runs first on the way in and last on the way out: ```python +from fastmcp import FastMCP +from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware +from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware +from fastmcp.server.middleware.logging import LoggingMiddleware + mcp = FastMCP("MyServer") -mcp.add_middleware(LoggingMiddleware()) +mcp.add_middleware(ErrorHandlingMiddleware()) # 1st in, last out +mcp.add_middleware(RateLimitingMiddleware()) # 2nd in, 2nd out +mcp.add_middleware(LoggingMiddleware()) # 3rd in, first out ``` -### Multiple Middleware +This ordering matters. Place error handling early so it catches exceptions from all subsequent middleware. Place logging late so it records the actual execution after other middleware has processed the request. -Middleware executes in the order it's added to the server. The first middleware added runs first on the way in, and last on the way out: +### Server Composition + +When using [mounted servers](/servers/providers/mounting), middleware behavior follows a clear hierarchy: + +- **Parent middleware** runs for all requests, including those routed to mounted servers +- **Mounted server middleware** only runs for requests handled by that specific server ```python -mcp = FastMCP("MyServer") +from fastmcp import FastMCP +from fastmcp.server.middleware.logging import LoggingMiddleware -mcp.add_middleware(AuthenticationMiddleware("secret-token")) -mcp.add_middleware(PerformanceMiddleware()) -mcp.add_middleware(LoggingMiddleware()) -``` - -This creates the following execution flow: -1. AuthenticationMiddleware (pre-processing) -2. PerformanceMiddleware (pre-processing) -3. LoggingMiddleware (pre-processing) -4. Actual tool/resource handler -5. LoggingMiddleware (post-processing) -6. PerformanceMiddleware (post-processing) -7. AuthenticationMiddleware (post-processing) - -## Server Composition and Middleware - -When using [Mounting Servers](/servers/providers/mounting) with `mount` or `import_server`, middleware behavior follows these rules: - -1. **Parent server middleware** runs for all requests, including those routed to mounted servers -2. **Mounted server middleware** only runs for requests handled by that specific server -3. **Middleware order** is preserved within each server - -This allows you to create layered middleware architectures where parent servers handle cross-cutting concerns like authentication, while child servers focus on domain-specific middleware. - -```python -# Parent server with middleware parent = FastMCP("Parent") -parent.add_middleware(AuthenticationMiddleware("token")) +parent.add_middleware(AuthMiddleware()) # Runs for ALL requests -# Child server with its own middleware child = FastMCP("Child") -child.add_middleware(LoggingMiddleware()) +child.add_middleware(LoggingMiddleware()) # Only runs for child's tools -@child.tool -def child_tool() -> str: - return "from child" - -# Mount the child server parent.mount(child, namespace="child") ``` -When a client calls "child_tool", the request will flow through the parent's authentication middleware first, then route to the child server where it will go through the child's logging middleware. +Requests to `child_tool` flow through the parent's `AuthMiddleware` first, then through the child's `LoggingMiddleware`. -## Built-in Middleware Examples +## Hooks -FastMCP includes several middleware implementations that demonstrate best practices and provide immediately useful functionality. Let's explore how each type works by building simplified versions, then see how to use the full implementations. +Rather than processing every message identically, FastMCP provides specialized hooks at different levels of specificity. Multiple hooks fire for a single request, going from general to specific: -### Timing Middleware +| Level | Hooks | Purpose | +|-------|-------|---------| +| Message | `on_message` | All MCP traffic (requests and notifications) | +| Type | `on_request`, `on_notification` | Requests expecting responses vs fire-and-forget | +| Operation | `on_call_tool`, `on_read_resource`, `on_get_prompt`, etc. | Specific MCP operations | -Performance monitoring is essential for understanding your server's behavior and identifying bottlenecks. FastMCP includes timing middleware at `fastmcp.server.middleware.timing`. +When a client calls a tool, the middleware chain processes `on_message` first, then `on_request`, then `on_call_tool`. This hierarchy lets you target exactly the right scope—use `on_message` for logging everything, `on_request` for authentication, and `on_call_tool` for tool-specific behavior. -Here's an example of how it works: +### Hook Signature + +Every hook follows the same pattern: + +```python +async def hook_name(self, context: MiddlewareContext, call_next) -> result_type: + # Pre-processing + result = await call_next(context) + # Post-processing + return result +``` + +**Parameters:** +- `context` — `MiddlewareContext` containing request information +- `call_next` — Async function to continue the middleware chain + +**Returns:** The appropriate result type for the hook (varies by operation). + +### MiddlewareContext + +The `context` parameter provides access to request details: + +| Attribute | Type | Description | +|-----------|------|-------------| +| `method` | `str` | MCP method name (e.g., `"tools/call"`) | +| `source` | `str` | Origin: `"client"` or `"server"` | +| `type` | `str` | Message type: `"request"` or `"notification"` | +| `message` | `object` | The MCP message data | +| `timestamp` | `datetime` | When the request was received | +| `fastmcp_context` | `Context` | FastMCP context object (if available) | + +### Message Hooks + +#### on_message + +Called for every MCP message—both requests and notifications. + +```python +async def on_message(self, context: MiddlewareContext, call_next): + result = await call_next(context) + return result +``` + +Use for: Logging, metrics, or any cross-cutting concern that applies to all traffic. + +#### on_request + +Called for MCP requests that expect a response. + +```python +async def on_request(self, context: MiddlewareContext, call_next): + result = await call_next(context) + return result +``` + +Use for: Authentication, authorization, request validation. + +#### on_notification + +Called for fire-and-forget MCP notifications. + +```python +async def on_notification(self, context: MiddlewareContext, call_next): + await call_next(context) + # Notifications don't return values +``` + +Use for: Event logging, async side effects. + +### Operation Hooks + +#### on_call_tool + +Called when a tool is executed. The `context.message` contains `name` (tool name) and `arguments` (dict). + +```python +async def on_call_tool(self, context: MiddlewareContext, call_next): + tool_name = context.message.name + args = context.message.arguments + result = await call_next(context) + return result +``` + +**Returns:** Tool execution result or raises `ToolError`. + +#### on_read_resource + +Called when a resource is read. The `context.message` contains `uri` (resource URI). + +```python +async def on_read_resource(self, context: MiddlewareContext, call_next): + uri = context.message.uri + result = await call_next(context) + return result +``` + +**Returns:** Resource content. + +#### on_get_prompt + +Called when a prompt is retrieved. The `context.message` contains `name` (prompt name) and `arguments` (dict). + +```python +async def on_get_prompt(self, context: MiddlewareContext, call_next): + prompt_name = context.message.name + result = await call_next(context) + return result +``` + +**Returns:** Prompt messages. + +#### on_list_tools + +Called when listing available tools. Returns a list of FastMCP `Tool` objects before MCP conversion. + +```python +async def on_list_tools(self, context: MiddlewareContext, call_next): + tools = await call_next(context) + # Filter or modify the tool list + return tools +``` + +**Returns:** `list[Tool]` — Can be filtered before returning to client. + +#### on_list_resources + +Called when listing available resources. Returns FastMCP `Resource` objects. + +```python +async def on_list_resources(self, context: MiddlewareContext, call_next): + resources = await call_next(context) + return resources +``` + +**Returns:** `list[Resource]` + +#### on_list_resource_templates + +Called when listing resource templates. + +```python +async def on_list_resource_templates(self, context: MiddlewareContext, call_next): + templates = await call_next(context) + return templates +``` + +**Returns:** `list[ResourceTemplate]` + +#### on_list_prompts + +Called when listing available prompts. + +```python +async def on_list_prompts(self, context: MiddlewareContext, call_next): + prompts = await call_next(context) + return prompts +``` + +**Returns:** `list[Prompt]` + +#### on_initialize + + + +Called when a client connects and initializes the session. This hook cannot modify the initialization response. + +```python +from mcp import McpError +from mcp.types import ErrorData + +async def on_initialize(self, context: MiddlewareContext, call_next): + client_info = context.message.params.get("clientInfo", {}) + client_name = client_info.get("name", "unknown") + + # Reject before call_next to send error to client + if client_name == "blocked-client": + raise McpError(ErrorData(code=-32000, message="Client not supported")) + + await call_next(context) + print(f"Client {client_name} initialized") +``` + +**Returns:** `None` — The initialization response is handled internally by the MCP protocol. + + +Raising `McpError` after `call_next()` will only log the error, not send it to the client. The response has already been sent. Always reject **before** `call_next()`. + + +### Raw Handler + +For complete control over all messages, override `__call__` instead of individual hooks: ```python -import time from fastmcp.server.middleware import Middleware, MiddlewareContext -class SimpleTimingMiddleware(Middleware): - async def on_request(self, context: MiddlewareContext, call_next): - start_time = time.perf_counter() - - try: - result = await call_next(context) - duration_ms = (time.perf_counter() - start_time) * 1000 - print(f"Request {context.method} completed in {duration_ms:.2f}ms") - return result - except Exception as e: - duration_ms = (time.perf_counter() - start_time) * 1000 - print(f"Request {context.method} failed after {duration_ms:.2f}ms: {e}") - raise +class RawMiddleware(Middleware): + async def __call__(self, context: MiddlewareContext, call_next): + print(f"Processing: {context.method}") + result = await call_next(context) + print(f"Completed: {context.method}") + return result ``` -To use the full version with proper logging and configuration: +This bypasses the hook dispatch system entirely. Use when you need uniform handling regardless of message type. + +### Session Availability + + + +The MCP session may not be available during certain phases like initialization. Check before accessing session-specific attributes: ```python -from fastmcp.server.middleware.timing import ( - TimingMiddleware, - DetailedTimingMiddleware -) +async def on_request(self, context: MiddlewareContext, call_next): + ctx = context.fastmcp_context -# Basic timing for all requests -mcp.add_middleware(TimingMiddleware()) + if ctx.request_context: + # MCP session available + session_id = ctx.session_id + request_id = ctx.request_id + else: + # Session not yet established (e.g., during initialization) + # Use HTTP helpers if needed + from fastmcp.server.dependencies import get_http_headers + headers = get_http_headers() -# Detailed per-operation timing (tools, resources, prompts) -mcp.add_middleware(DetailedTimingMiddleware()) + return await call_next(context) ``` -The built-in versions include custom logger support, proper formatting, and **DetailedTimingMiddleware** provides operation-specific hooks like `on_call_tool` and `on_read_resource` for granular timing. +For HTTP-specific data (headers, client IP) when using HTTP transports, see [HTTP Requests](/servers/context#http-requests). -### Tool Injection Middleware +## Built-in Middleware -Tool injection middleware is a middleware that injects tools into the server during the request lifecycle: +FastMCP includes production-ready middleware for common server concerns. + +### Logging ```python -from fastmcp.server.middleware.tool_injection import ToolInjectionMiddleware - -def my_tool_fn(a: int, b: int) -> int: - return a + b - -my_tool = Tool.from_function(fn=my_tool_fn, name="my_tool") - -mcp.add_middleware(ToolInjectionMiddleware(tools=[my_tool])) +from fastmcp.server.middleware.logging import LoggingMiddleware, StructuredLoggingMiddleware ``` -### Prompt Tool Middleware - -Prompt tool middleware is a compatibility middleware for clients that are unable to list or get prompts. It provides two tools: `list_prompts` and `get_prompt` which allow clients to list and get prompts respectively using only tool calls. +`LoggingMiddleware` provides human-readable request and response logging. `StructuredLoggingMiddleware` outputs JSON-formatted logs for aggregation tools like Datadog or Splunk. ```python -from fastmcp.server.middleware.tool_injection import PromptToolMiddleware +from fastmcp import FastMCP +from fastmcp.server.middleware.logging import LoggingMiddleware -mcp.add_middleware(PromptToolMiddleware()) -``` - -### Resource Tool Middleware - -Resource tool middleware is a compatibility middleware for clients that are unable to list or read resources. It provides two tools: `list_resources` and `read_resource` which allow clients to list and read resources respectively using only tool calls. - -```python -from fastmcp.server.middleware.tool_injection import ResourceToolMiddleware - -mcp.add_middleware(ResourceToolMiddleware()) -``` - -### Caching Middleware - -Caching middleware is essential for improving performance and reducing server load. FastMCP provides caching middleware at `fastmcp.server.middleware.caching`. - -Here's how to use the full version: - -```python -from fastmcp.server.middleware.caching import ResponseCachingMiddleware - -mcp.add_middleware(ResponseCachingMiddleware()) -``` - -Out of the box, it caches call/list tool, resources, and prompts to an in-memory cache with TTL-based expiration. Cache entries expire based on their TTL; there is no event-based cache invalidation. List calls are stored under global keys—when sharing a storage backend across multiple servers, consider namespacing collections to prevent conflicts. See [Storage Backends](/servers/storage-backends) for advanced configuration options. - -Each method can be configured individually, for example, caching list tools for 30 seconds, limiting caching to specific tools, and disabling caching for resource reads: - -```python -from fastmcp.server.middleware.caching import ResponseCachingMiddleware, CallToolSettings, ListToolsSettings, ReadResourceSettings - -mcp.add_middleware(ResponseCachingMiddleware( - list_tools_settings=ListToolsSettings( - ttl=30, - ), - call_tool_settings=CallToolSettings( - included_tools=["tool1"], - ), - read_resource_settings=ReadResourceSettings( - enabled=False - ) +mcp = FastMCP("MyServer") +mcp.add_middleware(LoggingMiddleware( + include_payloads=True, + max_payload_length=1000 )) ``` -#### Storage Backends +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `include_payloads` | `bool` | `False` | Log request/response content | +| `max_payload_length` | `int` | `500` | Truncate payloads beyond this length | +| `logger` | `Logger` | module logger | Custom logger instance | -By default, caching uses in-memory storage, which is fast but doesn't persist across restarts. For production or persistent caching across server restarts, configure a different storage backend. See [Storage Backends](/servers/storage-backends) for complete options including disk, Redis, DynamoDB, and custom implementations. +### Timing -Disk-based caching example: +```python +from fastmcp.server.middleware.timing import TimingMiddleware, DetailedTimingMiddleware +``` + +`TimingMiddleware` logs execution duration for all requests. `DetailedTimingMiddleware` provides per-operation timing with separate tracking for tools, resources, and prompts. + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.timing import TimingMiddleware + +mcp = FastMCP("MyServer") +mcp.add_middleware(TimingMiddleware()) +``` + +### Caching + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +``` + +Caches tool calls, resource reads, and list operations with TTL-based expiration. + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +mcp = FastMCP("MyServer") +mcp.add_middleware(ResponseCachingMiddleware()) +``` + +Each operation type can be configured independently using settings classes: + +```python +from fastmcp.server.middleware.caching import ( + ResponseCachingMiddleware, + CallToolSettings, + ListToolsSettings, + ReadResourceSettings +) + +mcp.add_middleware(ResponseCachingMiddleware( + list_tools_settings=ListToolsSettings(ttl=30), + call_tool_settings=CallToolSettings(included_tools=["expensive_tool"]), + read_resource_settings=ReadResourceSettings(enabled=False) +)) +``` + +| Settings Class | Configures | +|----------------|------------| +| `ListToolsSettings` | `on_list_tools` caching | +| `CallToolSettings` | `on_call_tool` caching | +| `ListResourcesSettings` | `on_list_resources` caching | +| `ReadResourceSettings` | `on_read_resource` caching | +| `ListPromptsSettings` | `on_list_prompts` caching | +| `GetPromptSettings` | `on_get_prompt` caching | + +Each settings class accepts: +- `enabled` — Enable/disable caching for this operation +- `ttl` — Time-to-live in seconds +- `included_*` / `excluded_*` — Whitelist or blacklist specific items + +For persistence or distributed deployments, configure a different storage backend: ```python from fastmcp.server.middleware.caching import ResponseCachingMiddleware from key_value.aio.stores.disk import DiskStore mcp.add_middleware(ResponseCachingMiddleware( - cache_storage=DiskStore(directory="cache"), + cache_storage=DiskStore(directory="cache") )) ``` -Redis for distributed deployments: +See [Storage Backends](/servers/storage-backends) for complete options. + +### Rate Limiting ```python -from fastmcp.server.middleware.caching import ResponseCachingMiddleware -from key_value.aio.stores.redis import RedisStore +from fastmcp.server.middleware.rate_limiting import ( + RateLimitingMiddleware, + SlidingWindowRateLimitingMiddleware +) +``` -mcp.add_middleware(ResponseCachingMiddleware( - cache_storage=RedisStore(host="redis.example.com", port=6379), +`RateLimitingMiddleware` uses a token bucket algorithm allowing controlled bursts. `SlidingWindowRateLimitingMiddleware` provides precise time-window rate limiting without burst allowance. + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware + +mcp = FastMCP("MyServer") +mcp.add_middleware(RateLimitingMiddleware( + max_requests_per_second=10.0, + burst_capacity=20 )) ``` -#### Cache Statistics +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `max_requests_per_second` | `float` | `10.0` | Sustained request rate | +| `burst_capacity` | `int` | `20` | Maximum burst size | +| `client_id_func` | `Callable` | `None` | Custom client identification | -The caching middleware collects operation statistics (hits, misses, etc.) through the underlying storage layer. Access statistics from the middleware instance: +For sliding window rate limiting: ```python -from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from fastmcp.server.middleware.rate_limiting import SlidingWindowRateLimitingMiddleware -middleware = ResponseCachingMiddleware() -mcp.add_middleware(middleware) - -# Later, retrieve statistics -stats = middleware.statistics() -print(f"Total cache operations: {stats}") +mcp.add_middleware(SlidingWindowRateLimitingMiddleware( + max_requests=100, + window_minutes=1 +)) ``` -### Ping Middleware +### Error Handling + +```python +from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware, RetryMiddleware +``` + +`ErrorHandlingMiddleware` provides centralized error logging and transformation. `RetryMiddleware` automatically retries with exponential backoff for transient failures. + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware + +mcp = FastMCP("MyServer") +mcp.add_middleware(ErrorHandlingMiddleware( + include_traceback=True, + transform_errors=True, + error_callback=my_error_callback +)) +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `include_traceback` | `bool` | `False` | Include stack traces in logs | +| `transform_errors` | `bool` | `False` | Convert exceptions to MCP errors | +| `error_callback` | `Callable` | `None` | Custom callback on errors | + +For automatic retries: + +```python +from fastmcp.server.middleware.error_handling import RetryMiddleware + +mcp.add_middleware(RetryMiddleware( + max_retries=3, + retry_exceptions=(ConnectionError, TimeoutError) +)) +``` + +### Ping -For long-lived connections, servers can send periodic pings to keep connections alive and prevent clients from dropping the connection due to inactivity. FastMCP provides ping middleware at `fastmcp.server.middleware.ping`. +```python +from fastmcp.server.middleware import PingMiddleware +``` + +Keeps long-lived connections alive by sending periodic pings. ```python from fastmcp import FastMCP @@ -631,216 +525,319 @@ mcp = FastMCP("MyServer") mcp.add_middleware(PingMiddleware(interval_ms=5000)) ``` -The middleware starts a background ping task on the first message from each client session. Pings continue at the configured interval until the session ends. When the session closes, the ping task is automatically cancelled and cleaned up. +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `interval_ms` | `int` | `30000` | Ping interval in milliseconds | -Configure the ping interval based on your client's timeout settings: +The ping task starts on the first message and stops automatically when the session ends. Most useful for stateful HTTP connections; has no effect on stateless connections. + +### Tool Injection ```python -# Ping every 30 seconds (default) -mcp.add_middleware(PingMiddleware()) - -# Ping every 5 seconds for clients with short timeouts -mcp.add_middleware(PingMiddleware(interval_ms=5000)) -``` - - -Ping middleware is most useful for stateful HTTP connections where clients maintain long-lived sessions. For stateless connections, pings have no effect since each request is independent. - - -### Logging Middleware - -Request and response logging is crucial for debugging, monitoring, and understanding usage patterns in your MCP server. FastMCP provides comprehensive logging middleware at `fastmcp.server.middleware.logging`. - -Here's an example of how it works: - -```python -from fastmcp.server.middleware import Middleware, MiddlewareContext - -class SimpleLoggingMiddleware(Middleware): - async def on_message(self, context: MiddlewareContext, call_next): - print(f"Processing {context.method} from {context.source}") - - try: - result = await call_next(context) - print(f"Completed {context.method}") - return result - except Exception as e: - print(f"Failed {context.method}: {e}") - raise -``` - -To use the full versions with advanced features: - -```python -from fastmcp.server.middleware.logging import ( - LoggingMiddleware, - StructuredLoggingMiddleware +from fastmcp.server.middleware.tool_injection import ( + ToolInjectionMiddleware, + PromptToolMiddleware, + ResourceToolMiddleware ) - -# Human-readable logging with payload support -mcp.add_middleware(LoggingMiddleware( - include_payloads=True, - max_payload_length=1000 -)) - -# JSON-structured logging for log aggregation tools -mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True)) ``` -The built-in versions include payload logging, structured JSON output, custom logger support, payload size limits, and operation-specific hooks for granular control. - -### Rate Limiting Middleware - -Rate limiting is essential for protecting your server from abuse, ensuring fair resource usage, and maintaining performance under load. FastMCP includes sophisticated rate limiting middleware at `fastmcp.server.middleware.rate_limiting`. - -Here's an example of how it works: - -```python -import time -from collections import defaultdict -from fastmcp.server.middleware import Middleware, MiddlewareContext -from mcp import McpError -from mcp.types import ErrorData - -class SimpleRateLimitMiddleware(Middleware): - def __init__(self, requests_per_minute: int = 60): - self.requests_per_minute = requests_per_minute - self.client_requests = defaultdict(list) - - async def on_request(self, context: MiddlewareContext, call_next): - current_time = time.time() - client_id = "default" # In practice, extract from headers or context - - # Clean old requests and check limit - cutoff_time = current_time - 60 - self.client_requests[client_id] = [ - req_time for req_time in self.client_requests[client_id] - if req_time > cutoff_time - ] - - if len(self.client_requests[client_id]) >= self.requests_per_minute: - raise McpError(ErrorData(code=-32000, message="Rate limit exceeded")) - - self.client_requests[client_id].append(current_time) - return await call_next(context) -``` - -To use the full versions with advanced algorithms: - -```python -from fastmcp.server.middleware.rate_limiting import ( - RateLimitingMiddleware, - SlidingWindowRateLimitingMiddleware -) - -# Token bucket rate limiting (allows controlled bursts) -mcp.add_middleware(RateLimitingMiddleware( - max_requests_per_second=10.0, - burst_capacity=20 -)) - -# Sliding window rate limiting (precise time-based control) -mcp.add_middleware(SlidingWindowRateLimitingMiddleware( - max_requests=100, - window_minutes=1 -)) -``` - -The built-in versions include token bucket algorithms, per-client identification, global rate limiting, and async-safe implementations with configurable client identification functions. - -### Error Handling Middleware - -Consistent error handling and recovery is critical for robust MCP servers. FastMCP provides comprehensive error handling middleware at `fastmcp.server.middleware.error_handling`. - -Here's an example of how it works: - -```python -import logging -from fastmcp.server.middleware import Middleware, MiddlewareContext - -class SimpleErrorHandlingMiddleware(Middleware): - def __init__(self): - self.logger = logging.getLogger("errors") - self.error_counts = {} - - async def on_message(self, context: MiddlewareContext, call_next): - try: - return await call_next(context) - except Exception as error: - # Log the error and track statistics - error_key = f"{type(error).__name__}:{context.method}" - self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1 - - self.logger.error(f"Error in {context.method}: {type(error).__name__}: {error}") - raise -``` - -To use the full versions with advanced features: - -```python -from fastmcp.server.middleware.error_handling import ( - ErrorHandlingMiddleware, - RetryMiddleware -) - -# Comprehensive error logging and transformation -mcp.add_middleware(ErrorHandlingMiddleware( - include_traceback=True, - transform_errors=True, - error_callback=my_error_callback -)) - -# Automatic retry with exponential backoff -mcp.add_middleware(RetryMiddleware( - max_retries=3, - retry_exceptions=(ConnectionError, TimeoutError) -)) -``` - -The built-in versions include error transformation, custom callbacks, configurable retry logic, and proper MCP error formatting. - -### Combining Middleware - -These middleware work together seamlessly: +`ToolInjectionMiddleware` dynamically injects tools during request processing. `PromptToolMiddleware` and `ResourceToolMiddleware` provide compatibility layers for clients that cannot list or access prompts and resources directly—they expose those capabilities as tools. ```python from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.server.middleware.tool_injection import ToolInjectionMiddleware + +def my_tool_fn(a: int, b: int) -> int: + return a + b + +my_tool = Tool.from_function(fn=my_tool_fn, name="my_tool") +mcp.add_middleware(ToolInjectionMiddleware(tools=[my_tool])) +``` + +### Combining Middleware + +Order matters. Place middleware that should run first (on the way in) earliest: + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware +from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware from fastmcp.server.middleware.timing import TimingMiddleware from fastmcp.server.middleware.logging import LoggingMiddleware -from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware -from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware mcp = FastMCP("Production Server") -# Add middleware in logical order -mcp.add_middleware(ErrorHandlingMiddleware()) # Handle errors first +mcp.add_middleware(ErrorHandlingMiddleware()) # Catch all errors mcp.add_middleware(RateLimitingMiddleware(max_requests_per_second=50)) -mcp.add_middleware(TimingMiddleware()) # Time actual execution -mcp.add_middleware(LoggingMiddleware()) # Log everything +mcp.add_middleware(TimingMiddleware()) +mcp.add_middleware(LoggingMiddleware()) @mcp.tool def my_tool(data: str) -> str: return f"Processed: {data}" ``` -This configuration provides comprehensive monitoring, protection, and observability for your MCP server. +## Custom Middleware -### Custom Middleware Example +When the built-in middleware doesn't fit your needs—custom authentication schemes, domain-specific logging, or request transformation—subclass `Middleware` and override the hooks you need. -You can also create custom middleware by extending the base class: +```python +from fastmcp import FastMCP +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class CustomMiddleware(Middleware): + async def on_request(self, context: MiddlewareContext, call_next): + # Pre-processing + print(f"→ {context.method}") + + result = await call_next(context) + + # Post-processing + print(f"← {context.method}") + return result + +mcp = FastMCP("MyServer") +mcp.add_middleware(CustomMiddleware()) +``` + +Override only the hooks relevant to your use case. Unoverridden hooks pass through automatically. + +### Denying Requests + +Raise the appropriate error type to stop processing and return an error to the client. + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.exceptions import ToolError + +class AuthMiddleware(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + tool_name = context.message.name + + if tool_name in ["delete_all", "admin_config"]: + raise ToolError("Access denied: requires admin privileges") + + return await call_next(context) +``` + +| Operation | Error Type | +|-----------|------------| +| Tool calls | `ToolError` | +| Resource reads | `ResourceError` | +| Prompt retrieval | `PromptError` | +| General requests | `McpError` | + +Do not return error values or skip `call_next()` to indicate errors—raise exceptions for proper error propagation. + +### Modifying Requests + +Change the message before passing it down the chain. ```python from fastmcp.server.middleware import Middleware, MiddlewareContext -class CustomHeaderMiddleware(Middleware): - async def on_request(self, context: MiddlewareContext, call_next): - # Add custom logic here - print(f"Processing {context.method}") - - result = await call_next(context) - - print(f"Completed {context.method}") - return result +class InputSanitizer(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + if context.message.name == "search": + # Normalize search query + query = context.message.arguments.get("query", "") + context.message.arguments["query"] = query.strip().lower() -mcp.add_middleware(CustomHeaderMiddleware()) + return await call_next(context) +``` + +### Modifying Responses + +Transform results after the handler executes. + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class ResponseEnricher(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + result = await call_next(context) + + if context.message.name == "get_data" and result.structured_content: + result.structured_content["processed_by"] = "enricher" + + return result +``` + +For more complex tool transformations, consider [Transforms](/servers/providers/transforms) instead. + +### Filtering Lists + +List operations return FastMCP objects that you can filter before they reach the client. When filtering list results, also block execution in the corresponding operation hook to maintain consistency: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.exceptions import ToolError + +class PrivateToolFilter(Middleware): + async def on_list_tools(self, context: MiddlewareContext, call_next): + tools = await call_next(context) + return [tool for tool in tools if "private" not in tool.tags] + + async def on_call_tool(self, context: MiddlewareContext, call_next): + if context.fastmcp_context: + tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name) + if "private" in tool.tags: + raise ToolError("Tool not found") + + return await call_next(context) +``` + +### Accessing Component Metadata + +During execution hooks, component metadata (like tags) isn't directly available. Look up the component through the server: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.exceptions import ToolError + +class TagBasedAuth(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + if context.fastmcp_context: + try: + tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name) + + if "requires-auth" in tool.tags: + # Check authentication here + pass + + except Exception: + pass # Let execution handle missing tools + + return await call_next(context) +``` + +The same pattern works for resources and prompts: + +```python +resource = await context.fastmcp_context.fastmcp.get_resource(context.message.uri) +prompt = await context.fastmcp_context.fastmcp.get_prompt(context.message.name) +``` + +### Storing State + + + +Middleware can store state that tools access later through the FastMCP context. + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class UserMiddleware(Middleware): + async def on_request(self, context: MiddlewareContext, call_next): + # Extract user from headers (HTTP transport) + from fastmcp.server.dependencies import get_http_headers + headers = get_http_headers() or {} + user_id = headers.get("x-user-id", "anonymous") + + # Store for tools to access + if context.fastmcp_context: + context.fastmcp_context.set_state("user_id", user_id) + + return await call_next(context) +``` + +Tools retrieve the state: + +```python +from fastmcp import FastMCP, Context + +mcp = FastMCP("MyServer") + +@mcp.tool +def get_user_data(ctx: Context) -> str: + user_id = ctx.get_state("user_id") + return f"Data for user: {user_id}" +``` + +See [Context State Management](/servers/context#state-management) for details. + +### Constructor Parameters + +Initialize middleware with configuration: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class ConfigurableMiddleware(Middleware): + def __init__(self, api_key: str, rate_limit: int = 100): + self.api_key = api_key + self.rate_limit = rate_limit + self.request_counts = {} + + async def on_request(self, context: MiddlewareContext, call_next): + # Use self.api_key, self.rate_limit, etc. + return await call_next(context) + +mcp.add_middleware(ConfigurableMiddleware( + api_key="secret", + rate_limit=50 +)) +``` + +### Error Handling in Custom Middleware + +Wrap `call_next()` to handle errors from downstream middleware and handlers. + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class ErrorLogger(Middleware): + async def on_request(self, context: MiddlewareContext, call_next): + try: + return await call_next(context) + except Exception as e: + print(f"Error in {context.method}: {type(e).__name__}: {e}") + raise # Re-raise to let error propagate +``` + +Catching and not re-raising suppresses the error entirely. Usually you want to log and re-raise. + +### Complete Example + +Authentication middleware checking API keys for specific tools: + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.server.dependencies import get_http_headers +from fastmcp.exceptions import ToolError + +class ApiKeyAuth(Middleware): + def __init__(self, valid_keys: set[str], protected_tools: set[str]): + self.valid_keys = valid_keys + self.protected_tools = protected_tools + + async def on_call_tool(self, context: MiddlewareContext, call_next): + tool_name = context.message.name + + if tool_name not in self.protected_tools: + return await call_next(context) + + headers = get_http_headers() or {} + api_key = headers.get("x-api-key") + + if api_key not in self.valid_keys: + raise ToolError(f"Invalid API key for protected tool: {tool_name}") + + return await call_next(context) + +mcp = FastMCP("Secure Server") +mcp.add_middleware(ApiKeyAuth( + valid_keys={"key-1", "key-2"}, + protected_tools={"delete_user", "admin_panel"} +)) + +@mcp.tool +def delete_user(user_id: str) -> str: + return f"Deleted user {user_id}" + +@mcp.tool +def get_user(user_id: str) -> str: + return f"User {user_id}" # Not protected ``` diff --git a/docs/servers/pagination.mdx b/docs/servers/pagination.mdx index 8c1002c7c..97ad2c7a2 100644 --- a/docs/servers/pagination.mdx +++ b/docs/servers/pagination.mdx @@ -3,6 +3,7 @@ title: Pagination sidebarTitle: Pagination description: Control how servers return large lists of components to clients. icon: page +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/progress.mdx b/docs/servers/progress.mdx index b5625864b..9600a05ce 100644 --- a/docs/servers/progress.mdx +++ b/docs/servers/progress.mdx @@ -7,22 +7,13 @@ icon: chart-line import { VersionBadge } from '/snippets/version-badge.mdx' -Progress reporting allows MCP tools to notify clients about the progress of long-running operations. This enables clients to display progress indicators and provide better user experience during time-consuming tasks. +Progress reporting allows MCP tools to notify clients about the progress of long-running operations. Clients can display progress indicators and provide better user experience during time-consuming tasks. -## Why Use Progress Reporting? +## Basic Usage -Progress reporting is valuable for: +Use `ctx.report_progress()` to send progress updates to the client. The method accepts a `progress` value representing how much work is complete, and an optional `total` representing the full scope of work. -- **User experience**: Keep users informed about long-running operations -- **Progress indicators**: Enable clients to show progress bars or percentages -- **Timeout prevention**: Demonstrate that operations are actively progressing -- **Debugging**: Track execution progress for performance analysis - -### Basic Usage - -Use `ctx.report_progress()` to send progress updates to the client: - -```python {14, 21} +```python from fastmcp import FastMCP, Context import asyncio @@ -33,157 +24,28 @@ async def process_items(items: list[str], ctx: Context) -> dict: """Process a list of items with progress updates.""" total = len(items) results = [] - + for i, item in enumerate(items): - # Report progress as we process each item await ctx.report_progress(progress=i, total=total) - - # Simulate processing time await asyncio.sleep(0.1) results.append(item.upper()) - - # Report 100% completion + await ctx.report_progress(progress=total, total=total) - return {"processed": len(results), "results": results} ``` -## Method Signature - - - - Report progress to the client for long-running operations - - - - Current progress value (e.g., 24, 0.75, 1500) - - - - Optional total value (e.g., 100, 1.0, 2000). When provided, clients may interpret this as enabling percentage calculation. - - - - - ## Progress Patterns -### Percentage-Based Progress - -Report progress as a percentage (0-100): - -```python {13-14} -@mcp.tool -async def download_file(url: str, ctx: Context) -> str: - """Download a file with percentage progress.""" - total_size = 1000 # KB - downloaded = 0 - - while downloaded < total_size: - # Download chunk - chunk_size = min(50, total_size - downloaded) - downloaded += chunk_size - - # Report percentage progress - percentage = (downloaded / total_size) * 100 - await ctx.report_progress(progress=percentage, total=100) - - await asyncio.sleep(0.1) # Simulate download time - - return f"Downloaded file from {url}" -``` - -### Absolute Progress - -Report progress with absolute values: - -```python {10} -@mcp.tool -async def backup_database(ctx: Context) -> str: - """Backup database tables with absolute progress.""" - tables = ["users", "orders", "products", "inventory", "logs"] - - for i, table in enumerate(tables): - await ctx.info(f"Backing up table: {table}") - - # Report absolute progress - await ctx.report_progress(progress=i + 1, total=len(tables)) - - # Simulate backup time - await asyncio.sleep(0.5) - - return "Database backup completed" -``` - -### Indeterminate Progress - -Report progress without a known total for operations where the endpoint is unknown: - -```python {11} -@mcp.tool -async def scan_directory(directory: str, ctx: Context) -> dict: - """Scan directory with indeterminate progress.""" - files_found = 0 - - # Simulate directory scanning - for i in range(10): # Unknown number of files - files_found += 1 - - # Report progress without total for indeterminate operations - await ctx.report_progress(progress=files_found) - - await asyncio.sleep(0.2) - - return {"files_found": files_found, "directory": directory} -``` - -### Multi-Stage Operations - -Break complex operations into stages with progress for each: - -```python -@mcp.tool -async def data_migration(source: str, destination: str, ctx: Context) -> str: - """Migrate data with multi-stage progress reporting.""" - - # Stage 1: Validation (0-25%) - await ctx.info("Validating source data") - for i in range(5): - await ctx.report_progress(progress=i * 5, total=100) - await asyncio.sleep(0.1) - - # Stage 2: Export (25-60%) - await ctx.info("Exporting data from source") - for i in range(7): - progress = 25 + (i * 5) - await ctx.report_progress(progress=progress, total=100) - await asyncio.sleep(0.1) - - # Stage 3: Transform (60-80%) - await ctx.info("Transforming data format") - for i in range(4): - progress = 60 + (i * 5) - await ctx.report_progress(progress=progress, total=100) - await asyncio.sleep(0.1) - - # Stage 4: Import (80-100%) - await ctx.info("Importing to destination") - for i in range(4): - progress = 80 + (i * 5) - await ctx.report_progress(progress=progress, total=100) - await asyncio.sleep(0.1) - - # Final completion - await ctx.report_progress(progress=100, total=100) - - return f"Migration from {source} to {destination} completed" -``` +| Pattern | Description | Example | +|---------|-------------|---------| +| Percentage | Progress as 0-100 percentage | `progress=75, total=100` | +| Absolute | Completed items of a known count | `progress=3, total=10` | +| Indeterminate | Progress without known endpoint | `progress=files_found` (no total) | +For multi-stage operations, map each stage to a portion of the total progress range. A four-stage operation might allocate 0-25% to validation, 25-60% to export, 60-80% to transform, and 80-100% to import. ## Client Requirements -Progress reporting requires clients to support progress handling: +Progress reporting requires clients to support progress handling. Clients must send a `progressToken` in the initial request to receive progress updates. If no progress token is provided, progress calls have no effect (they don't error). -- Clients must send a `progressToken` in the initial request to receive progress updates -- If no progress token is provided, progress calls will have no effect (they won't error) -- See [Client Progress](/clients/progress) for details on implementing client-side progress handling +See [Client Progress](/clients/progress) for details on implementing client-side progress handling. diff --git a/docs/servers/providers/custom.mdx b/docs/servers/providers/custom.mdx index 248e32d20..f5673c683 100644 --- a/docs/servers/providers/custom.mdx +++ b/docs/servers/providers/custom.mdx @@ -3,6 +3,7 @@ title: Custom Providers sidebarTitle: Custom description: Build providers that source components from any data source icon: code +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/providers/filesystem.mdx b/docs/servers/providers/filesystem.mdx index 92c318773..a5b798faa 100644 --- a/docs/servers/providers/filesystem.mdx +++ b/docs/servers/providers/filesystem.mdx @@ -3,6 +3,7 @@ title: Filesystem Provider sidebarTitle: Filesystem description: Automatic component discovery from Python files icon: folder-tree +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/providers/local.mdx b/docs/servers/providers/local.mdx index 7cdefa5e6..ae69d9063 100644 --- a/docs/servers/providers/local.mdx +++ b/docs/servers/providers/local.mdx @@ -3,6 +3,7 @@ title: Local Provider sidebarTitle: Local description: The default provider for decorator-registered components icon: house +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/providers/namespace.mdx b/docs/servers/providers/namespace.mdx new file mode 100644 index 000000000..715bebd39 --- /dev/null +++ b/docs/servers/providers/namespace.mdx @@ -0,0 +1,64 @@ +--- +title: Namespace Transform +sidebarTitle: Namespace +description: Prefix component names to prevent conflicts +icon: tag +tag: NEW +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +The `Namespace` transform prefixes all component names, preventing conflicts when composing multiple servers. + +Tools and prompts receive an underscore-separated prefix. Resources and templates receive a path-segment prefix in their URIs. + +| Component | Original | With `Namespace("api")` | +|-----------|----------|-------------------------| +| Tool | `my_tool` | `api_my_tool` | +| Prompt | `my_prompt` | `api_my_prompt` | +| Resource | `data://info` | `data://api/info` | +| Template | `data://{id}` | `data://api/{id}` | + +The most common use is through the `mount()` method's `namespace` parameter. + +```python +from fastmcp import FastMCP + +weather = FastMCP("Weather") +calendar = FastMCP("Calendar") + +@weather.tool +def get_data() -> str: + return "Weather data" + +@calendar.tool +def get_data() -> str: + return "Calendar data" + +# Without namespacing, these would conflict +main = FastMCP("Main") +main.mount(weather, namespace="weather") +main.mount(calendar, namespace="calendar") + +# Clients see: weather_get_data, calendar_get_data +``` + +You can also apply namespacing directly using the `Namespace` transform. + +```python +from fastmcp import FastMCP +from fastmcp.server.transforms import Namespace + +mcp = FastMCP("Server") + +@mcp.tool +def greet(name: str) -> str: + return f"Hello, {name}!" + +# Namespace all components +mcp.add_transform(Namespace("api")) + +# Tool is now: api_greet +``` diff --git a/docs/servers/providers/overview.mdx b/docs/servers/providers/overview.mdx index 07b9e8bce..3962e6e2a 100644 --- a/docs/servers/providers/overview.mdx +++ b/docs/servers/providers/overview.mdx @@ -3,6 +3,7 @@ title: Providers sidebarTitle: Overview description: How FastMCP sources tools, resources, and prompts icon: layer-group +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/providers/prompts-as-tools.mdx b/docs/servers/providers/prompts-as-tools.mdx index d7c504fc7..f0b499fc8 100644 --- a/docs/servers/providers/prompts-as-tools.mdx +++ b/docs/servers/providers/prompts-as-tools.mdx @@ -3,6 +3,7 @@ title: Prompts as Tools sidebarTitle: Prompts as Tools description: Expose prompts to tool-only clients icon: message-lines +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/providers/proxy.mdx b/docs/servers/providers/proxy.mdx index b9b04bbdc..805cb34be 100644 --- a/docs/servers/providers/proxy.mdx +++ b/docs/servers/providers/proxy.mdx @@ -1,7 +1,7 @@ --- -title: Remote Proxies -sidebarTitle: Proxying -description: Expose remote MCP servers through your local server +title: MCP Proxy Provider +sidebarTitle: MCP Proxy +description: Source components from other MCP servers icon: arrows-retweet --- @@ -9,25 +9,25 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -Proxying lets you expose a remote MCP server's tools, resources, and prompts through your local server. Under the hood, FastMCP uses `ProxyProvider` (v3.0.0+) to source components from a client connection. +The Proxy Provider sources components from another MCP server through a client connection. This lets you expose any MCP server's tools, resources, and prompts through your own server, whether the source is local or accessed over the network. -## Why Proxy Servers +## Why Use Proxy Provider -Proxying lets you: +The Proxy Provider enables: -- **Bridge transports**: Expose a remote SSE server via local stdio -- **Aggregate servers**: Combine multiple remote servers into one -- **Add security**: Act as a controlled gateway to backend servers -- **Simplify access**: Single endpoint even if backends change +- **Bridge transports**: Make an HTTP server available via stdio, or vice versa +- **Aggregate servers**: Combine multiple source servers into one unified server +- **Add security**: Act as a controlled gateway with authentication and authorization +- **Simplify access**: Provide a stable endpoint even if backend servers change ```mermaid sequenceDiagram participant Client as Your Client participant Proxy as FastMCP Proxy - participant Backend as Remote Server + participant Backend as Source Server Client->>Proxy: MCP Request (stdio) - Proxy->>Backend: MCP Request (HTTP) + Proxy->>Backend: MCP Request (HTTP/stdio/SSE) Backend-->>Proxy: MCP Response Proxy-->>Client: MCP Response ``` @@ -59,17 +59,17 @@ To mount a proxy inside another FastMCP server, see [Mounting External Servers]( ## Transport Bridging -A common use case is bridging transports - making a remote server available locally: +A common use case is bridging transports between servers: ```python from fastmcp.server import create_proxy -# Bridge remote HTTP to local stdio -remote_proxy = create_proxy("http://example.com/mcp/sse", name="Remote-to-Local") +# Bridge HTTP server to local stdio +http_proxy = create_proxy("http://example.com/mcp/sse", name="HTTP-to-stdio") # Run locally via stdio for Claude Desktop if __name__ == "__main__": - remote_proxy.run() # Defaults to stdio + http_proxy.run() # Defaults to stdio ``` Or expose a local server via HTTP: @@ -78,7 +78,7 @@ Or expose a local server via HTTP: from fastmcp.server import create_proxy # Bridge local server to HTTP -local_proxy = create_proxy("local_server.py", name="Local-to-HTTP") +local_proxy = create_proxy("local_server.py", name="stdio-to-HTTP") if __name__ == "__main__": local_proxy.run(transport="http", host="0.0.0.0", port=8080) @@ -280,7 +280,7 @@ This gives you full control over session creation and reuse strategies. ### Adding Proxied Components to Existing Server -Mount a proxy to add remote components to an existing server: +Mount a proxy to add components from another server: ```python from fastmcp import FastMCP @@ -293,9 +293,9 @@ server = FastMCP("My Server") def local_tool() -> str: return "Local result" -# Mount proxied tools from remote server -remote = create_proxy("http://remote-server/mcp") -server.mount(remote) +# Mount proxied tools from another server +external = create_proxy("http://external-server/mcp") +server.mount(external) # Now server has both local and proxied tools ``` diff --git a/docs/servers/providers/resources-as-tools.mdx b/docs/servers/providers/resources-as-tools.mdx index 3d32ba5fd..8c7e8bbed 100644 --- a/docs/servers/providers/resources-as-tools.mdx +++ b/docs/servers/providers/resources-as-tools.mdx @@ -3,6 +3,7 @@ title: Resources as Tools sidebarTitle: Resources as Tools description: Expose resources to tool-only clients icon: toolbox +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' diff --git a/docs/servers/providers/skills.mdx b/docs/servers/providers/skills.mdx index 548259c27..3c810b3f2 100644 --- a/docs/servers/providers/skills.mdx +++ b/docs/servers/providers/skills.mdx @@ -3,6 +3,7 @@ title: Skills Provider sidebarTitle: Skills description: Expose agent skills as MCP resources icon: wand-magic-sparkles +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' @@ -247,45 +248,6 @@ With `reload=True`, the provider re-discovers skills on each `list_resources()` Reload mode adds overhead to every request. Use it during development when you're actively editing skills, but disable it in production. -## Complete Example - -This example creates a server that exposes Claude Code skills, then uses a client to discover and read them. - -```python -import asyncio -import json - -from fastmcp import Client, FastMCP -from fastmcp.server.providers.skills import ClaudeSkillsProvider - -async def main(): - # Create server with Claude skills - mcp = FastMCP("Skills Demo") - mcp.add_provider(ClaudeSkillsProvider(reload=True)) - - async with Client(mcp) as client: - # List available skill resources - resources = await client.list_resources() - print("Available skills:") - for r in resources: - if r.uri.path and r.uri.path.endswith("SKILL.md"): - print(f" - {r.name}: {r.description}") - - # Read a specific skill's manifest - manifest_result = await client.read_resource("skill://pdf-processing/_manifest") - manifest = json.loads(manifest_result[0].text) - print(f"\nFiles in pdf-processing skill:") - for f in manifest["files"]: - print(f" - {f['path']} ({f['size']} bytes)") - - # Read the main skill file - skill_result = await client.read_resource("skill://pdf-processing/SKILL.md") - print(f"\nSkill content:\n{skill_result[0].text[:500]}...") - -if __name__ == "__main__": - asyncio.run(main()) -``` - ## Client Utilities FastMCP provides utilities for downloading skills from any MCP server that exposes them. These are standalone functions in `fastmcp.utilities.skills`. diff --git a/docs/servers/providers/tool-transformation.mdx b/docs/servers/providers/tool-transformation.mdx new file mode 100644 index 000000000..ec619d46f --- /dev/null +++ b/docs/servers/providers/tool-transformation.mdx @@ -0,0 +1,192 @@ +--- +title: Tool Transformation +sidebarTitle: Tool Transformation +description: Modify tool schemas - rename, reshape arguments, and customize behavior +icon: wrench +tag: NEW +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +Tool transformation lets you modify tool schemas - renaming tools, changing descriptions, adjusting tags, and reshaping argument schemas. FastMCP provides two mechanisms that share the same configuration options but differ in timing. + +**Deferred transformation** with `ToolTransform` applies modifications when tools flow through a transform chain. Use this for tools from mounted servers, proxies, or other providers where you don't control the source directly. + +**Immediate transformation** with `Tool.from_tool()` creates a modified tool object right away. Use this when you have direct access to a tool and want to transform it before registration. + +## ToolTransform + +The `ToolTransform` class is a transform that modifies tools as they flow through a provider. Provide a dictionary mapping original tool names to their transformation configuration. + +```python +from fastmcp import FastMCP +from fastmcp.server.transforms import ToolTransform +from fastmcp.tools.tool_transform import ToolTransformConfig + +mcp = FastMCP("Server") + +@mcp.tool +def verbose_internal_data_fetcher(query: str) -> str: + """Fetches data from the internal database.""" + return f"Results for: {query}" + +# Rename the tool to something simpler +mcp.add_transform(ToolTransform({ + "verbose_internal_data_fetcher": ToolTransformConfig( + name="search", + description="Search the database.", + ) +})) + +# Clients see "search" with the cleaner description +``` + +`ToolTransform` is useful when you want to modify tools from mounted or proxied servers without changing the original source. + +## Tool.from_tool() + +Use `Tool.from_tool()` when you have the tool object and want to create a transformed version for registration. + +```python +from fastmcp import FastMCP +from fastmcp.tools import Tool, tool +from fastmcp.tools.tool_transform import ArgTransform + +# Create a tool without registering it +@tool +def search(q: str, limit: int = 10) -> list[str]: + """Search for items.""" + return [f"Result {i} for {q}" for i in range(limit)] + +# Transform it before registration +better_search = Tool.from_tool( + search, + name="find_items", + description="Find items matching your search query.", + transform_args={ + "q": ArgTransform( + name="query", + description="The search terms to look for.", + ), + }, +) + +mcp = FastMCP("Server") +mcp.add_tool(better_search) +``` + +The standalone `@tool` decorator (from `fastmcp.tools`) creates a Tool object without registering it to any server. This separates creation from registration, letting you transform tools before deciding where they go. + +## Modification Options + +Both mechanisms support the same modifications. + +**Tool-level options:** + +| Option | Description | +|--------|-------------| +| `name` | New name for the tool | +| `description` | New description | +| `title` | Human-readable title | +| `tags` | Set of tags for categorization | +| `annotations` | MCP ToolAnnotations | +| `meta` | Custom metadata dictionary | + +**Argument-level options** (via `ArgTransform` or `ArgTransformConfig`): + +| Option | Description | +|--------|-------------| +| `name` | Rename the argument | +| `description` | New description for the argument | +| `default` | New default value | +| `default_factory` | Callable that generates a default (requires `hide=True`) | +| `hide` | Remove from client-visible schema | +| `required` | Make an optional argument required | +| `type` | Change the argument's type | +| `examples` | Example values for the argument | + +## Hiding Arguments + +Hide arguments to simplify the interface or inject values the client shouldn't control. + +```python +from fastmcp.tools.tool_transform import ArgTransform + +# Hide with a constant value +transform_args = { + "api_key": ArgTransform(hide=True, default="secret-key"), +} + +# Hide with a dynamic value +import uuid +transform_args = { + "request_id": ArgTransform(hide=True, default_factory=lambda: str(uuid.uuid4())), +} +``` + +Hidden arguments disappear from the tool's schema. The client never sees them, but the underlying function receives the configured value. + + +`default_factory` requires `hide=True`. Visible arguments need static defaults that can be represented in JSON Schema. + + +## Renaming Arguments + +Rename arguments to make them more intuitive for LLMs or match your API conventions. + +```python +from fastmcp.tools import Tool, tool +from fastmcp.tools.tool_transform import ArgTransform + +@tool +def search(q: str, n: int = 10) -> list[str]: + """Search for items.""" + return [] + +better_search = Tool.from_tool( + search, + transform_args={ + "q": ArgTransform(name="query", description="Search terms"), + "n": ArgTransform(name="max_results", description="Maximum results to return"), + }, +) +``` + +## Custom Transform Functions + +For advanced scenarios, provide a `transform_fn` that intercepts tool execution. The function can validate inputs, modify outputs, or add custom logic while still calling the original tool via `forward()`. + +```python +from fastmcp import FastMCP +from fastmcp.tools import Tool, tool +from fastmcp.tools.tool_transform import forward, ArgTransform + +@tool +def divide(a: float, b: float) -> float: + """Divide a by b.""" + return a / b + +async def safe_divide(numerator: float, denominator: float) -> float: + if denominator == 0: + raise ValueError("Cannot divide by zero") + return await forward(numerator=numerator, denominator=denominator) + +safe_division = Tool.from_tool( + divide, + name="safe_divide", + transform_fn=safe_divide, + transform_args={ + "a": ArgTransform(name="numerator"), + "b": ArgTransform(name="denominator"), + }, +) + +mcp = FastMCP("Server") +mcp.add_tool(safe_division) +``` + +The `forward()` function handles argument mapping automatically. Call it with the transformed argument names, and it maps them back to the original function's parameters. + +For direct access to the original function without mapping, use `forward_raw()` with the original parameter names. diff --git a/docs/servers/providers/transforms.mdx b/docs/servers/providers/transforms.mdx index 27fdb1778..1ed0bac44 100644 --- a/docs/servers/providers/transforms.mdx +++ b/docs/servers/providers/transforms.mdx @@ -1,8 +1,9 @@ --- -title: Transforms -sidebarTitle: Transforms +title: Transforms Overview +sidebarTitle: Overview description: Modify components as they flow through your server icon: wand-magic-sparkles +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' @@ -21,243 +22,15 @@ Provider → [Transform A] → [Transform B] → Client When listing components, transforms receive sequences and return transformed sequences—a pure function pattern. When getting a specific component by name, transforms use a middleware pattern with `call_next`, working in reverse: mapping the client's requested name back to the original, then transforming the result. -## Namespace +## Built-in Transforms -The `Namespace` transform prefixes all component names, preventing conflicts when composing multiple servers. +FastMCP provides several transforms for common use cases: -Tools and prompts receive an underscore-separated prefix. Resources and templates receive a path-segment prefix in their URIs. - -| Component | Original | With `Namespace("api")` | -|-----------|----------|-------------------------| -| Tool | `my_tool` | `api_my_tool` | -| Prompt | `my_prompt` | `api_my_prompt` | -| Resource | `data://info` | `data://api/info` | -| Template | `data://{id}` | `data://api/{id}` | - -The most common use is through the `mount()` method's `namespace` parameter. - -```python -from fastmcp import FastMCP - -weather = FastMCP("Weather") -calendar = FastMCP("Calendar") - -@weather.tool -def get_data() -> str: - return "Weather data" - -@calendar.tool -def get_data() -> str: - return "Calendar data" - -# Without namespacing, these would conflict -main = FastMCP("Main") -main.mount(weather, namespace="weather") -main.mount(calendar, namespace="calendar") - -# Clients see: weather_get_data, calendar_get_data -``` - -You can also apply namespacing directly using the `Namespace` transform. - -```python -from fastmcp import FastMCP -from fastmcp.server.transforms import Namespace - -mcp = FastMCP("Server") - -@mcp.tool -def greet(name: str) -> str: - return f"Hello, {name}!" - -# Namespace all components -mcp.add_transform(Namespace("api")) - -# Tool is now: api_greet -``` - -## Tool Transformation - -Tool transformation lets you modify tool schemas - renaming tools, changing descriptions, adjusting tags, and reshaping argument schemas. FastMCP provides two mechanisms that share the same configuration options but differ in timing. - -**Deferred transformation** with `ToolTransform` applies modifications when tools flow through a transform chain. Use this for tools from mounted servers, proxies, or other providers where you don't control the source directly. - -**Immediate transformation** with `Tool.from_tool()` creates a modified tool object right away. Use this when you have direct access to a tool and want to transform it before registration. - -### ToolTransform - -The `ToolTransform` class is a transform that modifies tools as they flow through a provider. Provide a dictionary mapping original tool names to their transformation configuration. - -```python -from fastmcp import FastMCP -from fastmcp.server.transforms import ToolTransform -from fastmcp.tools.tool_transform import ToolTransformConfig - -mcp = FastMCP("Server") - -@mcp.tool -def verbose_internal_data_fetcher(query: str) -> str: - """Fetches data from the internal database.""" - return f"Results for: {query}" - -# Rename the tool to something simpler -mcp.add_transform(ToolTransform({ - "verbose_internal_data_fetcher": ToolTransformConfig( - name="search", - description="Search the database.", - ) -})) - -# Clients see "search" with the cleaner description -``` - -`ToolTransform` is useful when you want to modify tools from mounted or proxied servers without changing the original source. - -### Tool.from_tool() - -Use `Tool.from_tool()` when you have the tool object and want to create a transformed version for registration. - -```python -from fastmcp import FastMCP -from fastmcp.tools import Tool, tool -from fastmcp.tools.tool_transform import ArgTransform - -# Create a tool without registering it -@tool -def search(q: str, limit: int = 10) -> list[str]: - """Search for items.""" - return [f"Result {i} for {q}" for i in range(limit)] - -# Transform it before registration -better_search = Tool.from_tool( - search, - name="find_items", - description="Find items matching your search query.", - transform_args={ - "q": ArgTransform( - name="query", - description="The search terms to look for.", - ), - }, -) - -mcp = FastMCP("Server") -mcp.add_tool(better_search) -``` - -The standalone `@tool` decorator (from `fastmcp.tools`) creates a Tool object without registering it to any server. This separates creation from registration, letting you transform tools before deciding where they go. - -### Modification Options - -Both mechanisms support the same modifications. - -**Tool-level options:** - -| Option | Description | -|--------|-------------| -| `name` | New name for the tool | -| `description` | New description | -| `title` | Human-readable title | -| `tags` | Set of tags for categorization | -| `annotations` | MCP ToolAnnotations | -| `meta` | Custom metadata dictionary | - -**Argument-level options** (via `ArgTransform` or `ArgTransformConfig`): - -| Option | Description | -|--------|-------------| -| `name` | Rename the argument | -| `description` | New description for the argument | -| `default` | New default value | -| `default_factory` | Callable that generates a default (requires `hide=True`) | -| `hide` | Remove from client-visible schema | -| `required` | Make an optional argument required | -| `type` | Change the argument's type | -| `examples` | Example values for the argument | - -### Hiding Arguments - -Hide arguments to simplify the interface or inject values the client shouldn't control. - -```python -from fastmcp.tools.tool_transform import ArgTransform - -# Hide with a constant value -transform_args = { - "api_key": ArgTransform(hide=True, default="secret-key"), -} - -# Hide with a dynamic value -import uuid -transform_args = { - "request_id": ArgTransform(hide=True, default_factory=lambda: str(uuid.uuid4())), -} -``` - -Hidden arguments disappear from the tool's schema. The client never sees them, but the underlying function receives the configured value. - - -`default_factory` requires `hide=True`. Visible arguments need static defaults that can be represented in JSON Schema. - - -### Renaming Arguments - -Rename arguments to make them more intuitive for LLMs or match your API conventions. - -```python -from fastmcp.tools import Tool, tool -from fastmcp.tools.tool_transform import ArgTransform - -@tool -def search(q: str, n: int = 10) -> list[str]: - """Search for items.""" - return [] - -better_search = Tool.from_tool( - search, - transform_args={ - "q": ArgTransform(name="query", description="Search terms"), - "n": ArgTransform(name="max_results", description="Maximum results to return"), - }, -) -``` - -### Custom Transform Functions - -For advanced scenarios, provide a `transform_fn` that intercepts tool execution. The function can validate inputs, modify outputs, or add custom logic while still calling the original tool via `forward()`. - -```python -from fastmcp import FastMCP -from fastmcp.tools import Tool, tool -from fastmcp.tools.tool_transform import forward, ArgTransform - -@tool -def divide(a: float, b: float) -> float: - """Divide a by b.""" - return a / b - -async def safe_divide(numerator: float, denominator: float) -> float: - if denominator == 0: - raise ValueError("Cannot divide by zero") - return await forward(numerator=numerator, denominator=denominator) - -safe_division = Tool.from_tool( - divide, - name="safe_divide", - transform_fn=safe_divide, - transform_args={ - "a": ArgTransform(name="numerator"), - "b": ArgTransform(name="denominator"), - }, -) - -mcp = FastMCP("Server") -mcp.add_tool(safe_division) -``` - -The `forward()` function handles argument mapping automatically. Call it with the transformed argument names, and it maps them back to the original function's parameters. - -For direct access to the original function without mapping, use `forward_raw()` with the original parameter names. +- **[Namespace](/servers/providers/namespace)** - Prefix component names to prevent conflicts when composing servers +- **[Tool Transformation](/servers/providers/tool-transformation)** - Rename tools, modify descriptions, reshape arguments +- **[Enabled](/servers/enabled)** - Control which components are visible at runtime +- **[Resources as Tools](/servers/providers/resources-as-tools)** - Expose resources to tool-only clients +- **[Prompts as Tools](/servers/providers/prompts-as-tools)** - Expose prompts to tool-only clients ## Server vs Provider Transforms diff --git a/docs/servers/sampling.mdx b/docs/servers/sampling.mdx index 841d5b01f..f15d50aa3 100644 --- a/docs/servers/sampling.mdx +++ b/docs/servers/sampling.mdx @@ -1,12 +1,11 @@ --- -title: LLM Sampling +title: Sampling sidebarTitle: Sampling description: Request LLM text generation from the client or a configured provider through the MCP context. icon: robot -tag: NEW --- -import { VersionBadge } from "/snippets/version-badge.mdx"; +import { VersionBadge } from "/snippets/version-badge.mdx" @@ -14,6 +13,417 @@ LLM sampling allows your MCP tools to request text generation from an LLM during By default, sampling requests are routed to the client's LLM. You can also configure a fallback handler to use a specific provider (like OpenAI) when the client doesn't support sampling, or to always use your own LLM regardless of client capabilities. +## Overview + +The simplest use of sampling is passing a prompt string to `ctx.sample()`. The method sends the prompt to the LLM, waits for the complete response, and returns a `SamplingResult`. You can access the generated text through the `.text` attribute. + +```python +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def summarize(content: str, ctx: Context) -> str: + """Generate a summary of the provided content.""" + result = await ctx.sample(f"Please summarize this:\n\n{content}") + return result.text or "" +``` + +The `SamplingResult` also provides `.result` (identical to `.text` for plain text responses) and `.history` containing the full message exchange—useful if you need to continue the conversation or debug the interaction. + +### System Prompts + +System prompts let you establish the LLM's role and behavioral guidelines before it processes your request. This is useful for controlling tone, enforcing constraints, or providing context that shouldn't clutter the user-facing prompt. + +````python +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def generate_code(concept: str, ctx: Context) -> str: + """Generate a Python code example for a concept.""" + result = await ctx.sample( + messages=f"Write a Python example demonstrating '{concept}'.", + system_prompt=( + "You are an expert Python programmer. " + "Provide concise, working code without explanations." + ), + temperature=0.7, + max_tokens=300 + ) + return f"```python\n{result.text}\n```" +```` + +The `temperature` parameter controls randomness—higher values (up to 1.0) produce more varied outputs, while lower values make responses more deterministic. The `max_tokens` parameter limits response length. + +### Model Preferences + +Model preferences let you hint at which LLM the client should use for a request. You can pass a single model name or a list of preferences in priority order. These are hints rather than requirements—the actual model used depends on what the client has available. + +```python +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def technical_analysis(data: str, ctx: Context) -> str: + """Analyze data using a reasoning-focused model.""" + result = await ctx.sample( + messages=f"Analyze this data:\n\n{data}", + model_preferences=["claude-opus-4-5", "gpt-5-2"], + temperature=0.2, + ) + return result.text or "" +``` + +Use model preferences when different tasks benefit from different model characteristics. Creative writing might prefer faster models with higher temperature, while complex analysis might benefit from larger reasoning-focused models. + +### Multi-Turn Conversations + +For requests that need conversational context, construct a list of `SamplingMessage` objects representing the conversation history. Each message has a `role` ("user" or "assistant") and `content` (a `TextContent` object). + +```python +from mcp.types import SamplingMessage, TextContent +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def contextual_analysis(query: str, data: str, ctx: Context) -> str: + """Analyze data with conversational context.""" + messages = [ + SamplingMessage( + role="user", + content=TextContent(type="text", text=f"Here's my data: {data}"), + ), + SamplingMessage( + role="assistant", + content=TextContent(type="text", text="I see the data. What would you like to know?"), + ), + SamplingMessage( + role="user", + content=TextContent(type="text", text=query), + ), + ] + result = await ctx.sample(messages=messages) + return result.text or "" +``` + +The LLM receives the full conversation thread and responds with awareness of the preceding context. + +### Fallback Handlers + +Client support for sampling is optional—some clients may not implement it. To ensure your tools work regardless of client capabilities, configure a `sampling_handler` that sends requests directly to an LLM provider. + +FastMCP provides built-in handlers for [OpenAI and Anthropic APIs](/clients/sampling#built-in-handlers). These handlers support the full sampling API including tools, automatically converting your Python functions to each provider's format. + + +Install handlers with `pip install fastmcp[openai]` or `pip install fastmcp[anthropic]`. + + +```python +from fastmcp import FastMCP +from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler + +server = FastMCP( + name="My Server", + sampling_handler=OpenAISamplingHandler(default_model="gpt-4o-mini"), + sampling_handler_behavior="fallback", +) +``` + +The `sampling_handler_behavior` parameter controls when the handler is used: + +- **`"fallback"`** (default): Use the handler only when the client doesn't support sampling. This lets capable clients use their own LLM while ensuring your tools still work with clients that lack sampling support. +- **`"always"`**: Always use the handler, bypassing the client entirely. Use this when you need guaranteed control over which LLM processes requests—for cost control, compliance requirements, or when specific model characteristics are essential. + +## Structured Output + + + +When you need validated, typed data instead of free-form text, use the `result_type` parameter. FastMCP ensures the LLM returns data matching your type, handling validation and retries automatically. + +The `result_type` parameter accepts Pydantic models, dataclasses, and basic types like `int`, `list[str]`, or `dict[str, int]`. When you specify a result type, FastMCP automatically creates a `final_response` tool that the LLM calls to provide its response. If validation fails, the error is sent back to the LLM for retry. + +```python +from pydantic import BaseModel +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +class SentimentResult(BaseModel): + sentiment: str + confidence: float + reasoning: str + +@mcp.tool +async def analyze_sentiment(text: str, ctx: Context) -> SentimentResult: + """Analyze text sentiment with structured output.""" + result = await ctx.sample( + messages=f"Analyze the sentiment of: {text}", + result_type=SentimentResult, + ) + return result.result # A validated SentimentResult object +``` + +When you call this tool, the LLM returns a structured response that FastMCP validates against your Pydantic model. You access the validated object through `result.result`, while `result.text` contains the JSON representation. + +### Structured Output with Tools + +Combine structured output with tools for agentic workflows that return validated data. The LLM uses your tools to gather information, then returns a response matching your type. + +```python +from pydantic import BaseModel +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +def search(query: str) -> str: + """Search the web for information.""" + return f"Results for: {query}" + +def fetch_url(url: str) -> str: + """Fetch content from a URL.""" + return f"Content from: {url}" + +class ResearchResult(BaseModel): + summary: str + sources: list[str] + confidence: float + +@mcp.tool +async def research(topic: str, ctx: Context) -> ResearchResult: + """Research a topic and return structured findings.""" + result = await ctx.sample( + messages=f"Research: {topic}", + tools=[search, fetch_url], + result_type=ResearchResult, + ) + return result.result +``` + + +Structured output with automatic validation only applies to `sample()`. With `sample_step()`, you must manage structured output yourself. + + +## Tool Use + + + +Sampling with tools enables agentic workflows where the LLM can call functions to gather information before responding. This implements [SEP-1577](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577), allowing the LLM to autonomously orchestrate multi-step operations. + +Pass Python functions to the `tools` parameter, and FastMCP handles the execution loop automatically—calling tools, returning results to the LLM, and continuing until the LLM provides a final response. + +### Defining Tools + +Define regular Python functions with type hints and docstrings. FastMCP extracts the function's name, docstring, and parameter types to create tool schemas that the LLM can understand. + +```python +from fastmcp import FastMCP, Context + +def search(query: str) -> str: + """Search the web for information.""" + return f"Results for: {query}" + +def get_time() -> str: + """Get the current time.""" + from datetime import datetime + return datetime.now().strftime("%H:%M:%S") + +mcp = FastMCP() + +@mcp.tool +async def research(question: str, ctx: Context) -> str: + """Answer questions using available tools.""" + result = await ctx.sample( + messages=question, + tools=[search, get_time], + ) + return result.text or "" +``` + +The LLM sees each function's signature and docstring, using this information to decide when and how to call them. Tool errors are caught and sent back to the LLM, allowing it to recover gracefully. An internal safety limit prevents infinite loops. + +### Custom Tool Definitions + +For custom names or descriptions, use `SamplingTool.from_function()`: + +```python +from fastmcp.server.sampling import SamplingTool + +tool = SamplingTool.from_function( + my_func, + name="custom_name", + description="Custom description" +) + +result = await ctx.sample(messages="...", tools=[tool]) +``` + +### Error Handling + +By default, when a sampling tool raises an exception, the error message (including details) is sent back to the LLM so it can attempt recovery. To prevent sensitive information from leaking to the LLM, use the `mask_error_details` parameter: + +```python +result = await ctx.sample( + messages=question, + tools=[search], + mask_error_details=True, # Generic error messages only +) +``` + +When `mask_error_details=True`, tool errors become generic messages like `"Error executing tool 'search'"` instead of exposing stack traces or internal details. + +To intentionally provide specific error messages to the LLM regardless of masking, raise `ToolError`: + +```python +from fastmcp.exceptions import ToolError + +def search(query: str) -> str: + """Search for information.""" + if not query.strip(): + raise ToolError("Search query cannot be empty") + return f"Results for: {query}" +``` + +`ToolError` messages always pass through to the LLM, making it the escape hatch for errors you want the LLM to see and handle. + +### Client Requirements + + +Sampling with tools requires the client to advertise the `sampling.tools` capability. FastMCP clients do this automatically. For external clients that don't support tool-enabled sampling, configure a fallback handler with `sampling_handler_behavior="always"`. + + +## Advanced Control + + + +While `sample()` handles the tool execution loop automatically, some scenarios require fine-grained control over each step. The `sample_step()` method makes a single LLM call and returns a `SampleStep` containing the response and updated history. + +Unlike `sample()`, `sample_step()` is stateless—it doesn't remember previous calls. You control the conversation by passing the full message history each time. The returned `step.history` includes all messages up through the current response, making it easy to continue the loop. + +Use `sample_step()` when you need to: + +- Inspect tool calls before they execute +- Implement custom termination conditions +- Add logging, metrics, or checkpointing between steps +- Build custom agentic loops with domain-specific logic + +### Basic Loop + +By default, `sample_step()` executes any tool calls and includes the results in the history. Call it in a loop, passing the updated history each time, until a stop condition is met. + +```python +from mcp.types import SamplingMessage +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +def search(query: str) -> str: + return f"Results for: {query}" + +def get_time() -> str: + return "12:00 PM" + +@mcp.tool +async def controlled_agent(question: str, ctx: Context) -> str: + """Agent with manual loop control.""" + messages: list[str | SamplingMessage] = [question] + + while True: + step = await ctx.sample_step( + messages=messages, + tools=[search, get_time], + ) + + if step.is_tool_use: + # Tools already executed (execute_tools=True by default) + for call in step.tool_calls: + print(f"Called tool: {call.name}") + + if not step.is_tool_use: + return step.text or "" + + messages = step.history +``` + +### SampleStep Properties + +Each `SampleStep` provides information about what the LLM returned: + +| Property | Description | +|----------|-------------| +| `step.is_tool_use` | True if the LLM requested tool calls | +| `step.tool_calls` | List of tool calls requested (if any) | +| `step.text` | The text content (if any) | +| `step.history` | All messages exchanged so far | + +The contents of `step.history` depend on `execute_tools`: +- **`execute_tools=True`** (default): Includes tool results, ready for the next iteration +- **`execute_tools=False`**: Includes the assistant's tool request, but you add results yourself + +### Manual Tool Execution + +Set `execute_tools=False` to handle tool execution yourself. When disabled, `step.history` contains the user message and the assistant's response with tool calls—but no tool results. You execute the tools and append the results as a user message. + +```python +from mcp.types import SamplingMessage, ToolResultContent, TextContent +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def research(question: str, ctx: Context) -> str: + """Research with manual tool handling.""" + + def search(query: str) -> str: + return f"Results for: {query}" + + def get_time() -> str: + return "12:00 PM" + + tools = {"search": search, "get_time": get_time} + messages: list[SamplingMessage] = [question] + + while True: + step = await ctx.sample_step( + messages=messages, + tools=list(tools.values()), + execute_tools=False, + ) + + if not step.is_tool_use: + return step.text or "" + + # Execute tools and collect results + tool_results = [] + for call in step.tool_calls: + fn = tools[call.name] + result = fn(**call.input) + tool_results.append( + ToolResultContent( + type="tool_result", + toolUseId=call.id, + content=[TextContent(type="text", text=result)], + ) + ) + + messages = list(step.history) + messages.append(SamplingMessage(role="user", content=tool_results)) +``` + +To report an error to the LLM, set `isError=True` on the tool result: + +```python +tool_result = ToolResultContent( + type="tool_result", + toolUseId=call.id, + content=[TextContent(type="text", text="Permission denied")], + isError=True, +) +``` + ## Method Reference @@ -70,7 +480,25 @@ By default, sampling requests are routed to the client's LLM. You can also confi Make a single LLM sampling call. Use this for fine-grained control over the sampling loop. - Same as `sample()`, plus: + + The prompt or conversation history. + + + + Instructions that establish the LLM's role and behavior. + + + + Controls randomness (0.0 = deterministic, 1.0 = creative). + + + + Maximum tokens to generate. + + + + Functions the LLM can call during sampling. + Controls tool usage: `"auto"`, `"required"`, or `"none"`. @@ -81,9 +509,8 @@ By default, sampling requests are routed to the client's LLM. You can also confi - If True, mask detailed error messages from tool execution. When None (default), uses the global `settings.mask_error_details` value. Tools can raise `ToolError` to bypass masking. + If True, mask detailed error messages from tool execution. - @@ -97,392 +524,3 @@ By default, sampling requests are routed to the client's LLM. You can also confi - -## Basic Sampling - -The simplest use of sampling is passing a prompt string to `ctx.sample()`. The method sends the prompt to the LLM, waits for the complete response, and returns a `SamplingResult`. You can access the generated text through the `.text` attribute. - -```python -from fastmcp import FastMCP, Context - -mcp = FastMCP() - -@mcp.tool -async def summarize(content: str, ctx: Context) -> str: - """Generate a summary of the provided content.""" - result = await ctx.sample(f"Please summarize this:\n\n{content}") - return result.text or "" -``` - -The `SamplingResult` also provides `.result` (identical to `.text` for plain text responses) and `.history` containing the full message exchange—useful if you need to continue the conversation or debug the interaction. - -### System Prompts - -System prompts let you establish the LLM's role and behavioral guidelines before it processes your request. This is useful for controlling tone, enforcing constraints, or providing context that shouldn't clutter the user-facing prompt. - -````python -@mcp.tool -async def generate_code(concept: str, ctx: Context) -> str: - """Generate a Python code example for a concept.""" - result = await ctx.sample( - messages=f"Write a Python example demonstrating '{concept}'.", - system_prompt=( - "You are an expert Python programmer. " - "Provide concise, working code without explanations." - ), - temperature=0.7, - max_tokens=300 - ) - return f"```python\n{result.text}\n```" -```` - -The `temperature` parameter controls randomness—higher values (up to 1.0) produce more varied outputs, while lower values make responses more deterministic. The `max_tokens` parameter limits response length. - -### Model Preferences - -Model preferences let you hint at which LLM the client should use for a request. You can pass a single model name or a list of preferences in priority order. These are hints rather than requirements—the actual model used depends on what the client has available. - -```python -@mcp.tool -async def technical_analysis(data: str, ctx: Context) -> str: - """Analyze data using a reasoning-focused model.""" - result = await ctx.sample( - messages=f"Analyze this data:\n\n{data}", - model_preferences=["claude-opus-4-5", "gpt-5-2"], - temperature=0.2, - ) - return result.text or "" -``` - -Use model preferences when different tasks benefit from different model characteristics. Creative writing might prefer faster models with higher temperature, while complex analysis might benefit from larger reasoning-focused models. - -### Multi-Turn Conversations - -For requests that need conversational context, construct a list of `SamplingMessage` objects representing the conversation history. Each message has a `role` ("user" or "assistant") and `content` (a `TextContent` object). - -```python -from mcp.types import SamplingMessage, TextContent - -@mcp.tool -async def contextual_analysis(query: str, data: str, ctx: Context) -> str: - """Analyze data with conversational context.""" - messages = [ - SamplingMessage( - role="user", - content=TextContent(type="text", text=f"Here's my data: {data}"), - ), - SamplingMessage( - role="assistant", - content=TextContent(type="text", text="I see the data. What would you like to know?"), - ), - SamplingMessage( - role="user", - content=TextContent(type="text", text=query), - ), - ] - result = await ctx.sample(messages=messages) - return result.text or "" -``` - -The LLM receives the full conversation thread and responds with awareness of the preceding context. - -## Structured Output - - - -When you need validated, typed data instead of free-form text, use the `result_type` parameter. FastMCP ensures the LLM returns data matching your type, handling validation and retries automatically. The `result_type` parameter accepts Pydantic models, dataclasses, and basic types like `int`, `list[str]`, or `dict[str, int]`. - -```python -from pydantic import BaseModel -from fastmcp import FastMCP, Context - -mcp = FastMCP() - -class SentimentResult(BaseModel): - sentiment: str - confidence: float - reasoning: str - -@mcp.tool -async def analyze_sentiment(text: str, ctx: Context) -> SentimentResult: - """Analyze text sentiment with structured output.""" - result = await ctx.sample( - messages=f"Analyze the sentiment of: {text}", - result_type=SentimentResult, - ) - return result.result # A validated SentimentResult object -``` - -When you call this tool, the LLM returns a structured response that FastMCP validates against your Pydantic model. You access the validated object through `result.result`, while `result.text` contains the JSON representation. - - - When you pass `result_type`, `sample()` automatically creates a - `final_response` tool that the LLM calls to provide its response. If - validation fails, the error is sent back to the LLM for retry. This automatic - handling only applies to `sample()`—with `sample_step()`, you must manage - structured output yourself. - - -## Sampling with Tools - - - -Sampling with tools enables agentic workflows where the LLM can call functions to gather information before responding. This implements [SEP-1577](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577), allowing the LLM to autonomously orchestrate multi-step operations. - -Pass Python functions to the `tools` parameter, and FastMCP handles the execution loop automatically—calling tools, returning results to the LLM, and continuing until the LLM provides a final response. - -### Defining Tools - -Define regular Python functions with type hints and docstrings. FastMCP extracts the function's name, docstring, and parameter types to create tool schemas that the LLM can understand. - -```python -from fastmcp import FastMCP, Context - -def search(query: str) -> str: - """Search the web for information.""" - return f"Results for: {query}" - -def get_time() -> str: - """Get the current time.""" - from datetime import datetime - return datetime.now().strftime("%H:%M:%S") - -mcp = FastMCP() - -@mcp.tool -async def research(question: str, ctx: Context) -> str: - """Answer questions using available tools.""" - result = await ctx.sample( - messages=question, - tools=[search, get_time], - ) - return result.text or "" -``` - -The LLM sees each function's signature and docstring, using this information to decide when and how to call them. Tool errors are caught and sent back to the LLM, allowing it to recover gracefully. An internal safety limit prevents infinite loops. - -### Tool Error Handling - -By default, when a sampling tool raises an exception, the error message (including details) is sent back to the LLM so it can attempt recovery. To prevent sensitive information from leaking to the LLM, use the `mask_error_details` parameter: - -```python -result = await ctx.sample( - messages=question, - tools=[search], - mask_error_details=True, # Generic error messages only -) -``` - -When `mask_error_details=True`, tool errors become generic messages like `"Error executing tool 'search'"` instead of exposing stack traces or internal details. - -To intentionally provide specific error messages to the LLM regardless of masking, raise `ToolError`: - -```python -from fastmcp.exceptions import ToolError - -def search(query: str) -> str: - """Search for information.""" - if not query.strip(): - raise ToolError("Search query cannot be empty") - return f"Results for: {query}" -``` - -`ToolError` messages always pass through to the LLM, making it the escape hatch for errors you want the LLM to see and handle. - -For custom names or descriptions, use `SamplingTool.from_function()`: - -```python -from fastmcp.server.sampling import SamplingTool - -tool = SamplingTool.from_function( - my_func, - name="custom_name", - description="Custom description" -) - -result = await ctx.sample(messages="...", tools=[tool]) -``` - -### Combining with Structured Output - -Combine tools with `result_type` for agentic workflows that return validated, structured data. The LLM uses your tools to gather information, then returns a response matching your type. - -```python -result = await ctx.sample( - messages="Research Python async patterns", - tools=[search, fetch_url], - result_type=ResearchResult, -) -``` - -## Loop Control - - - -While `sample()` handles the tool execution loop automatically, some scenarios require fine-grained control over each step. The `sample_step()` method makes a single LLM call and returns a `SampleStep` containing the response and updated history. - -Unlike `sample()`, `sample_step()` is stateless—it doesn't remember previous calls. You control the conversation by passing the full message history each time. The returned `step.history` includes all messages up through the current response, making it easy to continue the loop. - -Use `sample_step()` when you need to: - -- Inspect tool calls before they execute -- Implement custom termination conditions -- Add logging, metrics, or checkpointing between steps -- Build custom agentic loops with domain-specific logic - -### Using sample_step() - -By default, `sample_step()` executes any tool calls and includes the results in the history. Call it in a loop, passing the updated history each time, until a stop condition is met. - -```python -from mcp.types import SamplingMessage - -@mcp.tool -async def controlled_agent(question: str, ctx: Context) -> str: - """Agent with manual loop control.""" - messages: list[str | SamplingMessage] = [question] # strings auto-convert - - while True: - step = await ctx.sample_step( - messages=messages, - tools=[search, get_time], - ) - - if step.is_tool_use: - # Tools already executed (execute_tools=True by default) - # Log what was called before continuing - for call in step.tool_calls: - print(f"Called tool: {call.name}") - - if not step.is_tool_use: - return step.text or "" - - # Continue with updated history - messages = step.history -``` - -### SampleStep Properties - -Each `SampleStep` provides information about what the LLM returned: - -- `step.is_tool_use` — True if the LLM requested tool calls -- `step.tool_calls` — List of tool calls requested (if any) -- `step.text` — The text content (if any) -- `step.history` — All messages exchanged so far - -The contents of `step.history` depend on `execute_tools`: -- **`execute_tools=True`** (default): Includes tool results, ready for the next iteration -- **`execute_tools=False`**: Includes the assistant's tool request, but you add results yourself - -### Manual Tool Execution - -Set `execute_tools=False` to handle tool execution yourself. When disabled, `step.history` contains the user message and the assistant's response with tool calls—but no tool results. You execute the tools and append the results as a user message. - -```python -from mcp.types import SamplingMessage, ToolResultContent, TextContent -from fastmcp import FastMCP, Context - -mcp = FastMCP() - -@mcp.tool -async def research(question: str, ctx: Context) -> str: - """Research with manual tool handling.""" - - def search(query: str) -> str: - return f"Results for: {query}" - - def get_time() -> str: - return "12:00 PM" - - # Map tool names to functions - tools = {"search": search, "get_time": get_time} - - messages: list[SamplingMessage] = [question] # strings are converted automatically - - while True: - step = await ctx.sample_step( - messages=messages, - tools=list(tools.values()), - execute_tools=False, - ) - - if not step.is_tool_use: - return step.text or "" - - # Execute tools and collect results - tool_results = [] - for call in step.tool_calls: - fn = tools[call.name] - result = fn(**call.input) - tool_results.append( - ToolResultContent( - type="tool_result", - toolUseId=call.id, - content=[TextContent(type="text", text=result)], - ) - ) - - messages = list(step.history) - messages.append(SamplingMessage(role="user", content=tool_results)) -``` - -#### Error Handling - -To report an error, set `isError=True`. The LLM will see the error and can decide how to proceed: - -```python -tool_result = ToolResultContent( - type="tool_result", - toolUseId=call.id, - content=[TextContent(type="text", text="Permission denied")], - isError=True, -) -``` - -## Fallback Handlers - -Client support for sampling is optional—some clients may not implement it. To ensure your tools work regardless of client capabilities, configure a `sampling_handler` that sends requests directly to an LLM provider. - -FastMCP provides built-in handlers for [OpenAI and Anthropic APIs](/clients/sampling#built-in-handlers). These handlers support the full sampling API including tools, automatically converting your Python functions to each provider's format. - - -Install handlers with `pip install fastmcp[openai]` or `pip install fastmcp[anthropic]`. - - -```python -from fastmcp import FastMCP -from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler - -server = FastMCP( - name="My Server", - sampling_handler=OpenAISamplingHandler(default_model="gpt-4o-mini"), - sampling_handler_behavior="fallback", -) -``` - -Or with Anthropic: - -```python -from fastmcp import FastMCP -from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler - -server = FastMCP( - name="My Server", - sampling_handler=AnthropicSamplingHandler(default_model="claude-sonnet-4-5"), - sampling_handler_behavior="fallback", -) -``` - -### Behavior Modes - -The `sampling_handler_behavior` parameter controls when the handler is used: - -- **`"fallback"`** (default): Use the handler only when the client doesn't support sampling. This lets capable clients use their own LLM while ensuring your tools still work with clients that lack sampling support. -- **`"always"`**: Always use the handler, bypassing the client entirely. Use this when you need guaranteed control over which LLM processes requests—for cost control, compliance requirements, or when specific model characteristics are essential. - - - Sampling with tools requires the client to advertise the `sampling.tools` - capability. FastMCP clients do this automatically. For external clients that - don't support tool-enabled sampling, configure a fallback handler with - `sampling_handler_behavior="always"`. - diff --git a/docs/servers/server.mdx b/docs/servers/server.mdx index 7f5c6fa41..e3624ea4f 100644 --- a/docs/servers/server.mdx +++ b/docs/servers/server.mdx @@ -1,25 +1,24 @@ --- title: The FastMCP Server sidebarTitle: Overview -description: The core FastMCP server class for building MCP applications with tools, resources, and prompts. +description: The core FastMCP server class for building MCP applications icon: server --- import { VersionBadge } from "/snippets/version-badge.mdx" -The central piece of a FastMCP application is the `FastMCP` server class. This class acts as the main container for your application's tools, resources, and prompts, and manages communication with MCP clients. +The `FastMCP` class is the central piece of every FastMCP application. It acts as the container for your tools, resources, and prompts, managing communication with MCP clients and orchestrating the entire server lifecycle. ## Creating a Server -Instantiating a server is straightforward. You typically provide a name for your server, which helps identify it in client applications or logs. +Instantiate a server by providing a name that identifies it in client applications and logs. You can also provide instructions that help clients understand the server's purpose. ```python from fastmcp import FastMCP -# Create a basic server instance mcp = FastMCP(name="MyAssistantServer") -# You can also add instructions for how to interact with the server +# Instructions help clients understand how to interact with the server mcp_with_instructions = FastMCP( name="HelpfulAssistant", instructions=""" @@ -29,7 +28,7 @@ mcp_with_instructions = FastMCP( ) ``` -The `FastMCP` constructor accepts several arguments: +The `FastMCP` constructor accepts several configuration options. The most commonly used parameters control server identity, authentication, and component behavior. @@ -92,7 +91,7 @@ The `FastMCP` constructor accepts several arguments: - Controls how tool input parameters are validated. When `False` (default), FastMCP uses Pydantic's flexible validation that coerces compatible inputs (e.g., `"10"` → `10` for int parameters). When `True`, uses the MCP SDK's JSON Schema validation to validate inputs against the exact schema before passing them to your function, rejecting any type mismatches. The default mode improves compatibility with LLM clients while maintaining type safety. See [Input Validation Modes](/servers/tools#input-validation-modes) for details + Controls how tool input parameters are validated. When `False` (default), FastMCP uses Pydantic's flexible validation that coerces compatible inputs (e.g., `"10"` to `10` for int parameters). When `True`, uses the MCP SDK's JSON Schema validation to validate inputs against the exact schema before passing them to your function, rejecting any type mismatches. The default mode improves compatibility with LLM clients while maintaining type safety. See [Input Validation Modes](/servers/tools#input-validation-modes) for details @@ -101,13 +100,14 @@ The `FastMCP` constructor accepts several arguments: + ## Components -FastMCP servers expose several types of components to the client: +FastMCP servers expose three types of components to clients. Each type serves a distinct purpose in the MCP protocol. ### Tools -Tools are functions that the client can call to perform actions or access external systems. +Tools are functions that clients can invoke to perform actions or access external systems. They're the primary way clients interact with your server's capabilities. ```python @mcp.tool @@ -120,7 +120,7 @@ See [Tools](/servers/tools) for detailed documentation. ### Resources -Resources expose data sources that the client can read. +Resources expose data that clients can read. Unlike tools, resources are passive data sources that clients pull from rather than invoke. ```python @mcp.resource("data://config") @@ -129,25 +129,24 @@ def get_config() -> dict: return {"theme": "dark", "version": "1.0"} ``` -See [Resources & Templates](/servers/resources) for detailed documentation. +See [Resources](/servers/resources) for detailed documentation. ### Resource Templates -Resource templates are parameterized resources that allow the client to request specific data. +Resource templates are parameterized resources. The client provides values for template parameters in the URI, and the server returns data specific to those parameters. ```python @mcp.resource("users://{user_id}/profile") def get_user_profile(user_id: int) -> dict: """Retrieves a user's profile by ID.""" - # The {user_id} in the URI is extracted and passed to this function return {"id": user_id, "name": f"User {user_id}", "status": "active"} ``` -See [Resources & Templates](/servers/resources) for detailed documentation. +See [Resource Templates](/servers/resources#resource-templates) for detailed documentation. ### Prompts -Prompts are reusable message templates for guiding the LLM. +Prompts are reusable message templates that guide LLM interactions. They help establish consistent patterns for how clients should frame requests. ```python @mcp.prompt @@ -163,9 +162,9 @@ See [Prompts](/servers/prompts) for detailed documentation. -FastMCP supports tag-based filtering to selectively expose components based on configurable include/exclude tag sets. This is useful for creating different views of your server for different environments or users. +Tags let you categorize components and selectively expose them based on configurable include/exclude sets. This is useful for creating different views of your server for different environments or user types. -Components can be tagged when defined using the `tags` parameter: +Components can be tagged when defined using the `tags` parameter. A component can have multiple tags, and filtering operates on tag membership. ```python @mcp.tool(tags={"public", "utility"}) @@ -177,23 +176,22 @@ def admin_tool() -> str: return "This tool is for admins only" ``` - The filtering logic works as follows: - **Include tags**: If specified, only components with at least one matching tag are exposed - **Exclude tags**: Components with any matching tag are filtered out - **Precedence**: Exclude tags always take priority over include tags -To ensure a component is never exposed, you can set `enabled=False` on the component itself. To learn more, see the component-specific documentation. +To ensure a component is never exposed, you can set `enabled=False` on the component itself. See the component-specific documentation for details. -You configure tag-based filtering when creating your server: +Configure tag-based filtering when creating your server. ```python # Only expose components tagged with "public" mcp = FastMCP(include_tags={"public"}) -# Hide components tagged as "internal" or "deprecated" +# Hide components tagged as "internal" or "deprecated" mcp = FastMCP(exclude_tags={"internal", "deprecated"}) # Combine both: show admin tools but hide deprecated ones @@ -204,10 +202,9 @@ This filtering applies to all component types (tools, resources, resource templa ## Running the Server -FastMCP servers need a transport mechanism to communicate with clients. You typically start your server by calling the `mcp.run()` method on your `FastMCP` instance, often within an `if __name__ == "__main__":` block in your main server script. This pattern ensures compatibility with various MCP clients. +FastMCP servers communicate with clients through transport mechanisms. Start your server by calling `mcp.run()`, typically within an `if __name__ == "__main__":` block. This pattern ensures compatibility with various MCP clients. ```python -# my_server.py from fastmcp import FastMCP mcp = FastMCP(name="MyServer") @@ -218,25 +215,23 @@ def greet(name: str) -> str: return f"Hello, {name}!" if __name__ == "__main__": - # This runs the server, defaulting to STDIO transport + # Defaults to STDIO transport mcp.run() - - # To use a different transport, e.g., HTTP: + + # Or use HTTP transport # mcp.run(transport="http", host="127.0.0.1", port=9000) ``` -FastMCP supports several transport options: -- STDIO (default, for local tools) -- HTTP (recommended for web services, uses Streamable HTTP protocol) -- SSE (legacy web transport, deprecated) +FastMCP supports several transports: +- **STDIO** (default): For local integrations and CLI tools +- **HTTP**: For web services using the Streamable HTTP protocol +- **SSE**: Legacy web transport (deprecated) -The server can also be run using the FastMCP CLI. - -For detailed information on each transport, how to configure them (host, port, paths), and when to use which, please refer to the [**Running Your FastMCP Server**](/deployment/running-server) guide. +The server can also be run using the FastMCP CLI. For detailed information on transports and configuration, see the [Running Your Server](/deployment/running-server) guide. ## Custom Routes -When running your server with HTTP transport, you can add custom web routes alongside your MCP endpoint using the `@custom_route` decorator. This is useful for simple endpoints like health checks that need to be served alongside your MCP server: +When running with HTTP transport, you can add custom web routes alongside your MCP endpoint using the `@custom_route` decorator. This is useful for auxiliary endpoints like health checks. ```python from fastmcp import FastMCP @@ -259,142 +254,3 @@ Custom routes are served alongside your MCP endpoint and are useful for: - Basic webhooks or callbacks For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/http#integration-with-web-frameworks). - -## Composing Servers - - - -FastMCP supports composing multiple servers together using `import_server` (static copy) and `mount` (live link). This allows you to organize large applications into modular components or reuse existing servers. - -See the [Mounting Servers](/servers/providers/mounting) guide for full details, best practices, and examples. - -```python -# Example: Importing a subserver -from fastmcp import FastMCP -import asyncio - -main = FastMCP(name="Main") -sub = FastMCP(name="Sub") - -@sub.tool -def hello(): - return "hi" - -# Mount directly -main.mount(sub, namespace="sub") -``` - -## Proxying Servers - - - -FastMCP can act as a proxy for any MCP server (local or remote) using `create_proxy`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa. - -Proxies automatically handle concurrent operations safely by creating fresh sessions for each request when using disconnected clients. - -See the [Remote Proxies](/servers/providers/proxy) guide for details and advanced usage. - -```python -from fastmcp import Client -from fastmcp.server import create_proxy - -backend = Client("http://example.com/mcp/sse") -proxy = create_proxy(backend, name="ProxyServer") -# Now use the proxy like any FastMCP server -``` - -## OpenAPI Integration - - - -FastMCP can automatically generate servers from OpenAPI specifications or existing FastAPI applications using `FastMCP.from_openapi()` and `FastMCP.from_fastapi()`. This allows you to instantly convert existing APIs into MCP servers without manual tool creation. - -See the [FastAPI Integration](/integrations/fastapi) and [OpenAPI Integration](/integrations/openapi) guides for detailed examples and configuration options. - -```python -import httpx -from fastmcp import FastMCP - -# From OpenAPI spec -spec = httpx.get("https://api.example.com/openapi.json").json() -mcp = FastMCP.from_openapi(openapi_spec=spec, client=httpx.AsyncClient()) - -# From FastAPI app -from fastapi import FastAPI -app = FastAPI() -mcp = FastMCP.from_fastapi(app=app) -``` - -## Server Configuration - -Servers can be configured using a combination of initialization arguments, global settings, and transport-specific settings. - -### Server-Specific Configuration - -Server-specific settings are passed when creating the `FastMCP` instance and control server behavior: - -```python -from fastmcp import FastMCP - -# Configure server-specific settings -mcp = FastMCP( - name="ConfiguredServer", - include_tags={"public", "api"}, # Only expose these tagged components - exclude_tags={"internal", "deprecated"}, # Hide these tagged components - on_duplicate_tools="error", # Handle duplicate registrations - on_duplicate_resources="warn", - on_duplicate_prompts="replace", -) -``` - -### Global Settings - -Global settings affect all FastMCP servers and can be configured via environment variables (prefixed with `FASTMCP_`) or in a `.env` file: - -```python -import fastmcp - -# Access global settings -print(fastmcp.settings.log_level) # Default: "INFO" -print(fastmcp.settings.mask_error_details) # Default: False -print(fastmcp.settings.strict_input_validation) # Default: False -``` - -Common global settings include: -- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL` -- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS` -- **`strict_input_validation`**: Controls tool input validation mode (default: False for flexible coercion), set with `FASTMCP_STRICT_INPUT_VALIDATION`. See [Input Validation Modes](/servers/tools#input-validation-modes) -- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv - -### Transport-Specific Configuration - -Transport settings are provided when running the server and control network behavior: - -```python -# Configure transport when running -mcp.run( - transport="http", - host="0.0.0.0", # Bind to all interfaces - port=9000, # Custom port - log_level="DEBUG", # Override global log level -) - -# Or for async usage -await mcp.run_async( - transport="http", - host="127.0.0.1", - port=8080, -) -``` - -### Setting Global Configuration - -Global FastMCP settings can be configured via environment variables (prefixed with `FASTMCP_`): - -```bash -# Configure global FastMCP behavior -export FASTMCP_LOG_LEVEL=DEBUG -export FASTMCP_MASK_ERROR_DETAILS=True -export FASTMCP_STRICT_INPUT_VALIDATION=False -``` - diff --git a/docs/servers/telemetry.mdx b/docs/servers/telemetry.mdx index b1c2a964e..1055dc0c0 100644 --- a/docs/servers/telemetry.mdx +++ b/docs/servers/telemetry.mdx @@ -3,6 +3,7 @@ title: OpenTelemetry sidebarTitle: Telemetry description: Native OpenTelemetry instrumentation for distributed tracing. icon: chart-line +tag: NEW --- FastMCP includes native OpenTelemetry instrumentation for observability. Traces are automatically generated for tool, prompt, resource, and resource template operations, providing visibility into server behavior, request handling, and provider delegation chains. diff --git a/docs/servers/versioning.mdx b/docs/servers/versioning.mdx index f4db7584d..bdcefb217 100644 --- a/docs/servers/versioning.mdx +++ b/docs/servers/versioning.mdx @@ -3,6 +3,7 @@ title: Versioning sidebarTitle: Versioning description: Serve multiple API versions from a single codebase icon: code-branch +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' @@ -15,7 +16,7 @@ The primary use case is serving different API versions from one codebase. Instea ## Versioned API Surfaces -Consider a server that needs to support both v1 and v2 clients. The v2 API adds new parameters to existing tools, and you want both versions to coexist cleanly. You define your components on a shared provider, then create separate servers with different version filters. +Consider a server that needs to support both v1 and v2 clients. The v2 API adds new parameters to existing tools, and you want both versions to coexist cleanly. Define your components on a shared provider, then create separate servers with different version filters. ```python from fastmcp import FastMCP @@ -59,7 +60,7 @@ VersionFilter(version_gte="2.0", version_lt="3.0") ``` -**Unversioned components are exempt from version filtering.** A `VersionFilter` only affects versioned components—unversioned components always pass through regardless of the filter's constraints. This ensures that adding version filtering to a server with mixed versioned and unversioned tools doesn't accidentally hide the unversioned ones. To prevent confusion, FastMCP forbids mixing versioned and unversioned components with the same name. +**Unversioned components are exempt from version filtering.** A `VersionFilter` only affects versioned components - unversioned components always pass through regardless of the filter's constraints. This ensures that adding version filtering to a server with mixed versioned and unversioned tools doesn't accidentally hide the unversioned ones. To prevent confusion, FastMCP forbids mixing versioned and unversioned components with the same name. ### Filtering Mounted Servers @@ -139,6 +140,8 @@ The error message explains the conflict: "Cannot add versioned tool 'calculate' This restriction exists because unversioned components always pass through version filters. If you could mix versioned and unversioned components, you'd have no way to filter out the unversioned one using `VersionFilter`. By enforcing consistency at registration, FastMCP ensures version filtering behaves predictably. +Resources and prompts follow the same pattern. + ```python @mcp.resource("config://app", version="1.0") def config_v1() -> str: @@ -243,8 +246,8 @@ For [PEP 440](https://peps.python.org/pep-0440/) versions (like `"1.0"`, `"2.1.3 ```python # PEP 440 versions compare semantically -"1" < "2" < "10" # Not string order ("1" < "10" < "2") -"1.9" < "1.10" # Not string order ("1.10" < "1.9") +"1" < "2" < "10" # Numeric order (not "1" < "10" < "2") +"1.9" < "1.10" # Numeric order (not "1.10" < "1.9") "1.0a1" < "1.0b1" < "1.0" # Pre-releases sort before releases ``` diff --git a/docs/servers/visibility.mdx b/docs/servers/visibility.mdx index ad378eacd..509bd7068 100644 --- a/docs/servers/visibility.mdx +++ b/docs/servers/visibility.mdx @@ -1,6 +1,6 @@ --- title: Component Visibility -sidebarTitle: Component Visibility +sidebarTitle: Visibility description: Control which components are available to clients icon: toggle-on tag: NEW @@ -259,33 +259,6 @@ mcp.disable(tags={"beta"}) # new_feature is disabled (server's later disable overrides provider's enable) ``` -## Dynamic Changes - -Visibility state changes take effect immediately. You can adjust during request handling based on context. - -```python -from fastmcp import FastMCP -from fastmcp.server import Context - -mcp = FastMCP("Server") - -@mcp.tool(tags={"admin"}) -def admin_action() -> str: - return "Admin action performed" - -@mcp.tool -def check_permissions(ctx: Context) -> str: - """Check if admin tools should be available.""" - user = ctx.request_context.get_user() - - if user and user.is_admin: - mcp.enable(tags={"admin"}) - return "Admin tools enabled" - else: - mcp.disable(tags={"admin"}) - return "Admin tools disabled" -``` - ## Per-Session Visibility Server-level visibility changes affect all connected clients simultaneously. When you need different clients to see different components, use per-session visibility instead. diff --git a/docs/v2-banner.js b/docs/v2-banner.js new file mode 100644 index 000000000..ef65e067d --- /dev/null +++ b/docs/v2-banner.js @@ -0,0 +1,39 @@ +// Add v2 banner inside content-container with negative margins +(function() { + if (typeof window === 'undefined') return; + + function addBanner() { + const isV2 = window.location.pathname.includes('/v2/'); + const container = document.getElementById('content-container'); + let banner = document.getElementById('v2-banner'); + + if (isV2 && container) { + if (!banner) { + banner = document.createElement('div'); + banner.id = 'v2-banner'; + banner.innerHTML = 'These are the docs for FastMCP 2.0. The beta of FastMCP 3.0 is now available.'; + container.insertBefore(banner, container.firstChild); + } + } else if (!isV2 && banner) { + banner.remove(); + } + } + + function run() { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', addBanner); + } else { + addBanner(); + } + } + + run(); + + let lastUrl = location.href; + new MutationObserver(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(addBanner, 100); + } + }).observe(document.body, {subtree: true, childList: true}); +})(); diff --git a/docs/v2/changelog.mdx b/docs/v2/changelog.mdx new file mode 100644 index 000000000..4fd1dca91 --- /dev/null +++ b/docs/v2/changelog.mdx @@ -0,0 +1,2280 @@ +--- +title: "Changelog" +icon: "list-check" +rss: true +--- + + + +**[v2.14.3: Time After Timeout](https://github.com/jlowin/fastmcp/releases/tag/v2.14.3)** + +Sometimes five seconds just isn't enough. This release fixes an HTTP transport bug that was cutting connections short, along with OAuth and Redis fixes, better ASGI support, and CLI update notifications so you never miss a beat. + +## What's Changed +### Enhancements 🔧 +* Add debug logging for OAuth token expiry diagnostics by [@jlowin](https://github.com/jlowin) in [#2789](https://github.com/jlowin/fastmcp/pull/2789) +* Add CLI update notifications by [@jlowin](https://github.com/jlowin) in [#2839](https://github.com/jlowin/fastmcp/pull/2839) +* Use pip instead of uv pip in upgrade instructions by [@jlowin](https://github.com/jlowin) in [#2841](https://github.com/jlowin/fastmcp/pull/2841) +### Fixes 🐞 +* Backport OAuth token storage TTL fix to release/2.x by [@jlowin](https://github.com/jlowin) in [#2798](https://github.com/jlowin/fastmcp/pull/2798) +* Prefix Redis keys with docket name for ACL isolation (2.x backport) by [@chrisguidry](https://github.com/chrisguidry) in [#2812](https://github.com/jlowin/fastmcp/pull/2812) +* Fix ContextVar propagation for ASGI-mounted servers with tasks by [@chrisguidry](https://github.com/chrisguidry) in [#2843](https://github.com/jlowin/fastmcp/pull/2843) +* Fix HTTP transport timeout defaulting to 5 seconds by [@jlowin](https://github.com/jlowin) in [#2848](https://github.com/jlowin/fastmcp/pull/2848) + +**Full Changelog**: [v2.14.2...v2.14.3](https://github.com/jlowin/fastmcp/compare/v2.14.2...v2.14.3) + + + + + +**[v2.14.2: Port Authority](https://github.com/jlowin/fastmcp/releases/tag/v2.14.2)** + +FastMCP 2.14.2 brings a wave of community contributions safely into the 2.x line. A variety of important fixes backported from 3.0 work improve OpenAPI 3.1 compatibility, MCP spec compliance for output schemas and elicitation, and correct a subtle base_url fallback issue. The CLI now gently reminds you that FastMCP 3.0 is on the horizon. + +## What's Changed +### Enhancements 🔧 +* Pin MCP under 2.x by [@jlowin](https://github.com/jlowin) in [#2709](https://github.com/jlowin/fastmcp/pull/2709) +* Add auth_route parameter to SupabaseProvider by [@EloiZalczer](https://github.com/EloiZalczer) in [#2760](https://github.com/jlowin/fastmcp/pull/2760) +* Update CLI banner with FastMCP 3.0 notice by [@jlowin](https://github.com/jlowin) in [#2765](https://github.com/jlowin/fastmcp/pull/2765) +### Fixes 🐞 +* Let FastMCPError propagate unchanged from managers by [@jlowin](https://github.com/jlowin) in [#2697](https://github.com/jlowin/fastmcp/pull/2697) +* Fix test cleanup for uvicorn 0.39+ context isolation by [@jlowin](https://github.com/jlowin) in [#2696](https://github.com/jlowin/fastmcp/pull/2696) +* Bump pydocket to 0.16.3 to fix worker cleanup race condition by [@chrisguidry](https://github.com/chrisguidry) in [#2700](https://github.com/jlowin/fastmcp/pull/2700) +* Fix Prefect website URL in docs footer by [@mgoldsborough](https://github.com/mgoldsborough) in [#2705](https://github.com/jlowin/fastmcp/pull/2705) +* Fix: resolve root-level $ref in outputSchema for MCP spec compliance by [@majiayu000](https://github.com/majiayu000) in [#2727](https://github.com/jlowin/fastmcp/pull/2727) +* Fix OAuth Proxy resource parameter validation by [@jlowin](https://github.com/jlowin) in [#2763](https://github.com/jlowin/fastmcp/pull/2763) +* Fix openapi_version check to include 3.1 by [@deeleeramone](https://github.com/deeleeramone) in [#2769](https://github.com/jlowin/fastmcp/pull/2769) +* Fix titled enum elicitation schema to comply with MCP spec by [@jlowin](https://github.com/jlowin) in [#2774](https://github.com/jlowin/fastmcp/pull/2774) +* Fix base_url fallback when url is not set by [@bhbs](https://github.com/bhbs) in [#2782](https://github.com/jlowin/fastmcp/pull/2782) +* Lazy import DiskStore to avoid sqlite3 dependency on import by [@jlowin](https://github.com/jlowin) in [#2785](https://github.com/jlowin/fastmcp/pull/2785) +### Docs 📚 +* Add v3 breaking changes notice to README and docs by [@jlowin](https://github.com/jlowin) in [#2713](https://github.com/jlowin/fastmcp/pull/2713) +* Add changelog entries for v2.13.1 through v2.14.1 by [@jlowin](https://github.com/jlowin) in [#2724](https://github.com/jlowin/fastmcp/pull/2724) +* conference to 2.x branch by [@aaazzam](https://github.com/aaazzam) in [#2787](https://github.com/jlowin/fastmcp/pull/2787) + +**Full Changelog**: [v2.14.1...v2.14.2](https://github.com/jlowin/fastmcp/compare/v2.14.1...v2.14.2) + + + + + +**[v2.14.1: 'Tis a Gift to Be Sample](https://github.com/jlowin/fastmcp/releases/tag/v2.14.1)** + +FastMCP 2.14.1 introduces sampling with tools (SEP-1577), enabling servers to pass tools to `ctx.sample()` for agentic workflows where the LLM can automatically execute tool calls in a loop. The new `ctx.sample_step()` method provides single LLM calls that return `SampleStep` objects for custom control flow, while `result_type` enables structured outputs via validated Pydantic models. + +🤖 **AnthropicSamplingHandler** joins the existing OpenAI handler, providing multi-provider sampling support out of the box. + +⚡ **OpenAISamplingHandler promoted** from experimental status—sampling handlers are now production-ready with a unified API. + +## What's Changed +### New Features 🎉 +* Sampling with tools by [@jlowin](https://github.com/jlowin) in [#2538](https://github.com/jlowin/fastmcp/pull/2538) +* Add AnthropicSamplingHandler by [@jlowin](https://github.com/jlowin) in [#2677](https://github.com/jlowin/fastmcp/pull/2677) +### Enhancements 🔧 +* Add Python 3.13 to ubuntu CI by [@jlowin](https://github.com/jlowin) in [#2648](https://github.com/jlowin/fastmcp/pull/2648) +* Remove legacy task initialization workaround by [@jlowin](https://github.com/jlowin) in [#2649](https://github.com/jlowin/fastmcp/pull/2649) +* Consolidate session state reset logic by [@jlowin](https://github.com/jlowin) in [#2651](https://github.com/jlowin/fastmcp/pull/2651) +* Unify SamplingHandler; promote OpenAI from experimental by [@jlowin](https://github.com/jlowin) in [#2656](https://github.com/jlowin/fastmcp/pull/2656) +* Add `tool_names` parameter to mount() for name customization by [@jlowin](https://github.com/jlowin) in [#2660](https://github.com/jlowin/fastmcp/pull/2660) +* Use streamable HTTP client API from MCP SDK by [@jlowin](https://github.com/jlowin) in [#2678](https://github.com/jlowin/fastmcp/pull/2678) +* Deprecate `exclude_args` in favor of Depends() by [@jlowin](https://github.com/jlowin) in [#2693](https://github.com/jlowin/fastmcp/pull/2693) +### Fixes 🐞 +* Fix prompt tasks to return mcp.types.PromptMessage by [@jlowin](https://github.com/jlowin) in [#2650](https://github.com/jlowin/fastmcp/pull/2650) +* Fix Windows test warnings by [@jlowin](https://github.com/jlowin) in [#2653](https://github.com/jlowin/fastmcp/pull/2653) +* Cleanup cancelled connection startup by [@jlowin](https://github.com/jlowin) in [#2679](https://github.com/jlowin/fastmcp/pull/2679) +* Fix tool choice bug in sampling examples by [@shawnthapa](https://github.com/shawnthapa) in [#2686](https://github.com/jlowin/fastmcp/pull/2686) +### Docs 📚 +* Simplify Docket tip wording by [@chrisguidry](https://github.com/chrisguidry) in [#2662](https://github.com/jlowin/fastmcp/pull/2662) +### Other Changes 🦾 +* Bump pydocket to ≥0.15.5 by [@jlowin](https://github.com/jlowin) in [#2694](https://github.com/jlowin/fastmcp/pull/2694) + +## New Contributors +* [@shawnthapa](https://github.com/shawnthapa) made their first contribution in [#2686](https://github.com/jlowin/fastmcp/pull/2686) + +**Full Changelog**: [v2.14.0...v2.14.1](https://github.com/jlowin/fastmcp/compare/v2.14.0...v2.14.1) + + + + + +**[v2.14.0: Task and You Shall Receive](https://github.com/jlowin/fastmcp/releases/tag/v2.14.0)** + +FastMCP 2.14 begins adopting the MCP 2025-11-25 specification, introducing protocol-native background tasks (SEP-1686) that enable long-running operations to report progress without blocking clients. The experimental OpenAPI parser graduates to standard, the `OpenAISamplingHandler` is promoted from experimental, and deprecated APIs accumulated across the 2.x series are removed. + +⏳ **Background Tasks** let you add `task=True` to any async tool decorator to run operations in the background with progress tracking. Powered by [Docket](https://github.com/chrisguidry/docket), an enterprise task scheduler handling millions of concurrent tasks daily—in-memory backends work out-of-the-box, and Redis URLs enable persistence and horizontal scaling. + +🔧 **OpenAPI Parser Promoted** from experimental to standard with improved performance through single-pass schema processing and cleaner abstractions. + +📋 **MCP 2025-11-25 Specification Support** including SSE polling and event resumability (SEP-1699), multi-select enum elicitation schemas (SEP-1330), default values for elicitation (SEP-1034), and tool name validation at registration time (SEP-986). + +## Breaking Changes +- Docket is always enabled; task execution is forbidden through proxies +- Task protocol enabled by default +- Removed deprecated settings, imports, and methods accumulated across 2.x series + +## What's Changed +### New Features 🎉 +* OpenAPI parser is now the default by [@jlowin](https://github.com/jlowin) in [#2583](https://github.com/jlowin/fastmcp/pull/2583) +* Implement SEP-1686: Background Tasks by [@jlowin](https://github.com/jlowin) in [#2550](https://github.com/jlowin/fastmcp/pull/2550) +### Enhancements 🔧 +* Expose InitializeResult in middleware by [@jlowin](https://github.com/jlowin) in [#2562](https://github.com/jlowin/fastmcp/pull/2562) +* Update MCP SDK auth compatibility by [@jlowin](https://github.com/jlowin) in [#2574](https://github.com/jlowin/fastmcp/pull/2574) +* Validate tool names at registration (SEP-986) by [@jlowin](https://github.com/jlowin) in [#2588](https://github.com/jlowin/fastmcp/pull/2588) +* Support SEP-1034 and SEP-1330 for elicitation by [@jlowin](https://github.com/jlowin) in [#2595](https://github.com/jlowin/fastmcp/pull/2595) +* Implement SSE polling (SEP-1699) by [@jlowin](https://github.com/jlowin) in [#2612](https://github.com/jlowin/fastmcp/pull/2612) +* Expose session ID callback by [@jlowin](https://github.com/jlowin) in [#2628](https://github.com/jlowin/fastmcp/pull/2628) +### Fixes 🐞 +* Fix OAuth metadata discovery by [@jlowin](https://github.com/jlowin) in [#2565](https://github.com/jlowin/fastmcp/pull/2565) +* Fix fastapi.cli package structure by [@jlowin](https://github.com/jlowin) in [#2570](https://github.com/jlowin/fastmcp/pull/2570) +* Correct OAuth error codes by [@jlowin](https://github.com/jlowin) in [#2578](https://github.com/jlowin/fastmcp/pull/2578) +* Prevent function signature modification by [@jlowin](https://github.com/jlowin) in [#2590](https://github.com/jlowin/fastmcp/pull/2590) +* Fix proxy client kwargs by [@jlowin](https://github.com/jlowin) in [#2605](https://github.com/jlowin/fastmcp/pull/2605) +* Fix nested server routing by [@jlowin](https://github.com/jlowin) in [#2618](https://github.com/jlowin/fastmcp/pull/2618) +* Use access token expiry fallback by [@jlowin](https://github.com/jlowin) in [#2635](https://github.com/jlowin/fastmcp/pull/2635) +* Handle transport cleanup exceptions by [@jlowin](https://github.com/jlowin) in [#2642](https://github.com/jlowin/fastmcp/pull/2642) +### Docs 📚 +* Add OCI and Supabase integration docs by [@jlowin](https://github.com/jlowin) in [#2580](https://github.com/jlowin/fastmcp/pull/2580) +* Add v2.14.0 upgrade guide by [@jlowin](https://github.com/jlowin) in [#2598](https://github.com/jlowin/fastmcp/pull/2598) +* Rewrite background tasks documentation by [@jlowin](https://github.com/jlowin) in [#2620](https://github.com/jlowin/fastmcp/pull/2620) +* Document read-only tool patterns by [@jlowin](https://github.com/jlowin) in [#2632](https://github.com/jlowin/fastmcp/pull/2632) + +## New Contributors +11 total contributors including 7 first-time participants. + +**Full Changelog**: [v2.13.3...v2.14.0](https://github.com/jlowin/fastmcp/compare/v2.13.3...v2.14.0) + + + + + +**[v2.13.3: Pin-ish Line](https://github.com/jlowin/fastmcp/releases/tag/v2.13.3)** + +FastMCP 2.13.3 pins `mcp<1.23` as a precautionary measure. MCP SDK 1.23 introduced changes related to the November 25, 2025 MCP protocol update that break certain FastMCP patches and workarounds, particularly around OAuth implementation details. FastMCP 2.14 introduces proper support for the updated protocol and requires `mcp>=1.23`. + +## What's Changed +### Fixes 🐞 +* Pin MCP SDK below 1.23 by [@jlowin](https://github.com/jlowin) in [#2545](https://github.com/jlowin/fastmcp/pull/2545) + +**Full Changelog**: [v2.13.2...v2.13.3](https://github.com/jlowin/fastmcp/compare/v2.13.2...v2.13.3) + + + + + +**[v2.13.2: Refreshing Changes](https://github.com/jlowin/fastmcp/releases/tag/v2.13.2)** + +FastMCP 2.13.2 polishes the authentication stack with improvements to token refresh, scope handling, and multi-instance deployments. Discord was added as a built-in OAuth provider, Azure and Google token handling became more reliable, and proxy classes now properly forward icons and titles. + +## What's Changed +### New Features 🎉 +* Add Discord OAuth provider by [@jlowin](https://github.com/jlowin) in [#2480](https://github.com/jlowin/fastmcp/pull/2480) +### Enhancements 🔧 +* Descope Provider updates for new well-known URLs by [@anvibanga](https://github.com/anvibanga) in [#2465](https://github.com/jlowin/fastmcp/pull/2465) +* Scalekit provider improvements by [@jlowin](https://github.com/jlowin) in [#2472](https://github.com/jlowin/fastmcp/pull/2472) +* Add CSP customization for consent screens by [@jlowin](https://github.com/jlowin) in [#2488](https://github.com/jlowin/fastmcp/pull/2488) +* Add icon support to proxy classes by [@jlowin](https://github.com/jlowin) in [#2495](https://github.com/jlowin/fastmcp/pull/2495) +### Fixes 🐞 +* Google Provider now defaults to refresh token support by [@jlowin](https://github.com/jlowin) in [#2468](https://github.com/jlowin/fastmcp/pull/2468) +* Fix Azure OAuth token refresh with unprefixed scopes by [@jlowin](https://github.com/jlowin) in [#2475](https://github.com/jlowin/fastmcp/pull/2475) +* Prevent `$defs` mutation during tool transforms by [@jlowin](https://github.com/jlowin) in [#2482](https://github.com/jlowin/fastmcp/pull/2482) +* Fix OAuth proxy refresh token storage for multi-instance deployments by [@jlowin](https://github.com/jlowin) in [#2490](https://github.com/jlowin/fastmcp/pull/2490) +* Fix stale token issue after OAuth refresh by [@jlowin](https://github.com/jlowin) in [#2498](https://github.com/jlowin/fastmcp/pull/2498) +* Fix Azure provider OIDC scope handling by [@jlowin](https://github.com/jlowin) in [#2505](https://github.com/jlowin/fastmcp/pull/2505) + +## New Contributors +7 new contributors made their first FastMCP contributions in this release. + +**Full Changelog**: [v2.13.1...v2.13.2](https://github.com/jlowin/fastmcp/compare/v2.13.1...v2.13.2) + + + + + +**[v2.13.1: Heavy Meta](https://github.com/jlowin/fastmcp/releases/tag/v2.13.1)** + +FastMCP 2.13.1 introduces meta parameter support for `ToolResult`, enabling tools to return supplementary metadata alongside results. This supports emerging use cases like OpenAI's Apps SDK. The release also brings improved OAuth functionality with custom token verifiers including a new DebugTokenVerifier, and adds OCI and Supabase authentication providers. + +🏷️ **Meta parameters for ToolResult** enable tools to return supplementary metadata alongside results, supporting patterns like OpenAI's Apps SDK integration. + +🔐 **Custom token verifiers** with DebugTokenVerifier for development, plus Azure Government support through a `base_authority` parameter and Supabase authentication algorithm configuration. + +🔒 **Security fixes** address CVE-2025-61920 through authlib updates and validate Cursor deeplink URLs using safer Windows APIs. + +## What's Changed +### New Features 🎉 +* Add meta parameter support for ToolResult by [@jlowin](https://github.com/jlowin) in [#2350](https://github.com/jlowin/fastmcp/pull/2350) +* Add OCI authentication provider by [@jlowin](https://github.com/jlowin) in [#2365](https://github.com/jlowin/fastmcp/pull/2365) +* Add Supabase authentication provider by [@jlowin](https://github.com/jlowin) in [#2378](https://github.com/jlowin/fastmcp/pull/2378) +### Enhancements 🔧 +* Add custom token verifier support to OIDCProxy by [@jlowin](https://github.com/jlowin) in [#2355](https://github.com/jlowin/fastmcp/pull/2355) +* Add DebugTokenVerifier for development by [@jlowin](https://github.com/jlowin) in [#2362](https://github.com/jlowin/fastmcp/pull/2362) +* Add Azure Government support via base_authority parameter by [@jlowin](https://github.com/jlowin) in [#2385](https://github.com/jlowin/fastmcp/pull/2385) +* Add Supabase authentication algorithm configuration by [@jlowin](https://github.com/jlowin) in [#2392](https://github.com/jlowin/fastmcp/pull/2392) +### Fixes 🐞 +* Security: Update authlib for CVE-2025-61920 by [@jlowin](https://github.com/jlowin) in [#2398](https://github.com/jlowin/fastmcp/pull/2398) +* Validate Cursor deeplink URLs using safer Windows APIs by [@jlowin](https://github.com/jlowin) in [#2405](https://github.com/jlowin/fastmcp/pull/2405) +* Exclude MCP SDK 1.21.1 due to integration test failures by [@jlowin](https://github.com/jlowin) in [#2422](https://github.com/jlowin/fastmcp/pull/2422) + +## New Contributors +18 new contributors joined in this release across 70+ pull requests. + +**Full Changelog**: [v2.13.0...v2.13.1](https://github.com/jlowin/fastmcp/compare/v2.13.0...v2.13.1) + + + + + +**[v2.13.0: Cache Me If You Can](https://github.com/jlowin/fastmcp/releases/tag/v2.13.0)** + +FastMCP 2.13 "Cache Me If You Can" represents a fundamental maturation of the framework. After months of community feedback on authentication and state management, this release delivers the infrastructure FastMCP needs to handle production workloads: persistent storage, response caching, and pragmatic OAuth improvements that reflect real-world deployment challenges. + +💾 **Pluggable storage backends** bring persistent state to FastMCP servers. Built on [py-key-value-aio](https://github.com/strawgate/py-key-value), a new library from FastMCP maintainer Bill Easton ([@strawgate](https://github.com/strawgate)), the storage layer provides encrypted disk storage by default, platform-aware token management, and a simple key-value interface for application state. We're excited to bring this elegantly designed library into the FastMCP ecosystem - it's both powerful and remarkably easy to use, including wrappers to add encryption, TTLs, caching, and more to backends ranging from Elasticsearch, Redis, DynamoDB, filesystem, in-memory, and more! OAuth providers now automatically persist tokens across restarts, and developers can store arbitrary state without reaching for external databases. This foundation enables long-running sessions, cached credentials, and stateful applications built on MCP. + +🔐 **OAuth maturity** brings months of production learnings into the framework. The new consent screen prevents confused deputy and authorization bypass attacks discovered in earlier versions while providing a clean UX with customizable branding. The OAuth proxy now issues its own tokens with automatic key derivation from client secrets, and RFC 7662 token introspection support enables enterprise auth flows. Path prefix mounting enables OAuth-protected servers to integrate into existing web applications under custom paths like `/api`, and MCP 1.17+ compliance with RFC 9728 ensures protocol compatibility. Combined with improved error handling and platform-aware token storage, OAuth is now production-ready and security-hardened for serious applications. + +FastMCP now supports out-of-the-box authentication with: +- **[WorkOS](https://gofastmcp.com/integrations/workos)** and **[AuthKit](https://gofastmcp.com/integrations/authkit)** +- **[GitHub](https://gofastmcp.com/integrations/github)** +- **[Google](https://gofastmcp.com/integrations/google)** +- **[Azure](https://gofastmcp.com/integrations/azure)** (Entra ID) +- **[AWS Cognito](https://gofastmcp.com/integrations/aws-cognito)** +- **[Auth0](https://gofastmcp.com/integrations/auth0)** +- **[Descope](https://gofastmcp.com/integrations/descope)** +- **[Scalekit](https://gofastmcp.com/integrations/scalekit)** +- **[JWTs](https://gofastmcp.com/servers/auth/token-verification#jwt-token-verification)** +- **[RFC 7662 token introspection](https://gofastmcp.com/servers/auth/token-verification#token-introspection-protocol)** + +⚡ **Response Caching Middleware** dramatically improves performance for expensive operations. Cache tool and resource responses with configurable TTLs, reducing redundant API calls and speeding up repeated queries. + +🔄 **Server lifespans** provide proper initialization and cleanup hooks that run once per server instance instead of per client session. This fixes a long-standing source of confusion in the MCP SDK and enables proper resource management for database connections, background tasks, and other server-level state. Note: this is a breaking behavioral change if you were using the `lifespan` parameter. + +✨ **Developer experience improvements** include Pydantic input validation for better type safety, icon support for richer UX, RFC 6570 query parameters for resource templates, improved Context API methods (list_resources, list_prompts, get_prompt), and async file/directory resources. + +This release includes contributions from **20** new contributors and represents the largest feature set in a while. Thank you to everyone who tested preview builds and filed issues - your feedback shaped these improvements! + +**Full Changelog**: [v2.12.5...v2.13.0](https://github.com/jlowin/fastmcp/compare/v2.12.5...v2.13.0) + + + + + +**[v2.12.5: Safety Pin](https://github.com/jlowin/fastmcp/releases/tag/v2.12.5)** + +FastMCP 2.12.5 is a point release that pins the MCP SDK version below 1.17, which introduced a change affecting FastMCP users with auth providers mounted as part of a larger application. This ensures the `.well-known` payload appears in the expected location when using FastMCP authentication providers with composite applications. + +## What's Changed + +### Fixes 🐞 +* Pin MCP SDK version below 1.17 by [@jlowin](https://github.com/jlowin) in [a1b2c3d](https://github.com/jlowin/fastmcp/commit/dab2b316ddc3883b7896a86da21cacb68da01e5c) + +**Full Changelog**: [v2.12.4...v2.12.5](https://github.com/jlowin/fastmcp/compare/v2.12.4...v2.12.5) + + + + + +**[v2.12.4: OIDC What You Did There](https://github.com/jlowin/fastmcp/releases/tag/v2.12.4)** + +FastMCP 2.12.4 adds comprehensive OIDC support and expands authentication options with AWS Cognito and Descope providers. The release also includes improvements to logging middleware, URL handling for nested resources, persistent OAuth client registration storage, and various fixes to the experimental OpenAPI parser. + +## What's Changed +### New Features 🎉 +* feat: Add support for OIDC configuration by [@ruhulio](https://github.com/ruhulio) in [#1817](https://github.com/jlowin/fastmcp/pull/1817) +### Enhancements 🔧 +* feat: Move the Starlette context middleware to the front by [@akkuman](https://github.com/akkuman) in [#1812](https://github.com/jlowin/fastmcp/pull/1812) +* Refactor Logging and Structured Logging Middleware by [@strawgate](https://github.com/strawgate) in [#1805](https://github.com/jlowin/fastmcp/pull/1805) +* Update pull_request_template.md by [@jlowin](https://github.com/jlowin) in [#1824](https://github.com/jlowin/fastmcp/pull/1824) +* chore: Set redirect_path default in function by [@ruhulio](https://github.com/ruhulio) in [#1833](https://github.com/jlowin/fastmcp/pull/1833) +* feat: Set instructions in code by [@attiks](https://github.com/attiks) in [#1838](https://github.com/jlowin/fastmcp/pull/1838) +* Automatically Create inline Snapshots by [@strawgate](https://github.com/strawgate) in [#1779](https://github.com/jlowin/fastmcp/pull/1779) +* chore: Cleanup Auth0 redirect_path initialization by [@ruhulio](https://github.com/ruhulio) in [#1842](https://github.com/jlowin/fastmcp/pull/1842) +* feat: Add support for Descope Authentication by [@anvibanga](https://github.com/anvibanga) in [#1853](https://github.com/jlowin/fastmcp/pull/1853) +* Update descope version badges by [@jlowin](https://github.com/jlowin) in [#1870](https://github.com/jlowin/fastmcp/pull/1870) +* Update welcome images by [@jlowin](https://github.com/jlowin) in [#1884](https://github.com/jlowin/fastmcp/pull/1884) +* Fix rounded edges of image by [@jlowin](https://github.com/jlowin) in [#1886](https://github.com/jlowin/fastmcp/pull/1886) +* optimize test suite by [@zzstoatzz](https://github.com/zzstoatzz) in [#1893](https://github.com/jlowin/fastmcp/pull/1893) +* Enhancement: client completions support context_arguments by [@isijoe](https://github.com/isijoe) in [#1906](https://github.com/jlowin/fastmcp/pull/1906) +* Update Descope icon by [@anvibanga](https://github.com/anvibanga) in [#1912](https://github.com/jlowin/fastmcp/pull/1912) +* Add AWS Cognito OAuth Provider for Enterprise Authentication by [@stephaneberle9](https://github.com/stephaneberle9) in [#1873](https://github.com/jlowin/fastmcp/pull/1873) +* Fix typos discovered by codespell by [@cclauss](https://github.com/cclauss) in [#1922](https://github.com/jlowin/fastmcp/pull/1922) +* Use lowercase namespace for fastmcp logger by [@jlowin](https://github.com/jlowin) in [#1791](https://github.com/jlowin/fastmcp/pull/1791) +### Fixes 🐞 +* Update quickstart.mdx by [@radi-dev](https://github.com/radi-dev) in [#1821](https://github.com/jlowin/fastmcp/pull/1821) +* Remove extraneous union import by [@jlowin](https://github.com/jlowin) in [#1823](https://github.com/jlowin/fastmcp/pull/1823) +* Delay import of Provider classes until FastMCP Server Creation by [@strawgate](https://github.com/strawgate) in [#1820](https://github.com/jlowin/fastmcp/pull/1820) +* fix: correct documentation link in deprecation warning by [@strawgate](https://github.com/strawgate) in [#1828](https://github.com/jlowin/fastmcp/pull/1828) +* fix: Increase default 3s timeout on Pytest by [@dacamposol](https://github.com/dacamposol) in [#1866](https://github.com/jlowin/fastmcp/pull/1866) +* fix: Improve URL handling in OIDCConfiguration by [@ruhulio](https://github.com/ruhulio) in [#1850](https://github.com/jlowin/fastmcp/pull/1850) +* fix: correct typing for on_read_resource middleware method by [@strawgate](https://github.com/strawgate) in [#1858](https://github.com/jlowin/fastmcp/pull/1858) +* feat(experimental/openapi): replace $ref in additionalProperties; add tests by [@jlowin](https://github.com/jlowin) in [#1735](https://github.com/jlowin/fastmcp/pull/1735) +* Honor client supplied scopes during registration by [@dmikusa](https://github.com/dmikusa) in [#1860](https://github.com/jlowin/fastmcp/pull/1860) +* Fix: FastAPI list parameter parsing in experimental OpenAPI parser by [@jlowin](https://github.com/jlowin) in [#1834](https://github.com/jlowin/fastmcp/pull/1834) +* Add log level support for stdio and HTTP transports by [@jlowin](https://github.com/jlowin) in [#1840](https://github.com/jlowin/fastmcp/pull/1840) +* Fix OAuth pre-flight check to accept HTTP 200 responses by [@jlowin](https://github.com/jlowin) in [#1874](https://github.com/jlowin/fastmcp/pull/1874) +* Fix: Preserve OpenAPI parameter descriptions in experimental parser by [@shlomo666](https://github.com/shlomo666) in [#1877](https://github.com/jlowin/fastmcp/pull/1877) +* Add persistent storage for OAuth client registrations by [@jlowin](https://github.com/jlowin) in [#1879](https://github.com/jlowin/fastmcp/pull/1879) +* docs: update release dates based on github releases by [@lodu](https://github.com/lodu) in [#1890](https://github.com/jlowin/fastmcp/pull/1890) +* Small updates to Sampling types by [@strawgate](https://github.com/strawgate) in [#1882](https://github.com/jlowin/fastmcp/pull/1882) +* remove lockfile smart_home example by [@zzstoatzz](https://github.com/zzstoatzz) in [#1892](https://github.com/jlowin/fastmcp/pull/1892) +* Fix: Remove JSON schema title metadata while preserving parameters named 'title' by [@jlowin](https://github.com/jlowin) in [#1872](https://github.com/jlowin/fastmcp/pull/1872) +* Fix: get_resource_url nested URL handling by [@raphael-linx](https://github.com/raphael-linx) in [#1914](https://github.com/jlowin/fastmcp/pull/1914) +* Clean up code for creating the resource url by [@jlowin](https://github.com/jlowin) in [#1916](https://github.com/jlowin/fastmcp/pull/1916) +* Fix route count logging in OpenAPI server by [@zzstoatzz](https://github.com/zzstoatzz) in [#1928](https://github.com/jlowin/fastmcp/pull/1928) +### Docs 📚 +* docs: make Gemini CLI integration discoverable by [@jackwotherspoon](https://github.com/jackwotherspoon) in [#1827](https://github.com/jlowin/fastmcp/pull/1827) +* docs: update NEW tags for AI assistant integrations by [@jackwotherspoon](https://github.com/jackwotherspoon) in [#1829](https://github.com/jlowin/fastmcp/pull/1829) +* Update wordmark by [@jlowin](https://github.com/jlowin) in [#1832](https://github.com/jlowin/fastmcp/pull/1832) +* docs: improve OAuth and OIDC Proxy documentation by [@jlowin](https://github.com/jlowin) in [#1880](https://github.com/jlowin/fastmcp/pull/1880) +* Update readme + welcome docs by [@jlowin](https://github.com/jlowin) in [#1883](https://github.com/jlowin/fastmcp/pull/1883) +* Update dark mode image in README by [@jlowin](https://github.com/jlowin) in [#1885](https://github.com/jlowin/fastmcp/pull/1885) + +## New Contributors +* [@radi-dev](https://github.com/radi-dev) made their first contribution in [#1821](https://github.com/jlowin/fastmcp/pull/1821) +* [@akkuman](https://github.com/akkuman) made their first contribution in [#1812](https://github.com/jlowin/fastmcp/pull/1812) +* [@ruhulio](https://github.com/ruhulio) made their first contribution in [#1817](https://github.com/jlowin/fastmcp/pull/1817) +* [@attiks](https://github.com/attiks) made their first contribution in [#1838](https://github.com/jlowin/fastmcp/pull/1838) +* [@anvibanga](https://github.com/anvibanga) made their first contribution in [#1853](https://github.com/jlowin/fastmcp/pull/1853) +* [@shlomo666](https://github.com/shlomo666) made their first contribution in [#1877](https://github.com/jlowin/fastmcp/pull/1877) +* [@lodu](https://github.com/lodu) made their first contribution in [#1890](https://github.com/jlowin/fastmcp/pull/1890) +* [@isijoe](https://github.com/isijoe) made their first contribution in [#1906](https://github.com/jlowin/fastmcp/pull/1906) +* [@raphael-linx](https://github.com/raphael-linx) made their first contribution in [#1914](https://github.com/jlowin/fastmcp/pull/1914) +* [@stephaneberle9](https://github.com/stephaneberle9) made their first contribution in [#1873](https://github.com/jlowin/fastmcp/pull/1873) +* [@cclauss](https://github.com/cclauss) made their first contribution in [#1922](https://github.com/jlowin/fastmcp/pull/1922) + +**Full Changelog**: [v2.12.3...v2.12.4](https://github.com/jlowin/fastmcp/compare/v2.12.3...v2.12.4) + + + + + +**[v2.12.3: Double Time](https://github.com/jlowin/fastmcp/releases/tag/v2.12.3)** + +FastMCP 2.12.3 focuses on performance and developer experience improvements based on community feedback. This release includes optimized auth provider imports that reduce server startup time, enhanced OIDC authentication flows with proper token management, and several reliability fixes for OAuth proxy configurations. The addition of automatic inline snapshot creation significantly improves the testing experience for contributors. + +## What's Changed +### New Features 🎉 +* feat: Support setting MCP log level via transport configuration by [@jlowin](https://github.com/jlowin) in [#1756](https://github.com/jlowin/fastmcp/pull/1756) +### Enhancements 🔧 +* Add client-side auth support for mcp install cursor command by [@jlowin](https://github.com/jlowin) in [#1747](https://github.com/jlowin/fastmcp/pull/1747) +* Automatically Create inline Snapshots by [@strawgate](https://github.com/strawgate) in [#1779](https://github.com/jlowin/fastmcp/pull/1779) +* Use lowercase namespace for fastmcp logger by [@jlowin](https://github.com/jlowin) in [#1791](https://github.com/jlowin/fastmcp/pull/1791) +### Fixes 🐞 +* fix: correct merge mistake during auth0 refactor by [@strawgate](https://github.com/strawgate) in [#1742](https://github.com/jlowin/fastmcp/pull/1742) +* Remove extraneous union import by [@jlowin](https://github.com/jlowin) in [#1823](https://github.com/jlowin/fastmcp/pull/1823) +* Delay import of Provider classes until FastMCP Server Creation by [@strawgate](https://github.com/strawgate) in [#1820](https://github.com/jlowin/fastmcp/pull/1820) +* fix: refactor OIDC configuration provider for proper token management by [@strawgate](https://github.com/strawgate) in [#1751](https://github.com/jlowin/fastmcp/pull/1751) +* Fix smart_home example imports by [@strawgate](https://github.com/strawgate) in [#1753](https://github.com/jlowin/fastmcp/pull/1753) +* fix: correct oauth proxy initialization of client by [@strawgate](https://github.com/strawgate) in [#1759](https://github.com/jlowin/fastmcp/pull/1759) +* Fix: return empty string when prompts have no arguments by [@jlowin](https://github.com/jlowin) in [#1766](https://github.com/jlowin/fastmcp/pull/1766) +* Fix async server callbacks by [@strawgate](https://github.com/strawgate) in [#1774](https://github.com/jlowin/fastmcp/pull/1774) +* Fix error when retrieving Completion API errors by [@strawgate](https://github.com/strawgate) in [#1785](https://github.com/jlowin/fastmcp/pull/1785) +* fix: correct documentation link in deprecation warning by [@strawgate](https://github.com/strawgate) in [#1828](https://github.com/jlowin/fastmcp/pull/1828) +### Docs 📚 +* Add migration docs for 2.12 by [@jlowin](https://github.com/jlowin) in [#1745](https://github.com/jlowin/fastmcp/pull/1745) +* Update docs for default sampling implementation to mention OpenAI API Key by [@strawgate](https://github.com/strawgate) in [#1763](https://github.com/jlowin/fastmcp/pull/1763) +* Add tip about sampling prompts and user_context to sampling documentation by [@jlowin](https://github.com/jlowin) in [#1764](https://github.com/jlowin/fastmcp/pull/1764) +* Update quickstart.mdx by [@radi-dev](https://github.com/radi-dev) in [#1821](https://github.com/jlowin/fastmcp/pull/1821) +### Other Changes 🦾 +* Replace Marvin with Claude Code in CI by [@jlowin](https://github.com/jlowin) in [#1800](https://github.com/jlowin/fastmcp/pull/1800) +* Refactor logging and structured logging middleware by [@strawgate](https://github.com/strawgate) in [#1805](https://github.com/jlowin/fastmcp/pull/1805) +* feat: Move the Starlette context middleware to the front by [@akkuman](https://github.com/akkuman) in [#1812](https://github.com/jlowin/fastmcp/pull/1812) +* feat: Add support for OIDC configuration by [@ruhulio](https://github.com/ruhulio) in [#1817](https://github.com/jlowin/fastmcp/pull/1817) + +## New Contributors +* [@radi-dev](https://github.com/radi-dev) made their first contribution in [#1821](https://github.com/jlowin/fastmcp/pull/1821) +* [@akkuman](https://github.com/akkuman) made their first contribution in [#1812](https://github.com/jlowin/fastmcp/pull/1812) +* [@ruhulio](https://github.com/ruhulio) made their first contribution in [#1817](https://github.com/jlowin/fastmcp/pull/1817) + +**Full Changelog**: [v2.12.2...v2.12.3](https://github.com/jlowin/fastmcp/compare/v2.12.2...v2.12.3) + + + + + +**[v2.12.2: Perchance to Stream](https://github.com/jlowin/fastmcp/releases/tag/v2.12.2)** + +This is a hotfix for a bug where the `streamable-http` transport was not recognized as a valid option in `fastmcp.json` configuration files, despite being supported by the CLI. This resulted in a parsing error when the CLI arguments were merged against the configuration spec. + +## What's Changed +### Fixes 🐞 +* Fix streamable-http transport validation in fastmcp.json config by [@jlowin](https://github.com/jlowin) in [#1739](https://github.com/jlowin/fastmcp/pull/1739) + +**Full Changelog**: [v2.12.1...v2.12.2](https://github.com/jlowin/fastmcp/compare/v2.12.1...v2.12.2) + + + + + +**[v2.12.1: OAuth to Joy](https://github.com/jlowin/fastmcp/releases/tag/v2.12.1)** + +FastMCP 2.12.1 strengthens the OAuth proxy implementation based on extensive community testing and feedback. This release improves client storage reliability, adds PKCE forwarding for enhanced security, introduces configurable token endpoint authentication methods, and expands scope handling—all addressing real-world integration challenges discovered since 2.12.0. The enhanced test suite with mock providers ensures these improvements are robust and maintainable. + +## Breaking Changes +- **OAuth Proxy**: Users of built-in IDP integrations should note that `resource_server_url` has been renamed to `base_url` for clarity and consistency + +## What's Changed +### Enhancements 🔧 +* Make openai dependency optional by [@jlowin](https://github.com/jlowin) in [#1701](https://github.com/jlowin/fastmcp/pull/1701) +* Remove orphaned OAuth proxy code by [@jlowin](https://github.com/jlowin) in [#1722](https://github.com/jlowin/fastmcp/pull/1722) +* Expose valid scopes from OAuthProxy metadata by [@dmikusa](https://github.com/dmikusa) in [#1717](https://github.com/jlowin/fastmcp/pull/1717) +* OAuth proxy PKCE forwarding by [@jlowin](https://github.com/jlowin) in [#1733](https://github.com/jlowin/fastmcp/pull/1733) +* Add token_endpoint_auth_method parameter to OAuthProxy by [@jlowin](https://github.com/jlowin) in [#1736](https://github.com/jlowin/fastmcp/pull/1736) +* Clean up and enhance OAuth proxy tests with mock provider by [@jlowin](https://github.com/jlowin) in [#1738](https://github.com/jlowin/fastmcp/pull/1738) +### Fixes 🐞 +* refactor: replace auth provider registry with ImportString by [@jlowin](https://github.com/jlowin) in [#1710](https://github.com/jlowin/fastmcp/pull/1710) +* Fix OAuth resource URL handling and WWW-Authenticate header by [@jlowin](https://github.com/jlowin) in [#1706](https://github.com/jlowin/fastmcp/pull/1706) +* Fix OAuth proxy client storage and add retry logic by [@jlowin](https://github.com/jlowin) in [#1732](https://github.com/jlowin/fastmcp/pull/1732) +### Docs 📚 +* Fix documentation: use StreamableHttpTransport for headers in testing by [@jlowin](https://github.com/jlowin) in [#1702](https://github.com/jlowin/fastmcp/pull/1702) +* docs: add performance warnings for mounted servers and proxies by [@strawgate](https://github.com/strawgate) in [#1669](https://github.com/jlowin/fastmcp/pull/1669) +* Update documentation around scopes for google by [@jlowin](https://github.com/jlowin) in [#1703](https://github.com/jlowin/fastmcp/pull/1703) +* Add deployment information to quickstart by [@seanpwlms](https://github.com/seanpwlms) in [#1433](https://github.com/jlowin/fastmcp/pull/1433) +* Update quickstart by [@jlowin](https://github.com/jlowin) in [#1728](https://github.com/jlowin/fastmcp/pull/1728) +* Add development docs for FastMCP by [@jlowin](https://github.com/jlowin) in [#1719](https://github.com/jlowin/fastmcp/pull/1719) +### Other Changes 🦾 +* Set generics without bounds to default=Any by [@strawgate](https://github.com/strawgate) in [#1648](https://github.com/jlowin/fastmcp/pull/1648) + +## New Contributors +* [@dmikusa](https://github.com/dmikusa) made their first contribution in [#1717](https://github.com/jlowin/fastmcp/pull/1717) +* [@seanpwlms](https://github.com/seanpwlms) made their first contribution in [#1433](https://github.com/jlowin/fastmcp/pull/1433) + +**Full Changelog**: [v2.12.0...v2.12.1](https://github.com/jlowin/fastmcp/compare/v2.12.0...v2.12.1) + + + + + +**[v2.12.0: Auth to the Races](https://github.com/jlowin/fastmcp/releases/tag/v2.12.0)** + +FastMCP 2.12 represents one of our most significant releases to date, both in scope and community involvement. After extensive testing and iteration with the community, we're shipping major improvements to authentication, configuration, and MCP feature adoption. + +🔐 **OAuth Proxy for Broader Provider Support** addresses a fundamental challenge: while MCP requires Dynamic Client Registration (DCR), many popular OAuth providers don't support it. The new OAuth proxy bridges this gap, enabling FastMCP servers to authenticate with providers like GitHub, Google, WorkOS, and Azure through minimal configuration. These native integrations ship today, with more providers planned based on community needs. + +📋 **Declarative JSON Configuration** introduces a standardized, portable way to describe and deploy MCP servers. The `fastmcp.json` configuration file becomes the single source of truth for dependencies, transport settings, entrypoints, and server metadata. This foundation sets the stage for future capabilities like transformations and remote sources, moving toward a world where MCP servers are as portable and shareable as container images. + +🧠 **Sampling API Fallback** tackles the chicken-and-egg problem limiting adoption of advanced MCP features. Sampling—where servers request LLM completions from clients—is powerful but underutilized due to limited client support. FastMCP now lets server authors define fallback handlers that generate sampling completions server-side when clients don't support the feature, encouraging adoption while maintaining compatibility. + +This release took longer than usual to ship, and for good reason: the community's aggressive testing and feedback on the authentication system helped us reach a level of stability we're confident in. There's certainly more work ahead, but these foundations position FastMCP to handle increasingly complex use cases while remaining approachable for developers. + +Thank you to our new contributors and everyone who tested preview builds. Your feedback directly shaped these features. + +## What's Changed +### New Features 🎉 +* Add OAuth proxy that allows authentication with social IDPs without DCR support by [@jlowin](https://github.com/jlowin) in [#1434](https://github.com/jlowin/fastmcp/pull/1434) +* feat: introduce declarative JSON configuration system by [@jlowin](https://github.com/jlowin) in [#1517](https://github.com/jlowin/fastmcp/pull/1517) +* ✨ Fallback to a Completions API when Sampling is not available by [@strawgate](https://github.com/strawgate) in [#1145](https://github.com/jlowin/fastmcp/pull/1145) +* Implement typed source system for FastMCP declarative configuration by [@jlowin](https://github.com/jlowin) in [#1607](https://github.com/jlowin/fastmcp/pull/1607) +### Enhancements 🔧 +* Support importing custom_route endpoints when mounting servers by [@jlowin](https://github.com/jlowin) in [#1470](https://github.com/jlowin/fastmcp/pull/1470) +* Remove unnecessary asserts by [@jlowin](https://github.com/jlowin) in [#1484](https://github.com/jlowin/fastmcp/pull/1484) +* Add Claude issue triage by [@jlowin](https://github.com/jlowin) in [#1510](https://github.com/jlowin/fastmcp/pull/1510) +* Inline dedupe prompt by [@jlowin](https://github.com/jlowin) in [#1512](https://github.com/jlowin/fastmcp/pull/1512) +* Improve stdio and mcp_config clean-up by [@strawgate](https://github.com/strawgate) in [#1444](https://github.com/jlowin/fastmcp/pull/1444) +* involve kwargs to pass parameters on creating RichHandler for logging customization. by [@itaru2622](https://github.com/itaru2622) in [#1504](https://github.com/jlowin/fastmcp/pull/1504) +* Move SDK docs generation to post-merge workflow by [@jlowin](https://github.com/jlowin) in [#1513](https://github.com/jlowin/fastmcp/pull/1513) +* Improve label triage guidance by [@jlowin](https://github.com/jlowin) in [#1516](https://github.com/jlowin/fastmcp/pull/1516) +* Add code review guidelines for agents by [@jlowin](https://github.com/jlowin) in [#1520](https://github.com/jlowin/fastmcp/pull/1520) +* Remove trailing slash in unit tests by [@jlowin](https://github.com/jlowin) in [#1535](https://github.com/jlowin/fastmcp/pull/1535) +* Update OAuth callback UI branding by [@jlowin](https://github.com/jlowin) in [#1536](https://github.com/jlowin/fastmcp/pull/1536) +* Fix Marvin workflow to support development tools by [@jlowin](https://github.com/jlowin) in [#1537](https://github.com/jlowin/fastmcp/pull/1537) +* Add mounted_components_raise_on_load_error setting for debugging by [@jlowin](https://github.com/jlowin) in [#1534](https://github.com/jlowin/fastmcp/pull/1534) +* feat: Add --workspace flag to fastmcp install cursor by [@jlowin](https://github.com/jlowin) in [#1522](https://github.com/jlowin/fastmcp/pull/1522) +* switch from `pyright` to `ty` by [@zzstoatzz](https://github.com/zzstoatzz) in [#1545](https://github.com/jlowin/fastmcp/pull/1545) +* feat: trigger Marvin workflow on PR body content by [@jlowin](https://github.com/jlowin) in [#1549](https://github.com/jlowin/fastmcp/pull/1549) +* Add WorkOS and Azure OAuth providers by [@jlowin](https://github.com/jlowin) in [#1550](https://github.com/jlowin/fastmcp/pull/1550) +* Adjust timeout for slow MCP Server shutdown test by [@strawgate](https://github.com/strawgate) in [#1561](https://github.com/jlowin/fastmcp/pull/1561) +* Update banner by [@jlowin](https://github.com/jlowin) in [#1567](https://github.com/jlowin/fastmcp/pull/1567) +* Added import of AuthProxy to auth __init__ by [@KaliszS](https://github.com/KaliszS) in [#1568](https://github.com/jlowin/fastmcp/pull/1568) +* Add configurable redirect URI validation for OAuth providers by [@jlowin](https://github.com/jlowin) in [#1582](https://github.com/jlowin/fastmcp/pull/1582) +* Remove invalid-argument-type ignore and fix type errors by [@jlowin](https://github.com/jlowin) in [#1588](https://github.com/jlowin/fastmcp/pull/1588) +* Remove generate-schema from public CLI by [@jlowin](https://github.com/jlowin) in [#1591](https://github.com/jlowin/fastmcp/pull/1591) +* Skip flaky windows test / mulit-client garbage collection by [@jlowin](https://github.com/jlowin) in [#1592](https://github.com/jlowin/fastmcp/pull/1592) +* Add setting to disable logging configuration by [@isra17](https://github.com/isra17) in [#1575](https://github.com/jlowin/fastmcp/pull/1575) +* Improve debug logging for nested Servers / Clients by [@strawgate](https://github.com/strawgate) in [#1604](https://github.com/jlowin/fastmcp/pull/1604) +* Add GitHub pull request template by [@strawgate](https://github.com/strawgate) in [#1581](https://github.com/jlowin/fastmcp/pull/1581) +* chore: Automate docs and schema updates via PRs by [@jlowin](https://github.com/jlowin) in [#1611](https://github.com/jlowin/fastmcp/pull/1611) +* Experiment with haiku for limited workflows by [@jlowin](https://github.com/jlowin) in [#1613](https://github.com/jlowin/fastmcp/pull/1613) +* feat: Improve GitHub workflow automation for schema and SDK docs by [@jlowin](https://github.com/jlowin) in [#1615](https://github.com/jlowin/fastmcp/pull/1615) +* Consolidate server loading logic into FileSystemSource by [@jlowin](https://github.com/jlowin) in [#1614](https://github.com/jlowin/fastmcp/pull/1614) +* Prevent Haiku Marvin from commenting when there are no duplicates by [@jlowin](https://github.com/jlowin) in [#1622](https://github.com/jlowin/fastmcp/pull/1622) +* chore: Add clarifying note to automated PR bodies by [@jlowin](https://github.com/jlowin) in [#1623](https://github.com/jlowin/fastmcp/pull/1623) +* feat: introduce inline snapshots by [@strawgate](https://github.com/strawgate) in [#1605](https://github.com/jlowin/fastmcp/pull/1605) +* Improve fastmcp.json environment configuration and project-based deployments by [@jlowin](https://github.com/jlowin) in [#1631](https://github.com/jlowin/fastmcp/pull/1631) +* fix: allow passing query params in OAuthProxy upstream authorization url by [@danb27](https://github.com/danb27) in [#1630](https://github.com/jlowin/fastmcp/pull/1630) +* Support multiple --with-editable flags in CLI commands by [@jlowin](https://github.com/jlowin) in [#1634](https://github.com/jlowin/fastmcp/pull/1634) +* feat: support comma separated oauth scopes by [@jlowin](https://github.com/jlowin) in [#1642](https://github.com/jlowin/fastmcp/pull/1642) +* Add allowed_client_redirect_uris to OAuth provider subclasses by [@jlowin](https://github.com/jlowin) in [#1662](https://github.com/jlowin/fastmcp/pull/1662) +* Consolidate CLI config parsing and prevent infinite loops by [@jlowin](https://github.com/jlowin) in [#1660](https://github.com/jlowin/fastmcp/pull/1660) +* Internal refactor: mcp server config by [@jlowin](https://github.com/jlowin) in [#1672](https://github.com/jlowin/fastmcp/pull/1672) +* Refactor Environment to support multiple runtime types by [@jlowin](https://github.com/jlowin) in [#1673](https://github.com/jlowin/fastmcp/pull/1673) +* Add type field to Environment base class by [@jlowin](https://github.com/jlowin) in [#1676](https://github.com/jlowin/fastmcp/pull/1676) +### Fixes 🐞 +* Fix breaking change: restore output_schema=False compatibility by [@jlowin](https://github.com/jlowin) in [#1482](https://github.com/jlowin/fastmcp/pull/1482) +* Fix #1506: Update tool filtering documentation from _meta to meta by [@maybenotconnor](https://github.com/maybenotconnor) in [#1511](https://github.com/jlowin/fastmcp/pull/1511) +* Fix pytest warnings by [@jlowin](https://github.com/jlowin) in [#1559](https://github.com/jlowin/fastmcp/pull/1559) +* nest schemas under assets by [@jlowin](https://github.com/jlowin) in [#1593](https://github.com/jlowin/fastmcp/pull/1593) +* Skip flaky windows test by [@jlowin](https://github.com/jlowin) in [#1596](https://github.com/jlowin/fastmcp/pull/1596) +* ACTUALLY move schemas to fastmcp.json by [@jlowin](https://github.com/jlowin) in [#1597](https://github.com/jlowin/fastmcp/pull/1597) +* Fix and centralize CLI path resolution by [@jlowin](https://github.com/jlowin) in [#1590](https://github.com/jlowin/fastmcp/pull/1590) +* Remove client info modifications by [@jlowin](https://github.com/jlowin) in [#1620](https://github.com/jlowin/fastmcp/pull/1620) +* Fix $defs being discarded in input schema of transformed tool by [@pldesch-chift](https://github.com/pldesch-chift) in [#1578](https://github.com/jlowin/fastmcp/pull/1578) +* Fix enum elicitation to use inline schemas for MCP compatibility by [@jlowin](https://github.com/jlowin) in [#1632](https://github.com/jlowin/fastmcp/pull/1632) +* Reuse session for `StdioTransport` in `Client.new` by [@strawgate](https://github.com/strawgate) in [#1635](https://github.com/jlowin/fastmcp/pull/1635) +* Feat: Configurable LoggingMiddleware payload serialization by [@vl-kp](https://github.com/vl-kp) in [#1636](https://github.com/jlowin/fastmcp/pull/1636) +* Fix OAuth redirect URI validation for DCR compatibility by [@jlowin](https://github.com/jlowin) in [#1661](https://github.com/jlowin/fastmcp/pull/1661) +* Add default scope handling in OAuth proxy by [@romanusyk](https://github.com/romanusyk) in [#1667](https://github.com/jlowin/fastmcp/pull/1667) +* Fix OAuth token expiry handling by [@jlowin](https://github.com/jlowin) in [#1671](https://github.com/jlowin/fastmcp/pull/1671) +* Add resource_server_url parameter to OAuth proxy providers by [@jlowin](https://github.com/jlowin) in [#1682](https://github.com/jlowin/fastmcp/pull/1682) +### Breaking Changes 🛫 +* Enhance inspect command with structured output and format options by [@jlowin](https://github.com/jlowin) in [#1481](https://github.com/jlowin/fastmcp/pull/1481) +### Docs 📚 +* Update changelog by [@jlowin](https://github.com/jlowin) in [#1453](https://github.com/jlowin/fastmcp/pull/1453) +* Update banner by [@jlowin](https://github.com/jlowin) in [#1472](https://github.com/jlowin/fastmcp/pull/1472) +* Update logo files by [@jlowin](https://github.com/jlowin) in [#1473](https://github.com/jlowin/fastmcp/pull/1473) +* Update deployment docs by [@jlowin](https://github.com/jlowin) in [#1486](https://github.com/jlowin/fastmcp/pull/1486) +* Update FastMCP Cloud screenshot by [@jlowin](https://github.com/jlowin) in [#1487](https://github.com/jlowin/fastmcp/pull/1487) +* Update authentication note in docs by [@jlowin](https://github.com/jlowin) in [#1488](https://github.com/jlowin/fastmcp/pull/1488) +* chore: Update installation.mdx version snippet by [@thomas-te](https://github.com/thomas-te) in [#1496](https://github.com/jlowin/fastmcp/pull/1496) +* Update fastmcp cloud server requirements by [@jlowin](https://github.com/jlowin) in [#1497](https://github.com/jlowin/fastmcp/pull/1497) +* Fix oauth pyright type checking by [@strawgate](https://github.com/strawgate) in [#1498](https://github.com/jlowin/fastmcp/pull/1498) +* docs: Fix type annotation in return value documentation by [@MaikelVeen](https://github.com/MaikelVeen) in [#1499](https://github.com/jlowin/fastmcp/pull/1499) +* Fix PromptMessage usage in docs example by [@jlowin](https://github.com/jlowin) in [#1515](https://github.com/jlowin/fastmcp/pull/1515) +* Create CODE_OF_CONDUCT.md by [@jlowin](https://github.com/jlowin) in [#1523](https://github.com/jlowin/fastmcp/pull/1523) +* Fixed wrong import path in new docs page by [@KaliszS](https://github.com/KaliszS) in [#1538](https://github.com/jlowin/fastmcp/pull/1538) +* Document symmetric key JWT verification support by [@jlowin](https://github.com/jlowin) in [#1586](https://github.com/jlowin/fastmcp/pull/1586) +* Update fastmcp.json schema path by [@jlowin](https://github.com/jlowin) in [#1595](https://github.com/jlowin/fastmcp/pull/1595) +### Dependencies 📦 +* Bump actions/create-github-app-token from 1 to 2 by [@dependabot](https://github.com/dependabot)[bot] in [#1436](https://github.com/jlowin/fastmcp/pull/1436) +* Bump astral-sh/setup-uv from 4 to 6 by [@dependabot](https://github.com/dependabot)[bot] in [#1532](https://github.com/jlowin/fastmcp/pull/1532) +* Bump actions/checkout from 4 to 5 by [@dependabot](https://github.com/dependabot)[bot] in [#1533](https://github.com/jlowin/fastmcp/pull/1533) +### Other Changes 🦾 +* Add dedupe workflow by [@jlowin](https://github.com/jlowin) in [#1454](https://github.com/jlowin/fastmcp/pull/1454) +* Update AGENTS.md by [@jlowin](https://github.com/jlowin) in [#1471](https://github.com/jlowin/fastmcp/pull/1471) +* Give Marvin the power of the Internet by [@strawgate](https://github.com/strawgate) in [#1475](https://github.com/jlowin/fastmcp/pull/1475) +* Update `just` error message for static checks by [@jlowin](https://github.com/jlowin) in [#1483](https://github.com/jlowin/fastmcp/pull/1483) +* Remove labeler by [@jlowin](https://github.com/jlowin) in [#1509](https://github.com/jlowin/fastmcp/pull/1509) +* update aproto server to handle rich links by [@zzstoatzz](https://github.com/zzstoatzz) in [#1556](https://github.com/jlowin/fastmcp/pull/1556) +* fix: enable triage bot for fork PRs using pull_request_target by [@jlowin](https://github.com/jlowin) in [#1557](https://github.com/jlowin/fastmcp/pull/1557) + +## New Contributors +* [@thomas-te](https://github.com/thomas-te) made their first contribution in [#1496](https://github.com/jlowin/fastmcp/pull/1496) +* [@maybenotconnor](https://github.com/maybenotconnor) made their first contribution in [#1511](https://github.com/jlowin/fastmcp/pull/1511) +* [@MaikelVeen](https://github.com/MaikelVeen) made their first contribution in [#1499](https://github.com/jlowin/fastmcp/pull/1499) +* [@KaliszS](https://github.com/KaliszS) made their first contribution in [#1538](https://github.com/jlowin/fastmcp/pull/1538) +* [@isra17](https://github.com/isra17) made their first contribution in [#1575](https://github.com/jlowin/fastmcp/pull/1575) +* [@marvin-context-protocol](https://github.com/marvin-context-protocol)[bot] made their first contribution in [#1616](https://github.com/jlowin/fastmcp/pull/1616) +* [@pldesch-chift](https://github.com/pldesch-chift) made their first contribution in [#1578](https://github.com/jlowin/fastmcp/pull/1578) +* [@vl-kp](https://github.com/vl-kp) made their first contribution in [#1636](https://github.com/jlowin/fastmcp/pull/1636) +* [@romanusyk](https://github.com/romanusyk) made their first contribution in [#1667](https://github.com/jlowin/fastmcp/pull/1667) + +**Full Changelog**: [v2.11.3...v2.12.0](https://github.com/jlowin/fastmcp/compare/v2.11.3...v2.12.0) + + + + + +**[v2.11.3: API-tite for Change](https://github.com/jlowin/fastmcp/releases/tag/v2.11.3)** + +This release includes significant enhancements to the experimental OpenAPI parser and fixes a significant bug that led schemas not to be included in input/output schemas if they were transitive dependencies (e.g. A → B → C implies A depends on C). For users naively transforming large OpenAPI specs into MCP servers, this may result in ballooning payload sizes and necessitate curation. + +## What's Changed +### Enhancements 🔧 +* Improve redirect handling to address 307's by [@jlowin](https://github.com/jlowin) in [#1387](https://github.com/jlowin/fastmcp/pull/1387) +* Ensure resource + template names are properly prefixed when importing/mounting by [@jlowin](https://github.com/jlowin) in [#1423](https://github.com/jlowin/fastmcp/pull/1423) +* fixes #1398: Add JWT claims to AccessToken by [@panargirakis](https://github.com/panargirakis) in [#1399](https://github.com/jlowin/fastmcp/pull/1399) +* Enable Protected Resource Metadata to provide resource_name and resou… by [@yannj-fr](https://github.com/yannj-fr) in [#1371](https://github.com/jlowin/fastmcp/pull/1371) +* Pin mcp SDK under 2.0 to avoid breaking changes by [@jlowin](https://github.com/jlowin) in [#1428](https://github.com/jlowin/fastmcp/pull/1428) +* Clean up complexity from PR #1426 by [@jlowin](https://github.com/jlowin) in [#1435](https://github.com/jlowin/fastmcp/pull/1435) +* Optimize OpenAPI payload size by 46% by [@jlowin](https://github.com/jlowin) in [#1452](https://github.com/jlowin/fastmcp/pull/1452) +* Update static checks by [@jlowin](https://github.com/jlowin) in [#1448](https://github.com/jlowin/fastmcp/pull/1448) +### Fixes 🐞 +* Fix client-side logging bug #1394 by [@chi2liu](https://github.com/chi2liu) in [#1397](https://github.com/jlowin/fastmcp/pull/1397) +* fix: Fix httpx_client_factory type annotation to match MCP SDK (#1402) by [@chi2liu](https://github.com/chi2liu) in [#1405](https://github.com/jlowin/fastmcp/pull/1405) +* Fix OpenAPI allOf handling at requestBody top level (#1378) by [@chi2liu](https://github.com/chi2liu) in [#1425](https://github.com/jlowin/fastmcp/pull/1425) +* Fix OpenAPI transitive references and performance (#1372) by [@jlowin](https://github.com/jlowin) in [#1426](https://github.com/jlowin/fastmcp/pull/1426) +* fix(type): lifespan is partially unknown by [@ykun9](https://github.com/ykun9) in [#1389](https://github.com/jlowin/fastmcp/pull/1389) +* Ensure transformed tools generate structured content by [@jlowin](https://github.com/jlowin) in [#1443](https://github.com/jlowin/fastmcp/pull/1443) +### Docs 📚 +* docs(client/logging): reflect corrected default log level mapping by [@jlowin](https://github.com/jlowin) in [#1403](https://github.com/jlowin/fastmcp/pull/1403) +* Add documentation for get_access_token() dependency function by [@jlowin](https://github.com/jlowin) in [#1446](https://github.com/jlowin/fastmcp/pull/1446) +### Other Changes 🦾 +* Add comprehensive tests for utilities.components module by [@chi2liu](https://github.com/chi2liu) in [#1395](https://github.com/jlowin/fastmcp/pull/1395) +* Consolidate agent instructions into AGENTS.md by [@jlowin](https://github.com/jlowin) in [#1404](https://github.com/jlowin/fastmcp/pull/1404) +* Fix performance test threshold to prevent flaky failures by [@jlowin](https://github.com/jlowin) in [#1406](https://github.com/jlowin/fastmcp/pull/1406) +* Update agents.md; add github instructions by [@jlowin](https://github.com/jlowin) in [#1410](https://github.com/jlowin/fastmcp/pull/1410) +* Add Marvin assistant by [@jlowin](https://github.com/jlowin) in [#1412](https://github.com/jlowin/fastmcp/pull/1412) +* Marvin: fix deprecated variable names by [@jlowin](https://github.com/jlowin) in [#1417](https://github.com/jlowin/fastmcp/pull/1417) +* Simplify action setup and add github tools for Marvin by [@jlowin](https://github.com/jlowin) in [#1419](https://github.com/jlowin/fastmcp/pull/1419) +* Update marvin workflow name by [@jlowin](https://github.com/jlowin) in [#1421](https://github.com/jlowin/fastmcp/pull/1421) +* Improve GitHub templates by [@jlowin](https://github.com/jlowin) in [#1422](https://github.com/jlowin/fastmcp/pull/1422) + +## New Contributors +* [@panargirakis](https://github.com/panargirakis) made their first contribution in [#1399](https://github.com/jlowin/fastmcp/pull/1399) +* [@ykun9](https://github.com/ykun9) made their first contribution in [#1389](https://github.com/jlowin/fastmcp/pull/1389) +* [@yannj-fr](https://github.com/yannj-fr) made their first contribution in [#1371](https://github.com/jlowin/fastmcp/pull/1371) + +**Full Changelog**: [v2.11.2...v2.11.3](https://github.com/jlowin/fastmcp/compare/v2.11.2...v2.11.3) + + + + + +## [v2.11.2: Satis-factory](https://github.com/jlowin/fastmcp/releases/tag/v2.11.2) + +## What's Changed +### Enhancements 🔧 +* Support factory functions in fastmcp run by [@jlowin](https://github.com/jlowin) in [#1384](https://github.com/jlowin/fastmcp/pull/1384) +* Add async support to client_factory in FastMCPProxy (#1286) by [@bianning](https://github.com/bianning) in [#1375](https://github.com/jlowin/fastmcp/pull/1375) +### Fixes 🐞 +* Fix server_version field in inspect manifest by [@jlowin](https://github.com/jlowin) in [#1383](https://github.com/jlowin/fastmcp/pull/1383) +* Fix Settings field with both default and default_factory by [@jlowin](https://github.com/jlowin) in [#1380](https://github.com/jlowin/fastmcp/pull/1380) +### Other Changes 🦾 +* Remove unused arg by [@jlowin](https://github.com/jlowin) in [#1382](https://github.com/jlowin/fastmcp/pull/1382) +* Add remote auth provider tests by [@jlowin](https://github.com/jlowin) in [#1351](https://github.com/jlowin/fastmcp/pull/1351) + +## New Contributors +* [@bianning](https://github.com/bianning) made their first contribution in [#1375](https://github.com/jlowin/fastmcp/pull/1375) + +**Full Changelog**: [v2.11.1...v2.11.2](https://github.com/jlowin/fastmcp/compare/v2.11.1...v2.11.2) + + + + + +## [v2.11.1: You're Better Auth Now](https://github.com/jlowin/fastmcp/releases/tag/v2.11.1) + +## What's Changed +### New Features 🎉 +* Introduce `RemoteAuthProvider` for cleaner external identity provider integration, update docs by [@jlowin](https://github.com/jlowin) in [#1346](https://github.com/jlowin/fastmcp/pull/1346) +### Enhancements 🔧 +* perf: optimize string operations in OpenAPI parameter processing by [@chi2liu](https://github.com/chi2liu) in [#1342](https://github.com/jlowin/fastmcp/pull/1342) +### Fixes 🐞 +* Fix method-bound FunctionTool schemas by [@strawgate](https://github.com/strawgate) in [#1360](https://github.com/jlowin/fastmcp/pull/1360) +* Manually set `_key` after `model_copy()` to enable prefixing Transformed Tools by [@strawgate](https://github.com/strawgate) in [#1357](https://github.com/jlowin/fastmcp/pull/1357) +### Docs 📚 +* Docs updates by [@jlowin](https://github.com/jlowin) in [#1336](https://github.com/jlowin/fastmcp/pull/1336) +* Add 2.11 to changelog by [@jlowin](https://github.com/jlowin) in [#1337](https://github.com/jlowin/fastmcp/pull/1337) +* Update AuthKit vocab by [@jlowin](https://github.com/jlowin) in [#1338](https://github.com/jlowin/fastmcp/pull/1338) +* Fix typo in decorating-methods.mdx by [@Ozzuke](https://github.com/Ozzuke) in [#1344](https://github.com/jlowin/fastmcp/pull/1344) + +## New Contributors +* [@Ozzuke](https://github.com/Ozzuke) made their first contribution in [#1344](https://github.com/jlowin/fastmcp/pull/1344) + +**Full Changelog**: [v2.11.0...v2.11.1](https://github.com/jlowin/fastmcp/compare/v2.11.0...v2.11.1) + + + + + +## [v2.11.0: Auth to a Good Start](https://github.com/jlowin/fastmcp/releases/tag/v2.11.0) + +FastMCP 2.11 doubles down on what developers need most: speed and simplicity. This massive release delivers significant performance improvements and a dramatically better developer experience. + +🔐 **Enterprise-Ready Authentication** brings comprehensive OAuth 2.1 support with WorkOS's AuthKit integration. The new AuthProvider interface leverages MCP's support for separate resource and authorization servers, handling API keys and remote authentication with Dynamic Client Registration. AuthKit integration means you can plug into existing enterprise identity systems without rebuilding your auth stack, setting the stage for plug-and-play auth that doesn't require users to become security experts overnight. + +⚡ The **Experimental OpenAPI Parser** delivers dramatic performance improvements through single-pass schema processing and optimized memory usage. OpenAPI integrations are now significantly faster, with cleaner, more maintainable code. _(Note: the experimental parser is disabled by default, set `FASTMCPEXPERIMENTALENABLENEWOPENAPIPARSER=1` to enable it. A message will be shown to all users on the legacy parser encouraging them to try the new one before it becomes the default.)_ + +🧠 **Context State Management** finally gives you persistent state across tool calls with a simple dict interface, while enhanced meta support lets you expose rich component metadata to clients. Combined with improved type annotations, string-based argument descriptions, and UV transport support, this release makes FastMCP feel more intuitive than ever. + +This release represents a TON of community contributions and sets the foundation for even more ambitious features ahead. + +## What's Changed +### New Features 🎉 +* Introduce experimental OpenAPI parser with improved performance and maintainability by [@jlowin](https://github.com/jlowin) in [#1209](https://github.com/jlowin/fastmcp/pull/1209) +* Add state dict to Context (#1118) by [@mukulmurthy](https://github.com/mukulmurthy) in [#1160](https://github.com/jlowin/fastmcp/pull/1160) +* Expose FastMCP tags to clients via component `meta` dict by [@jlowin](https://github.com/jlowin) in [#1281](https://github.com/jlowin/fastmcp/pull/1281) +* Add _fastmcp meta namespace by [@jlowin](https://github.com/jlowin) in [#1290](https://github.com/jlowin/fastmcp/pull/1290) +* Add TokenVerifier protocol support alongside existing OAuthProvider authentication by [@jlowin](https://github.com/jlowin) in [#1297](https://github.com/jlowin/fastmcp/pull/1297) +* Add comprehensive OAuth 2.1 authentication system with WorkOS integration by [@jlowin](https://github.com/jlowin) in [#1327](https://github.com/jlowin/fastmcp/pull/1327) +### Enhancements 🔧 +* [🐶] Transform MCP Server Tools by [@strawgate](https://github.com/strawgate) in [#1132](https://github.com/jlowin/fastmcp/pull/1132) +* Add --python, --project, and --with-requirements options to CLI commands by [@jlowin](https://github.com/jlowin) in [#1190](https://github.com/jlowin/fastmcp/pull/1190) +* Support `fastmcp run mcp.json` by [@strawgate](https://github.com/strawgate) in [#1138](https://github.com/jlowin/fastmcp/pull/1138) +* Support from __future__ import annotations by [@jlowin](https://github.com/jlowin) in [#1199](https://github.com/jlowin/fastmcp/pull/1199) +* Optimize OpenAPI parser performance with single-pass schema processing by [@jlowin](https://github.com/jlowin) in [#1214](https://github.com/jlowin/fastmcp/pull/1214) +* Log tool name on transform validation error by [@strawgate](https://github.com/strawgate) in [#1238](https://github.com/jlowin/fastmcp/pull/1238) +* Refactor `get_http_request` and `context.session_id` by [@hopeful0](https://github.com/hopeful0) in [#1242](https://github.com/jlowin/fastmcp/pull/1242) +* Support creating tool argument descriptions from string annotations by [@jlowin](https://github.com/jlowin) in [#1255](https://github.com/jlowin/fastmcp/pull/1255) +* feat: Add Annotations support for resources and resource templates by [@chughtapan](https://github.com/chughtapan) in [#1260](https://github.com/jlowin/fastmcp/pull/1260) +* Add UV Transport by [@strawgate](https://github.com/strawgate) in [#1270](https://github.com/jlowin/fastmcp/pull/1270) +* Improve OpenAPI-to-JSONSchema conversion utilities by [@jlowin](https://github.com/jlowin) in [#1283](https://github.com/jlowin/fastmcp/pull/1283) +* Ensure proxy components forward meta dicts by [@jlowin](https://github.com/jlowin) in [#1282](https://github.com/jlowin/fastmcp/pull/1282) +* fix: server argument passing in CLI run command by [@chughtapan](https://github.com/chughtapan) in [#1293](https://github.com/jlowin/fastmcp/pull/1293) +* Add meta support to tool transformation utilities by [@jlowin](https://github.com/jlowin) in [#1295](https://github.com/jlowin/fastmcp/pull/1295) +* feat: Allow Resource Metadata URL as field in OAuthProvider by [@dacamposol](https://github.com/dacamposol) in [#1287](https://github.com/jlowin/fastmcp/pull/1287) +* Use a simple overwrite instead of a merge for meta by [@jlowin](https://github.com/jlowin) in [#1296](https://github.com/jlowin/fastmcp/pull/1296) +* Remove unused TimedCache by [@strawgate](https://github.com/strawgate) in [#1303](https://github.com/jlowin/fastmcp/pull/1303) +* refactor: standardize logging usage across OpenAPI utilities by [@chi2liu](https://github.com/chi2liu) in [#1322](https://github.com/jlowin/fastmcp/pull/1322) +* perf: optimize OpenAPI parsing by reducing dict copy operations by [@chi2liu](https://github.com/chi2liu) in [#1321](https://github.com/jlowin/fastmcp/pull/1321) +* Structured client-side logging by [@cjermain](https://github.com/cjermain) in [#1326](https://github.com/jlowin/fastmcp/pull/1326) +### Fixes 🐞 +* fix: preserve def reference when referenced in allOf / oneOf / anyOf by [@algirdasci](https://github.com/algirdasci) in [#1208](https://github.com/jlowin/fastmcp/pull/1208) +* fix: add type hint to custom_route decorator by [@zzstoatzz](https://github.com/zzstoatzz) in [#1210](https://github.com/jlowin/fastmcp/pull/1210) +* chore: typo by [@richardkmichael](https://github.com/richardkmichael) in [#1216](https://github.com/jlowin/fastmcp/pull/1216) +* fix: handle non-string $ref values in experimental OpenAPI parser by [@jlowin](https://github.com/jlowin) in [#1217](https://github.com/jlowin/fastmcp/pull/1217) +* Skip repeated type conversion and validation in proxy client elicitation handler by [@chughtapan](https://github.com/chughtapan) in [#1222](https://github.com/jlowin/fastmcp/pull/1222) +* Ensure default fields are not marked nullable by [@jlowin](https://github.com/jlowin) in [#1224](https://github.com/jlowin/fastmcp/pull/1224) +* Fix stateful proxy client mixing in multi-proxies sessions by [@hopeful0](https://github.com/hopeful0) in [#1245](https://github.com/jlowin/fastmcp/pull/1245) +* Fix invalid async context manager usage in proxy documentation by [@zzstoatzz](https://github.com/zzstoatzz) in [#1246](https://github.com/jlowin/fastmcp/pull/1246) +* fix: experimental FastMCPOpenAPI server lost headers in request when __init__(client with headers) by [@itaru2622](https://github.com/itaru2622) in [#1254](https://github.com/jlowin/fastmcp/pull/1254) +* Fix typing, add tests for tool call middleware by [@jlowin](https://github.com/jlowin) in [#1269](https://github.com/jlowin/fastmcp/pull/1269) +* Fix: prune hidden parameter defs by [@muhammadkhalid-03](https://github.com/muhammadkhalid-03) in [#1257](https://github.com/jlowin/fastmcp/pull/1257) +* Fix nullable field handling in OpenAPI to JSON Schema conversion by [@jlowin](https://github.com/jlowin) in [#1279](https://github.com/jlowin/fastmcp/pull/1279) +* Ensure fastmcp run supports v1 servers by [@jlowin](https://github.com/jlowin) in [#1332](https://github.com/jlowin/fastmcp/pull/1332) +### Breaking Changes 🛫 +* Change server flag to --name by [@jlowin](https://github.com/jlowin) in [#1248](https://github.com/jlowin/fastmcp/pull/1248) +### Docs 📚 +* Remove unused import from FastAPI integration documentation by [@mariotaddeucci](https://github.com/mariotaddeucci) in [#1194](https://github.com/jlowin/fastmcp/pull/1194) +* Update fastapi docs by [@jlowin](https://github.com/jlowin) in [#1198](https://github.com/jlowin/fastmcp/pull/1198) +* Add docs for context state management by [@jlowin](https://github.com/jlowin) in [#1227](https://github.com/jlowin/fastmcp/pull/1227) +* Permit.io integration docs by [@orweis](https://github.com/orweis) in [#1226](https://github.com/jlowin/fastmcp/pull/1226) +* Update docs to reflect sync tools by [@jlowin](https://github.com/jlowin) in [#1234](https://github.com/jlowin/fastmcp/pull/1234) +* Update changelog.mdx by [@jlowin](https://github.com/jlowin) in [#1235](https://github.com/jlowin/fastmcp/pull/1235) +* Update SDK docs by [@jlowin](https://github.com/jlowin) in [#1236](https://github.com/jlowin/fastmcp/pull/1236) +* Update --name flag documentation for Cursor/Claude by [@adam-conway](https://github.com/adam-conway) in [#1239](https://github.com/jlowin/fastmcp/pull/1239) +* Add annotations docs by [@jlowin](https://github.com/jlowin) in [#1268](https://github.com/jlowin/fastmcp/pull/1268) +* Update openapi/fastapi URLs README.md by [@jbn](https://github.com/jbn) in [#1278](https://github.com/jlowin/fastmcp/pull/1278) +* Add 2.11 version badge for state management by [@jlowin](https://github.com/jlowin) in [#1289](https://github.com/jlowin/fastmcp/pull/1289) +* Add meta parameter support to tools, resources, templates, and prompts decorators by [@jlowin](https://github.com/jlowin) in [#1294](https://github.com/jlowin/fastmcp/pull/1294) +* docs: update get_state and set_state references by [@Maxi91f](https://github.com/Maxi91f) in [#1306](https://github.com/jlowin/fastmcp/pull/1306) +* Add unit tests and docs for denying tool calls with middleware by [@jlowin](https://github.com/jlowin) in [#1333](https://github.com/jlowin/fastmcp/pull/1333) +* Remove reference to stacked decorators by [@jlowin](https://github.com/jlowin) in [#1334](https://github.com/jlowin/fastmcp/pull/1334) +* Eunomia authorization server can run embedded within the MCP server by [@tommitt](https://github.com/tommitt) in [#1317](https://github.com/jlowin/fastmcp/pull/1317) +### Other Changes 🦾 +* Update README.md by [@jlowin](https://github.com/jlowin) in [#1230](https://github.com/jlowin/fastmcp/pull/1230) +* Logcapture addition to test_server file by [@Sourav-Tripathy](https://github.com/Sourav-Tripathy) in [#1229](https://github.com/jlowin/fastmcp/pull/1229) +* Add tests for headers with both legacy and experimental openapi parser by [@jlowin](https://github.com/jlowin) in [#1259](https://github.com/jlowin/fastmcp/pull/1259) +* Small clean-up from MCP Tool Transform PR by [@strawgate](https://github.com/strawgate) in [#1267](https://github.com/jlowin/fastmcp/pull/1267) +* Add test for proxy tags visibility by [@jlowin](https://github.com/jlowin) in [#1302](https://github.com/jlowin/fastmcp/pull/1302) +* Add unit test for sampling with image messages by [@jlowin](https://github.com/jlowin) in [#1329](https://github.com/jlowin/fastmcp/pull/1329) +* Remove redundant resource_metadata_url assignment by [@jlowin](https://github.com/jlowin) in [#1328](https://github.com/jlowin/fastmcp/pull/1328) +* Update bug.yml by [@jlowin](https://github.com/jlowin) in [#1331](https://github.com/jlowin/fastmcp/pull/1331) +* Ensure validation errors are raised when masked by [@jlowin](https://github.com/jlowin) in [#1330](https://github.com/jlowin/fastmcp/pull/1330) + +## New Contributors +* [@mariotaddeucci](https://github.com/mariotaddeucci) made their first contribution in [#1194](https://github.com/jlowin/fastmcp/pull/1194) +* [@algirdasci](https://github.com/algirdasci) made their first contribution in [#1208](https://github.com/jlowin/fastmcp/pull/1208) +* [@chughtapan](https://github.com/chughtapan) made their first contribution in [#1222](https://github.com/jlowin/fastmcp/pull/1222) +* [@mukulmurthy](https://github.com/mukulmurthy) made their first contribution in [#1160](https://github.com/jlowin/fastmcp/pull/1160) +* [@orweis](https://github.com/orweis) made their first contribution in [#1226](https://github.com/jlowin/fastmcp/pull/1226) +* [@Sourav-Tripathy](https://github.com/Sourav-Tripathy) made their first contribution in [#1229](https://github.com/jlowin/fastmcp/pull/1229) +* [@adam-conway](https://github.com/adam-conway) made their first contribution in [#1239](https://github.com/jlowin/fastmcp/pull/1239) +* [@muhammadkhalid-03](https://github.com/muhammadkhalid-03) made their first contribution in [#1257](https://github.com/jlowin/fastmcp/pull/1257) +* [@jbn](https://github.com/jbn) made their first contribution in [#1278](https://github.com/jlowin/fastmcp/pull/1278) +* [@dacamposol](https://github.com/dacamposol) made their first contribution in [#1287](https://github.com/jlowin/fastmcp/pull/1287) +* [@chi2liu](https://github.com/chi2liu) made their first contribution in [#1322](https://github.com/jlowin/fastmcp/pull/1322) +* [@cjermain](https://github.com/cjermain) made their first contribution in [#1326](https://github.com/jlowin/fastmcp/pull/1326) + +**Full Changelog**: [v2.10.6...v2.11.0](https://github.com/jlowin/fastmcp/compare/v2.10.6...v2.11.0) + + + + + +## [v2.10.6: Hymn for the Weekend](https://github.com/jlowin/fastmcp/releases/tag/v2.10.6) + +A special Saturday release with many fixes. + +## What's Changed +### Enhancements 🔧 +* Resolve #1139 -- Implement include_context argument in Context.sample by [@codingjoe](https://github.com/codingjoe) in [#1141](https://github.com/jlowin/fastmcp/pull/1141) +* feat(settings): add log level normalization by [@ka2048](https://github.com/ka2048) in [#1171](https://github.com/jlowin/fastmcp/pull/1171) +* add server name to mounted server warnings by [@artificial-aidan](https://github.com/artificial-aidan) in [#1147](https://github.com/jlowin/fastmcp/pull/1147) +* Add StatefulProxyClient by [@hopeful0](https://github.com/hopeful0) in [#1109](https://github.com/jlowin/fastmcp/pull/1109) +### Fixes 🐞 +* Fix OpenAPI empty parameters by [@FabrizioSandri](https://github.com/FabrizioSandri) in [#1128](https://github.com/jlowin/fastmcp/pull/1128) +* Fix title field preservation in tool transformations by [@jlowin](https://github.com/jlowin) in [#1131](https://github.com/jlowin/fastmcp/pull/1131) +* Fix optional parameter validation in OpenAPI integration by [@jlowin](https://github.com/jlowin) in [#1135](https://github.com/jlowin/fastmcp/pull/1135) +* Do not silently exclude the "context" key from JSON body by [@melkamar](https://github.com/melkamar) in [#1153](https://github.com/jlowin/fastmcp/pull/1153) +* Fix tool output schema generation to respect Pydantic serialization aliases by [@zzstoatzz](https://github.com/zzstoatzz) in [#1148](https://github.com/jlowin/fastmcp/pull/1148) +* fix: _replace_ref_with_defs; ensure ref_path is string by [@itaru2622](https://github.com/itaru2622) in [#1164](https://github.com/jlowin/fastmcp/pull/1164) +* Fix nesting when making OpenAPI arrays and objects optional by [@melkamar](https://github.com/melkamar) in [#1178](https://github.com/jlowin/fastmcp/pull/1178) +* Fix `mcp-json` output format to include server name by [@jlowin](https://github.com/jlowin) in [#1185](https://github.com/jlowin/fastmcp/pull/1185) +* Only configure logging one time by [@jlowin](https://github.com/jlowin) in [#1187](https://github.com/jlowin/fastmcp/pull/1187) +### Docs 📚 +* Update changelog.mdx by [@jlowin](https://github.com/jlowin) in [#1127](https://github.com/jlowin/fastmcp/pull/1127) +* Eunomia Authorization with native FastMCP's Middleware by [@tommitt](https://github.com/tommitt) in [#1144](https://github.com/jlowin/fastmcp/pull/1144) +* update api ref for new `mdxify` version by [@zzstoatzz](https://github.com/zzstoatzz) in [#1182](https://github.com/jlowin/fastmcp/pull/1182) +### Other Changes 🦾 +* Expand empty parameter filtering and add comprehensive tests by [@jlowin](https://github.com/jlowin) in [#1129](https://github.com/jlowin/fastmcp/pull/1129) +* Add no-commit-to-branch hook by [@zzstoatzz](https://github.com/zzstoatzz) in [#1149](https://github.com/jlowin/fastmcp/pull/1149) +* Update README.md by [@jlowin](https://github.com/jlowin) in [#1165](https://github.com/jlowin/fastmcp/pull/1165) +* skip on rate limit by [@zzstoatzz](https://github.com/zzstoatzz) in [#1183](https://github.com/jlowin/fastmcp/pull/1183) +* Remove deprecated proxy creation by [@jlowin](https://github.com/jlowin) in [#1186](https://github.com/jlowin/fastmcp/pull/1186) +* Separate integration tests from unit tests in CI by [@jlowin](https://github.com/jlowin) in [#1188](https://github.com/jlowin/fastmcp/pull/1188) + +## New Contributors +* [@FabrizioSandri](https://github.com/FabrizioSandri) made their first contribution in [#1128](https://github.com/jlowin/fastmcp/pull/1128) +* [@melkamar](https://github.com/melkamar) made their first contribution in [#1153](https://github.com/jlowin/fastmcp/pull/1153) +* [@codingjoe](https://github.com/codingjoe) made their first contribution in [#1141](https://github.com/jlowin/fastmcp/pull/1141) +* [@itaru2622](https://github.com/itaru2622) made their first contribution in [#1164](https://github.com/jlowin/fastmcp/pull/1164) +* [@ka2048](https://github.com/ka2048) made their first contribution in [#1171](https://github.com/jlowin/fastmcp/pull/1171) +* [@artificial-aidan](https://github.com/artificial-aidan) made their first contribution in [#1147](https://github.com/jlowin/fastmcp/pull/1147) + +**Full Changelog**: [v2.10.5...v2.10.6](https://github.com/jlowin/fastmcp/compare/v2.10.5...v2.10.6) + + + + + +## [v2.10.5: Middle Management](https://github.com/jlowin/fastmcp/releases/tag/v2.10.5) + +A maintenance release focused on OpenAPI refinements and middleware fixes, plus console improvements. + +## What's Changed +### Enhancements 🔧 +* Fix Claude Code CLI detection for npm global installations by [@jlowin](https://github.com/jlowin) in [#1106](https://github.com/jlowin/fastmcp/pull/1106) +* Fix OpenAPI parameter name collisions with location suffixing by [@jlowin](https://github.com/jlowin) in [#1107](https://github.com/jlowin/fastmcp/pull/1107) +* Add mirrored component support for proxy servers by [@jlowin](https://github.com/jlowin) in [#1105](https://github.com/jlowin/fastmcp/pull/1105) +### Fixes 🐞 +* Fix OpenAPI deepObject style parameter encoding by [@jlowin](https://github.com/jlowin) in [#1122](https://github.com/jlowin/fastmcp/pull/1122) +* xfail when github token is not set ('' or None) by [@jlowin](https://github.com/jlowin) in [#1123](https://github.com/jlowin/fastmcp/pull/1123) +* fix: replace oneOf with anyOf in OpenAPI output schemas by [@MagnusS0](https://github.com/MagnusS0) in [#1119](https://github.com/jlowin/fastmcp/pull/1119) +* Fix middleware list result types by [@jlowin](https://github.com/jlowin) in [#1125](https://github.com/jlowin/fastmcp/pull/1125) +* Improve console width for logo by [@jlowin](https://github.com/jlowin) in [#1126](https://github.com/jlowin/fastmcp/pull/1126) +### Docs 📚 +* Improve transport + integration docs by [@jlowin](https://github.com/jlowin) in [#1103](https://github.com/jlowin/fastmcp/pull/1103) +* Update proxy.mdx by [@coldfire-x](https://github.com/coldfire-x) in [#1108](https://github.com/jlowin/fastmcp/pull/1108) +### Other Changes 🦾 +* Update github remote server tests with secret by [@jlowin](https://github.com/jlowin) in [#1112](https://github.com/jlowin/fastmcp/pull/1112) + +## New Contributors +* [@coldfire-x](https://github.com/coldfire-x) made their first contribution in [#1108](https://github.com/jlowin/fastmcp/pull/1108) +* [@MagnusS0](https://github.com/MagnusS0) made their first contribution in [#1119](https://github.com/jlowin/fastmcp/pull/1119) + +**Full Changelog**: [v2.10.4...v2.10.5](https://github.com/jlowin/fastmcp/compare/v2.10.4...v2.10.5) + + + + + +## [v2.10.4: Transport-ation](https://github.com/jlowin/fastmcp/releases/tag/v2.10.4) + +A quick fix to ensure the CLI accepts "streamable-http" as a valid transport option. + +## What's Changed +### Fixes 🐞 +* Ensure the CLI accepts "streamable-http" as a valid transport by [@jlowin](https://github.com/jlowin) in [#1099](https://github.com/jlowin/fastmcp/pull/1099) + +**Full Changelog**: [v2.10.3...v2.10.4](https://github.com/jlowin/fastmcp/compare/v2.10.3...v2.10.4) + + + + + +## [v2.10.3: CLI Me a River](https://github.com/jlowin/fastmcp/releases/tag/v2.10.3) + +A major CLI overhaul featuring a complete refactor from typer to cyclopts, new IDE integrations, and comprehensive OpenAPI improvements. + +## What's Changed +### New Features 🎉 +* Refactor CLI from typer to cyclopts and add comprehensive tests by [@jlowin](https://github.com/jlowin) in [#1062](https://github.com/jlowin/fastmcp/pull/1062) +* Add output schema support for OpenAPI tools by [@jlowin](https://github.com/jlowin) in [#1073](https://github.com/jlowin/fastmcp/pull/1073) +### Enhancements 🔧 +* Add Cursor support via CLI integration by [@jlowin](https://github.com/jlowin) in [#1052](https://github.com/jlowin/fastmcp/pull/1052) +* Add Claude Code install integration by [@jlowin](https://github.com/jlowin) in [#1053](https://github.com/jlowin/fastmcp/pull/1053) +* Generate MCP JSON config output from CLI as new `fastmcp install` command by [@jlowin](https://github.com/jlowin) in [#1056](https://github.com/jlowin/fastmcp/pull/1056) +* Use isawaitable instead of iscoroutine by [@jlowin](https://github.com/jlowin) in [#1059](https://github.com/jlowin/fastmcp/pull/1059) +* feat: Add `--path` Option to CLI for HTTP/SSE Route by [@davidbk-legit](https://github.com/davidbk-legit) in [#1087](https://github.com/jlowin/fastmcp/pull/1087) +* Fix concurrent proxy client operations with session isolation by [@jlowin](https://github.com/jlowin) in [#1083](https://github.com/jlowin/fastmcp/pull/1083) +### Fixes 🐞 +* Refactor Client context management to avoid concurrency issue by [@hopeful0](https://github.com/hopeful0) in [#1054](https://github.com/jlowin/fastmcp/pull/1054) +* Keep json schema $defs on transform by [@strawgate](https://github.com/strawgate) in [#1066](https://github.com/jlowin/fastmcp/pull/1066) +* Ensure fastmcp version copy is plaintext by [@jlowin](https://github.com/jlowin) in [#1071](https://github.com/jlowin/fastmcp/pull/1071) +* Fix single-element list unwrapping in tool content by [@jlowin](https://github.com/jlowin) in [#1074](https://github.com/jlowin/fastmcp/pull/1074) +* Fix max recursion error when pruning OpenAPI definitions by [@dimitribarbot](https://github.com/dimitribarbot) in [#1092](https://github.com/jlowin/fastmcp/pull/1092) +* Fix OpenAPI tool name registration when modified by mcp_component_fn by [@jlowin](https://github.com/jlowin) in [#1096](https://github.com/jlowin/fastmcp/pull/1096) +### Docs 📚 +* Docs: add example of more concise way to use bearer auth by [@neilconway](https://github.com/neilconway) in [#1055](https://github.com/jlowin/fastmcp/pull/1055) +* Update favicon by [@jlowin](https://github.com/jlowin) in [#1058](https://github.com/jlowin/fastmcp/pull/1058) +* Update environment note by [@jlowin](https://github.com/jlowin) in [#1075](https://github.com/jlowin/fastmcp/pull/1075) +* Add fastmcp version --copy documentation by [@jlowin](https://github.com/jlowin) in [#1076](https://github.com/jlowin/fastmcp/pull/1076) +### Other Changes 🦾 +* Remove asserts and add documentation following #1054 by [@jlowin](https://github.com/jlowin) in [#1057](https://github.com/jlowin/fastmcp/pull/1057) +* Add --copy flag for fastmcp version by [@jlowin](https://github.com/jlowin) in [#1063](https://github.com/jlowin/fastmcp/pull/1063) +* Fix docstring format for fastmcp.client.Client by [@neilconway](https://github.com/neilconway) in [#1094](https://github.com/jlowin/fastmcp/pull/1094) + +## New Contributors +* [@neilconway](https://github.com/neilconway) made their first contribution in [#1055](https://github.com/jlowin/fastmcp/pull/1055) +* [@davidbk-legit](https://github.com/davidbk-legit) made their first contribution in [#1087](https://github.com/jlowin/fastmcp/pull/1087) +* [@dimitribarbot](https://github.com/dimitribarbot) made their first contribution in [#1092](https://github.com/jlowin/fastmcp/pull/1092) + +**Full Changelog**: [v2.10.2...v2.10.3](https://github.com/jlowin/fastmcp/compare/v2.10.2...v2.10.3) + + + + + +## [v2.10.2: Forward March](https://github.com/jlowin/fastmcp/releases/tag/v2.10.2) + +The headline feature of this release is the ability to "forward" advanced MCP interactions like logging, progress, and elicitation through proxy servers. If the remote server requests an elicitation, the proxy client will pass that request to the new, "ultimate" client. + +## What's Changed +### New Features 🎉 +* Proxy support advanced MCP features by [@hopeful0](https://github.com/hopeful0) in [#1022](https://github.com/jlowin/fastmcp/pull/1022) +### Enhancements 🔧 +* Re-add splash screen by [@jlowin](https://github.com/jlowin) in [#1027](https://github.com/jlowin/fastmcp/pull/1027) +* Reduce banner padding by [@jlowin](https://github.com/jlowin) in [#1030](https://github.com/jlowin/fastmcp/pull/1030) +* Allow per-server timeouts in MCPConfig by [@cegersdoerfer](https://github.com/cegersdoerfer) in [#1031](https://github.com/jlowin/fastmcp/pull/1031) +* Support 'scp' claim for OAuth scopes in BearerAuthProvider by [@jlowin](https://github.com/jlowin) in [#1033](https://github.com/jlowin/fastmcp/pull/1033) +* Add path expansion to image/audio/file by [@jlowin](https://github.com/jlowin) in [#1038](https://github.com/jlowin/fastmcp/pull/1038) +* Ensure multi-client configurations use new ProxyClient by [@jlowin](https://github.com/jlowin) in [#1045](https://github.com/jlowin/fastmcp/pull/1045) +### Fixes 🐞 +* Expose stateless_http kwarg for mcp.run() by [@jlowin](https://github.com/jlowin) in [#1018](https://github.com/jlowin/fastmcp/pull/1018) +* Avoid propagating logs by [@jlowin](https://github.com/jlowin) in [#1042](https://github.com/jlowin/fastmcp/pull/1042) +### Docs 📚 +* Clean up docs by [@jlowin](https://github.com/jlowin) in [#1028](https://github.com/jlowin/fastmcp/pull/1028) +* Docs: clarify server URL paths for ChatGPT integration by [@thap2331](https://github.com/thap2331) in [#1017](https://github.com/jlowin/fastmcp/pull/1017) +### Other Changes 🦾 +* Split giant openapi test file into smaller files by [@jlowin](https://github.com/jlowin) in [#1034](https://github.com/jlowin/fastmcp/pull/1034) +* Add comprehensive OpenAPI 3.0 vs 3.1 compatibility tests by [@jlowin](https://github.com/jlowin) in [#1035](https://github.com/jlowin/fastmcp/pull/1035) +* Update banner and use console.log by [@jlowin](https://github.com/jlowin) in [#1041](https://github.com/jlowin/fastmcp/pull/1041) + +## New Contributors +* [@cegersdoerfer](https://github.com/cegersdoerfer) made their first contribution in [#1031](https://github.com/jlowin/fastmcp/pull/1031) +* [@hopeful0](https://github.com/hopeful0) made their first contribution in [#1022](https://github.com/jlowin/fastmcp/pull/1022) +* [@thap2331](https://github.com/thap2331) made their first contribution in [#1017](https://github.com/jlowin/fastmcp/pull/1017) + +**Full Changelog**: [v2.10.1...v2.10.2](https://github.com/jlowin/fastmcp/compare/v2.10.1...v2.10.2) + + + + + +## [v2.10.1: Revert to Sender](https://github.com/jlowin/fastmcp/releases/tag/v2.10.1) + +A quick patch to revert the CLI banner that was added in v2.10.0. + +## What's Changed +### Docs 📚 +* Update changelog.mdx by [@jlowin](https://github.com/jlowin) in [#1009](https://github.com/jlowin/fastmcp/pull/1009) +* Revert "Add CLI banner" by [@jlowin](https://github.com/jlowin) in [#1011](https://github.com/jlowin/fastmcp/pull/1011) + +**Full Changelog**: [v2.10.0...v2.10.1](https://github.com/jlowin/fastmcp/compare/v2.10.0...v2.10.1) + + + + + +## [v2.10.0: Great Spec-tations](https://github.com/jlowin/fastmcp/releases/tag/v2.10.0) + +FastMCP 2.10 brings full compliance with the 6/18/2025 MCP spec update, introducing elicitation support for dynamic server-client communication and output schemas for structured tool responses. Please note that due to these changes, this release also includes a breaking change to the return signature of `client.call_tool()`. + +### Elicitation Support +Elicitation allows MCP servers to request additional information from clients during tool execution, enabling more interactive and dynamic server behavior. This opens up new possibilities for tools that need user input or confirmation during execution. + +### Output Schemas +Tools can now define structured output schemas, ensuring that responses conform to expected formats and making tool integration more predictable and type-safe. + +## What's Changed +### New Features 🎉 +* MCP 6/18/25: Add output schema to tools by [@jlowin](https://github.com/jlowin) in [#901](https://github.com/jlowin/fastmcp/pull/901) +* MCP 6/18/25: Elicitation support by [@jlowin](https://github.com/jlowin) in [#889](https://github.com/jlowin/fastmcp/pull/889) +### Enhancements 🔧 +* Update types + tests for SDK changes by [@jlowin](https://github.com/jlowin) in [#888](https://github.com/jlowin/fastmcp/pull/888) +* MCP 6/18/25: Update auth primitives by [@jlowin](https://github.com/jlowin) in [#966](https://github.com/jlowin/fastmcp/pull/966) +* Add OpenAPI extensions support to HTTPRoute by [@maddymanu](https://github.com/maddymanu) in [#977](https://github.com/jlowin/fastmcp/pull/977) +* Add title field support to FastMCP components by [@jlowin](https://github.com/jlowin) in [#982](https://github.com/jlowin/fastmcp/pull/982) +* Support implicit Elicitation acceptance by [@jlowin](https://github.com/jlowin) in [#983](https://github.com/jlowin/fastmcp/pull/983) +* Support 'no response' elicitation requests by [@jlowin](https://github.com/jlowin) in [#992](https://github.com/jlowin/fastmcp/pull/992) +* Add Support for Configurable Algorithms by [@sstene1](https://github.com/sstene1) in [#997](https://github.com/jlowin/fastmcp/pull/997) +### Fixes 🐞 +* Improve stdio error handling to raise connection failures immediately by [@jlowin](https://github.com/jlowin) in [#984](https://github.com/jlowin/fastmcp/pull/984) +* Fix type hints for FunctionResource:fn by [@CfirTsabari](https://github.com/CfirTsabari) in [#986](https://github.com/jlowin/fastmcp/pull/986) +* Update link to OpenAI MCP example by [@mossbanay](https://github.com/mossbanay) in [#985](https://github.com/jlowin/fastmcp/pull/985) +* Fix output schema generation edge case by [@jlowin](https://github.com/jlowin) in [#995](https://github.com/jlowin/fastmcp/pull/995) +* Refactor array parameter formatting to reduce code duplication by [@jlowin](https://github.com/jlowin) in [#1007](https://github.com/jlowin/fastmcp/pull/1007) +* Fix OpenAPI array parameter explode handling by [@jlowin](https://github.com/jlowin) in [#1008](https://github.com/jlowin/fastmcp/pull/1008) +### Breaking Changes 🛫 +* MCP 6/18/25: Upgrade to mcp 1.10 by [@jlowin](https://github.com/jlowin) in [#887](https://github.com/jlowin/fastmcp/pull/887) +### Docs 📚 +* Update middleware imports and documentation by [@jlowin](https://github.com/jlowin) in [#999](https://github.com/jlowin/fastmcp/pull/999) +* Update OpenAI docs by [@jlowin](https://github.com/jlowin) in [#1001](https://github.com/jlowin/fastmcp/pull/1001) +* Add CLI banner by [@jlowin](https://github.com/jlowin) in [#1005](https://github.com/jlowin/fastmcp/pull/1005) +### Examples & Contrib 💡 +* Component Manager by [@gorocode](https://github.com/gorocode) in [#976](https://github.com/jlowin/fastmcp/pull/976) +### Other Changes 🦾 +* Minor auth improvements by [@jlowin](https://github.com/jlowin) in [#967](https://github.com/jlowin/fastmcp/pull/967) +* Add .ccignore for copychat by [@jlowin](https://github.com/jlowin) in [#1000](https://github.com/jlowin/fastmcp/pull/1000) + +## New Contributors +* [@maddymanu](https://github.com/maddymanu) made their first contribution in [#977](https://github.com/jlowin/fastmcp/pull/977) +* [@github0hello](https://github.com/github0hello) made their first contribution in [#979](https://github.com/jlowin/fastmcp/pull/979) +* [@tommitt](https://github.com/tommitt) made their first contribution in [#975](https://github.com/jlowin/fastmcp/pull/975) +* [@CfirTsabari](https://github.com/CfirTsabari) made their first contribution in [#986](https://github.com/jlowin/fastmcp/pull/986) +* [@mossbanay](https://github.com/mossbanay) made their first contribution in [#985](https://github.com/jlowin/fastmcp/pull/985) +* [@sstene1](https://github.com/sstene1) made their first contribution in [#997](https://github.com/jlowin/fastmcp/pull/997) + +**Full Changelog**: [v2.9.2...v2.10.0](https://github.com/jlowin/fastmcp/compare/v2.9.2...v2.10.0) + + + + + +## [v2.9.2: Safety Pin](https://github.com/jlowin/fastmcp/releases/tag/v2.9.2) + +This is a patch release to pin `mcp` below 1.10, which includes changes related to the 6/18/2025 MCP spec update and could potentially break functionality for some FastMCP users. + +## What's Changed +### Docs 📚 +* Fix version badge for messages by [@jlowin](https://github.com/jlowin) in [#960](https://github.com/jlowin/fastmcp/pull/960) +### Dependencies 📦 +* Pin mcp dependency by [@jlowin](https://github.com/jlowin) in [#962](https://github.com/jlowin/fastmcp/pull/962) + +**Full Changelog**: [v2.9.1...v2.9.2](https://github.com/jlowin/fastmcp/compare/v2.9.1...v2.9.2) + + + + + +## [v2.9.1: Call Me Maybe](https://github.com/jlowin/fastmcp/releases/tag/v2.9.1) + +FastMCP 2.9.1 introduces automatic MCP list change notifications, allowing servers to notify clients when tools, resources, or prompts are dynamically updated. This enables more responsive and adaptive MCP integrations. + +## What's Changed +### New Features 🎉 +* Add automatic MCP list change notifications and client message handling by [@jlowin](https://github.com/jlowin) in [#939](https://github.com/jlowin/fastmcp/pull/939) +### Enhancements 🔧 +* Add debug logging to bearer token authentication by [@jlowin](https://github.com/jlowin) in [#952](https://github.com/jlowin/fastmcp/pull/952) +### Fixes 🐞 +* Fix duplicate error logging in exception handlers by [@jlowin](https://github.com/jlowin) in [#938](https://github.com/jlowin/fastmcp/pull/938) +* Fix parameter location enum handling in OpenAPI parser by [@jlowin](https://github.com/jlowin) in [#953](https://github.com/jlowin/fastmcp/pull/953) +* Fix external schema reference handling in OpenAPI parser by [@jlowin](https://github.com/jlowin) in [#954](https://github.com/jlowin/fastmcp/pull/954) +### Docs 📚 +* Update changelog for 2.9 release by [@jlowin](https://github.com/jlowin) in [#929](https://github.com/jlowin/fastmcp/pull/929) +* Regenerate API references by [@zzstoatzz](https://github.com/zzstoatzz) in [#935](https://github.com/jlowin/fastmcp/pull/935) +* Regenerate API references by [@zzstoatzz](https://github.com/zzstoatzz) in [#947](https://github.com/jlowin/fastmcp/pull/947) +* Regenerate API references by [@zzstoatzz](https://github.com/zzstoatzz) in [#949](https://github.com/jlowin/fastmcp/pull/949) +### Examples & Contrib 💡 +* Add `create_thread` tool to bsky MCP server by [@zzstoatzz](https://github.com/zzstoatzz) in [#927](https://github.com/jlowin/fastmcp/pull/927) +* Update `mount_example.py` to work with current fastmcp API by [@rajephon](https://github.com/rajephon) in [#957](https://github.com/jlowin/fastmcp/pull/957) + +## New Contributors +* [@rajephon](https://github.com/rajephon) made their first contribution in [#957](https://github.com/jlowin/fastmcp/pull/957) + +**Full Changelog**: [v2.9.0...v2.9.1](https://github.com/jlowin/fastmcp/compare/v2.9.0...v2.9.1) + + + + + +## [v2.9.0: Stuck in the Middleware With You](https://github.com/jlowin/fastmcp/releases/tag/v2.9.0) + +FastMCP 2.9 introduces two important features that push beyond the basic MCP protocol: MCP Middleware and server-side type conversion. + +### MCP Middleware +MCP middleware lets you intercept and modify requests and responses at the protocol level, giving you powerful capabilities for logging, authentication, validation, and more. This is particularly useful for building production-ready MCP servers that need sophisticated request handling. + +### Server-side Type Conversion +This release also introduces server-side type conversion for prompt arguments, ensuring that data is properly formatted before being passed to your functions. This reduces the burden on individual tools and prompts to handle type validation and conversion. + +## What's Changed +### New Features 🎉 +* Add File utility for binary data by [@gorocode](https://github.com/gorocode) in [#843](https://github.com/jlowin/fastmcp/pull/843) +* Consolidate prefix logic into FastMCP methods by [@jlowin](https://github.com/jlowin) in [#861](https://github.com/jlowin/fastmcp/pull/861) +* Add MCP Middleware by [@jlowin](https://github.com/jlowin) in [#870](https://github.com/jlowin/fastmcp/pull/870) +* Implement server-side type conversion for prompt arguments by [@jlowin](https://github.com/jlowin) in [#908](https://github.com/jlowin/fastmcp/pull/908) +### Enhancements 🔧 +* Fix tool description indentation issue by [@zfflxx](https://github.com/zfflxx) in [#845](https://github.com/jlowin/fastmcp/pull/845) +* Add version parameter to FastMCP constructor by [@mkyutani](https://github.com/mkyutani) in [#842](https://github.com/jlowin/fastmcp/pull/842) +* Update version to not be positional by [@jlowin](https://github.com/jlowin) in [#848](https://github.com/jlowin/fastmcp/pull/848) +* Add key to component by [@jlowin](https://github.com/jlowin) in [#869](https://github.com/jlowin/fastmcp/pull/869) +* Add session_id property to Context for data sharing by [@jlowin](https://github.com/jlowin) in [#881](https://github.com/jlowin/fastmcp/pull/881) +* Fix CORS documentation example by [@jlowin](https://github.com/jlowin) in [#895](https://github.com/jlowin/fastmcp/pull/895) +### Fixes 🐞 +* "report_progress missing passing related_request_id causes notifications not working" by [@alexsee](https://github.com/alexsee) in [#838](https://github.com/jlowin/fastmcp/pull/838) +* Fix JWT issuer validation to support string values per RFC 7519 by [@jlowin](https://github.com/jlowin) in [#892](https://github.com/jlowin/fastmcp/pull/892) +* Fix BearerAuthProvider audience type annotations by [@jlowin](https://github.com/jlowin) in [#894](https://github.com/jlowin/fastmcp/pull/894) +### Docs 📚 +* Add CLAUDE.md development guidelines by [@jlowin](https://github.com/jlowin) in [#880](https://github.com/jlowin/fastmcp/pull/880) +* Update context docs for session_id property by [@jlowin](https://github.com/jlowin) in [#882](https://github.com/jlowin/fastmcp/pull/882) +* Add API reference by [@zzstoatzz](https://github.com/zzstoatzz) in [#893](https://github.com/jlowin/fastmcp/pull/893) +* Fix API ref rendering by [@zzstoatzz](https://github.com/zzstoatzz) in [#900](https://github.com/jlowin/fastmcp/pull/900) +* Simplify docs nav by [@jlowin](https://github.com/jlowin) in [#902](https://github.com/jlowin/fastmcp/pull/902) +* Add fastmcp inspect command by [@jlowin](https://github.com/jlowin) in [#904](https://github.com/jlowin/fastmcp/pull/904) +* Update client docs by [@jlowin](https://github.com/jlowin) in [#912](https://github.com/jlowin/fastmcp/pull/912) +* Update docs nav by [@jlowin](https://github.com/jlowin) in [#913](https://github.com/jlowin/fastmcp/pull/913) +* Update integration documentation for Claude Desktop, ChatGPT, and Claude Code by [@jlowin](https://github.com/jlowin) in [#915](https://github.com/jlowin/fastmcp/pull/915) +* Add http as an alias for streamable http by [@jlowin](https://github.com/jlowin) in [#917](https://github.com/jlowin/fastmcp/pull/917) +* Clean up parameter documentation by [@jlowin](https://github.com/jlowin) in [#918](https://github.com/jlowin/fastmcp/pull/918) +* Add middleware examples for timing, logging, rate limiting, and error handling by [@jlowin](https://github.com/jlowin) in [#919](https://github.com/jlowin/fastmcp/pull/919) +* ControlFlow → FastMCP rename by [@jlowin](https://github.com/jlowin) in [#922](https://github.com/jlowin/fastmcp/pull/922) +### Examples & Contrib 💡 +* Add contrib.mcp_mixin support for annotations by [@rsp2k](https://github.com/rsp2k) in [#860](https://github.com/jlowin/fastmcp/pull/860) +* Add ATProto (Bluesky) MCP Server Example by [@zzstoatzz](https://github.com/zzstoatzz) in [#916](https://github.com/jlowin/fastmcp/pull/916) +* Fix path in atproto example pyproject by [@zzstoatzz](https://github.com/zzstoatzz) in [#920](https://github.com/jlowin/fastmcp/pull/920) +* Remove uv source in example by [@zzstoatzz](https://github.com/zzstoatzz) in [#921](https://github.com/jlowin/fastmcp/pull/921) + +## New Contributors +* [@alexsee](https://github.com/alexsee) made their first contribution in [#838](https://github.com/jlowin/fastmcp/pull/838) +* [@zfflxx](https://github.com/zfflxx) made their first contribution in [#845](https://github.com/jlowin/fastmcp/pull/845) +* [@mkyutani](https://github.com/mkyutani) made their first contribution in [#842](https://github.com/jlowin/fastmcp/pull/842) +* [@gorocode](https://github.com/gorocode) made their first contribution in [#843](https://github.com/jlowin/fastmcp/pull/843) +* [@rsp2k](https://github.com/rsp2k) made their first contribution in [#860](https://github.com/jlowin/fastmcp/pull/860) +* [@owtaylor](https://github.com/owtaylor) made their first contribution in [#897](https://github.com/jlowin/fastmcp/pull/897) +* [@Jason-CKY](https://github.com/Jason-CKY) made their first contribution in [#906](https://github.com/jlowin/fastmcp/pull/906) + +**Full Changelog**: [v2.8.1...v2.9.0](https://github.com/jlowin/fastmcp/compare/v2.8.1...v2.9.0) + + + + + +## [v2.8.1: Sound Judgement](https://github.com/jlowin/fastmcp/releases/tag/v2.8.1) + +2.8.1 introduces audio support, as well as minor fixes and updates for deprecated features. + +### Audio Support +This release adds support for audio content in MCP tools and resources, expanding FastMCP's multimedia capabilities beyond text and images. + +## What's Changed +### New Features 🎉 +* Add audio support by [@jlowin](https://github.com/jlowin) in [#833](https://github.com/jlowin/fastmcp/pull/833) +### Enhancements 🔧 +* Add flag for disabling deprecation warnings by [@jlowin](https://github.com/jlowin) in [#802](https://github.com/jlowin/fastmcp/pull/802) +* Add examples to Tool Arg Param transformation by [@strawgate](https://github.com/strawgate) in [#806](https://github.com/jlowin/fastmcp/pull/806) +### Fixes 🐞 +* Restore .settings access as deprecated by [@jlowin](https://github.com/jlowin) in [#800](https://github.com/jlowin/fastmcp/pull/800) +* Ensure handling of false http kwargs correctly; removed unused kwarg by [@jlowin](https://github.com/jlowin) in [#804](https://github.com/jlowin/fastmcp/pull/804) +* Bump mcp 1.9.4 by [@jlowin](https://github.com/jlowin) in [#835](https://github.com/jlowin/fastmcp/pull/835) +### Docs 📚 +* Update changelog for 2.8.0 by [@jlowin](https://github.com/jlowin) in [#794](https://github.com/jlowin/fastmcp/pull/794) +* Update welcome docs by [@jlowin](https://github.com/jlowin) in [#808](https://github.com/jlowin/fastmcp/pull/808) +* Update headers in docs by [@jlowin](https://github.com/jlowin) in [#809](https://github.com/jlowin/fastmcp/pull/809) +* Add MCP group to tutorials by [@jlowin](https://github.com/jlowin) in [#810](https://github.com/jlowin/fastmcp/pull/810) +* Add Community section to documentation by [@zzstoatzz](https://github.com/zzstoatzz) in [#819](https://github.com/jlowin/fastmcp/pull/819) +* Add 2.8 update by [@jlowin](https://github.com/jlowin) in [#821](https://github.com/jlowin/fastmcp/pull/821) +* Embed YouTube videos in community showcase by [@zzstoatzz](https://github.com/zzstoatzz) in [#820](https://github.com/jlowin/fastmcp/pull/820) +### Other Changes 🦾 +* Ensure http args are passed through by [@jlowin](https://github.com/jlowin) in [#803](https://github.com/jlowin/fastmcp/pull/803) +* Fix install link in readme by [@jlowin](https://github.com/jlowin) in [#836](https://github.com/jlowin/fastmcp/pull/836) + +**Full Changelog**: [v2.8.0...v2.8.1](https://github.com/jlowin/fastmcp/compare/v2.8.0...v2.8.1) + + + + + +## [v2.8.0: Transform and Roll Out](https://github.com/jlowin/fastmcp/releases/tag/v2.8.0) + +FastMCP 2.8.0 introduces powerful new ways to customize and control your MCP servers! + +### Tool Transformation + +The highlight of this release is first-class [**Tool Transformation**](/patterns/tool-transformation), a new feature that lets you create enhanced variations of existing tools. You can now easily rename arguments, hide parameters, modify descriptions, and even wrap tools with custom validation or post-processing logic—all without rewriting the original code. This makes it easier than ever to adapt generic tools for specific LLM use cases or to simplify complex APIs. Huge thanks to [@strawgate](https://github.com/strawgate) for partnering on this, starting with [#591](https://github.com/jlowin/fastmcp/discussions/591) and [#599](https://github.com/jlowin/fastmcp/pull/599) and continuing offline. + +### Component Control +This release also gives you more granular control over which components are exposed to clients. With new [**tag-based filtering**](/servers/server#tag-based-filtering), you can selectively enable or disable tools, resources, and prompts based on tags, perfect for managing different environments or user permissions. Complementing this, every component now supports being [programmatically enabled or disabled](/servers/tools#disabling-tools), offering dynamic control over your server's capabilities. + +### Tools-by-Default +Finally, to improve compatibility with a wider range of LLM clients, this release changes the default behavior for OpenAPI integration: all API endpoints are now converted to `Tools` by default. This is a **breaking change** but pragmatically necessitated by the fact that the majority of MCP clients available today are, sadly, only compatible with MCP tools. Therefore, this change significantly simplifies the out-of-the-box experience and ensures your entire API is immediately accessible to any tool-using agent. + +## What's Changed +### New Features 🎉 +* First-class tool transformation by [@jlowin](https://github.com/jlowin) in [#745](https://github.com/jlowin/fastmcp/pull/745) +* Support enable/disable for all FastMCP components (tools, prompts, resources, templates) by [@jlowin](https://github.com/jlowin) in [#781](https://github.com/jlowin/fastmcp/pull/781) +* Add support for tag-based component filtering by [@jlowin](https://github.com/jlowin) in [#748](https://github.com/jlowin/fastmcp/pull/748) +* Allow tag assignments for OpenAPI by [@jlowin](https://github.com/jlowin) in [#791](https://github.com/jlowin/fastmcp/pull/791) +### Enhancements 🔧 +* Create common base class for components by [@jlowin](https://github.com/jlowin) in [#776](https://github.com/jlowin/fastmcp/pull/776) +* Move components to own file; add resource by [@jlowin](https://github.com/jlowin) in [#777](https://github.com/jlowin/fastmcp/pull/777) +* Update FastMCP component with __eq__ and __repr__ by [@jlowin](https://github.com/jlowin) in [#779](https://github.com/jlowin/fastmcp/pull/779) +* Remove open-ended and server-specific settings by [@jlowin](https://github.com/jlowin) in [#750](https://github.com/jlowin/fastmcp/pull/750) +### Fixes 🐞 +* Ensure client is only initialized once by [@jlowin](https://github.com/jlowin) in [#758](https://github.com/jlowin/fastmcp/pull/758) +* Fix field validator for resource by [@jlowin](https://github.com/jlowin) in [#778](https://github.com/jlowin/fastmcp/pull/778) +* Ensure proxies can overwrite remote tools without falling back to the remote by [@jlowin](https://github.com/jlowin) in [#782](https://github.com/jlowin/fastmcp/pull/782) +### Breaking Changes 🛫 +* Treat all openapi routes as tools by [@jlowin](https://github.com/jlowin) in [#788](https://github.com/jlowin/fastmcp/pull/788) +* Fix issue with global OpenAPI tags by [@jlowin](https://github.com/jlowin) in [#792](https://github.com/jlowin/fastmcp/pull/792) +### Docs 📚 +* Minor docs updates by [@jlowin](https://github.com/jlowin) in [#755](https://github.com/jlowin/fastmcp/pull/755) +* Add 2.7 update by [@jlowin](https://github.com/jlowin) in [#756](https://github.com/jlowin/fastmcp/pull/756) +* Reduce 2.7 image size by [@jlowin](https://github.com/jlowin) in [#757](https://github.com/jlowin/fastmcp/pull/757) +* Update updates.mdx by [@jlowin](https://github.com/jlowin) in [#765](https://github.com/jlowin/fastmcp/pull/765) +* Hide docs sidebar scrollbar by default by [@jlowin](https://github.com/jlowin) in [#766](https://github.com/jlowin/fastmcp/pull/766) +* Add "stop vibe testing" to tutorials by [@jlowin](https://github.com/jlowin) in [#767](https://github.com/jlowin/fastmcp/pull/767) +* Add docs links by [@jlowin](https://github.com/jlowin) in [#768](https://github.com/jlowin/fastmcp/pull/768) +* Fix: updated variable name under Gemini remote client by [@yrangana](https://github.com/yrangana) in [#769](https://github.com/jlowin/fastmcp/pull/769) +* Revert "Hide docs sidebar scrollbar by default" by [@jlowin](https://github.com/jlowin) in [#770](https://github.com/jlowin/fastmcp/pull/770) +* Add updates by [@jlowin](https://github.com/jlowin) in [#773](https://github.com/jlowin/fastmcp/pull/773) +* Add tutorials by [@jlowin](https://github.com/jlowin) in [#783](https://github.com/jlowin/fastmcp/pull/783) +* Update LLM-friendly docs by [@jlowin](https://github.com/jlowin) in [#784](https://github.com/jlowin/fastmcp/pull/784) +* Update oauth.mdx by [@JeremyCraigMartinez](https://github.com/JeremyCraigMartinez) in [#787](https://github.com/jlowin/fastmcp/pull/787) +* Add changelog by [@jlowin](https://github.com/jlowin) in [#789](https://github.com/jlowin/fastmcp/pull/789) +* Add tutorials by [@jlowin](https://github.com/jlowin) in [#790](https://github.com/jlowin/fastmcp/pull/790) +* Add docs for tag-based filtering by [@jlowin](https://github.com/jlowin) in [#793](https://github.com/jlowin/fastmcp/pull/793) +### Other Changes 🦾 +* Create dependabot.yml by [@jlowin](https://github.com/jlowin) in [#759](https://github.com/jlowin/fastmcp/pull/759) +* Bump astral-sh/setup-uv from 3 to 6 by [@dependabot](https://github.com/dependabot) in [#760](https://github.com/jlowin/fastmcp/pull/760) +* Add dependencies section to release by [@jlowin](https://github.com/jlowin) in [#761](https://github.com/jlowin/fastmcp/pull/761) +* Remove extra imports for MCPConfig by [@Maanas-Verma](https://github.com/Maanas-Verma) in [#763](https://github.com/jlowin/fastmcp/pull/763) +* Split out enhancements in release notes by [@jlowin](https://github.com/jlowin) in [#764](https://github.com/jlowin/fastmcp/pull/764) + +## New Contributors +* [@dependabot](https://github.com/dependabot) made their first contribution in [#760](https://github.com/jlowin/fastmcp/pull/760) +* [@Maanas-Verma](https://github.com/Maanas-Verma) made their first contribution in [#763](https://github.com/jlowin/fastmcp/pull/763) +* [@JeremyCraigMartinez](https://github.com/JeremyCraigMartinez) made their first contribution in [#787](https://github.com/jlowin/fastmcp/pull/787) + +**Full Changelog**: [v2.7.1...v2.8.0](https://github.com/jlowin/fastmcp/compare/v2.7.1...v2.8.0) + + + + + +## [v2.7.1: The Bearer Necessities](https://github.com/jlowin/fastmcp/releases/tag/v2.7.1) + +This release primarily contains a fix for parsing string tokens that are provided to FastMCP clients. + +### New Features 🎉 + +* Respect cache setting, set default to 1 second by [@jlowin](https://github.com/jlowin) in [#747](https://github.com/jlowin/fastmcp/pull/747) + +### Fixes 🐞 + +* Ensure event store is properly typed by [@jlowin](https://github.com/jlowin) in [#753](https://github.com/jlowin/fastmcp/pull/753) +* Fix passing token string to client auth & add auth to MCPConfig clients by [@jlowin](https://github.com/jlowin) in [#754](https://github.com/jlowin/fastmcp/pull/754) + +### Docs 📚 + +* Docs : fix client to mcp\_client in Gemini example by [@yrangana](https://github.com/yrangana) in [#734](https://github.com/jlowin/fastmcp/pull/734) +* update add tool docstring by [@strawgate](https://github.com/strawgate) in [#739](https://github.com/jlowin/fastmcp/pull/739) +* Fix contrib link by [@richardkmichael](https://github.com/richardkmichael) in [#749](https://github.com/jlowin/fastmcp/pull/749) + +### Other Changes 🦾 + +* Switch Pydantic defaults to kwargs by [@strawgate](https://github.com/strawgate) in [#731](https://github.com/jlowin/fastmcp/pull/731) +* Fix Typo in CLI module by [@wfclark5](https://github.com/wfclark5) in [#737](https://github.com/jlowin/fastmcp/pull/737) +* chore: fix prompt docstring by [@danb27](https://github.com/danb27) in [#752](https://github.com/jlowin/fastmcp/pull/752) +* Add accept to excluded headers by [@jlowin](https://github.com/jlowin) in [#751](https://github.com/jlowin/fastmcp/pull/751) + +### New Contributors + +* [@wfclark5](https://github.com/wfclark5) made their first contribution in [#737](https://github.com/jlowin/fastmcp/pull/737) +* [@richardkmichael](https://github.com/richardkmichael) made their first contribution in [#749](https://github.com/jlowin/fastmcp/pull/749) +* [@danb27](https://github.com/danb27) made their first contribution in [#752](https://github.com/jlowin/fastmcp/pull/752) + +**Full Changelog**: [v2.7.0...v2.7.1](https://github.com/jlowin/fastmcp/compare/v2.7.0...v2.7.1) + + + + +## [v2.7.0: Pare Programming](https://github.com/jlowin/fastmcp/releases/tag/v2.7.0) + +This is primarily a housekeeping release to remove or deprecate cruft that's accumulated since v1. Primarily, this release refactors FastMCP's internals in preparation for features planned in the next few major releases. However please note that as a result, this release has some minor breaking changes (which is why it's 2.7, not 2.6.2, in accordance with repo guidelines) though not to the core user-facing APIs. + +### Breaking Changes 🛫 + +* decorators return the objects they create, not the decorated function +* websockets is an optional dependency +* methods on the server for automatically converting functions into tools/resources/prompts have been deprecated in favor of using the decorators directly + +### New Features 🎉 + +* allow passing flags to servers by [@zzstoatzz](https://github.com/zzstoatzz) in [#690](https://github.com/jlowin/fastmcp/pull/690) +* replace $ref pointing to `#/components/schemas/` with `#/$defs/` by [@phateffect](https://github.com/phateffect) in [#697](https://github.com/jlowin/fastmcp/pull/697) +* Split Tool into Tool and FunctionTool by [@jlowin](https://github.com/jlowin) in [#700](https://github.com/jlowin/fastmcp/pull/700) +* Use strict basemodel for Prompt; relax from\_function deprecation by [@jlowin](https://github.com/jlowin) in [#701](https://github.com/jlowin/fastmcp/pull/701) +* Formalize resource/functionresource replationship by [@jlowin](https://github.com/jlowin) in [#702](https://github.com/jlowin/fastmcp/pull/702) +* Formalize template/functiontemplate split by [@jlowin](https://github.com/jlowin) in [#703](https://github.com/jlowin/fastmcp/pull/703) +* Support flexible @tool decorator call patterns by [@jlowin](https://github.com/jlowin) in [#706](https://github.com/jlowin/fastmcp/pull/706) +* Ensure deprecation warnings have stacklevel=2 by [@jlowin](https://github.com/jlowin) in [#710](https://github.com/jlowin/fastmcp/pull/710) +* Allow naked prompt decorator by [@jlowin](https://github.com/jlowin) in [#711](https://github.com/jlowin/fastmcp/pull/711) + +### Fixes 🐞 + +* Updates / Fixes for Tool Content Conversion by [@strawgate](https://github.com/strawgate) in [#642](https://github.com/jlowin/fastmcp/pull/642) +* Fix pr labeler permissions by [@jlowin](https://github.com/jlowin) in [#708](https://github.com/jlowin/fastmcp/pull/708) +* remove -n auto by [@jlowin](https://github.com/jlowin) in [#709](https://github.com/jlowin/fastmcp/pull/709) +* Fix links in README.md by [@alainivars](https://github.com/alainivars) in [#723](https://github.com/jlowin/fastmcp/pull/723) + +Happily, this release DOES permit the use of "naked" decorators to align with Pythonic practice: + +```python +@mcp.tool +def my_tool(): + ... +``` + +**Full Changelog**: [v2.6.2...v2.7.0](https://github.com/jlowin/fastmcp/compare/v2.6.2...v2.7.0) + + + + +## [v2.6.1: Blast Auth (second ignition)](https://github.com/jlowin/fastmcp/releases/tag/v2.6.1) + +This is a patch release to restore py.typed in #686. + +### Docs 📚 + +* Update readme by [@jlowin](https://github.com/jlowin) in [#679](https://github.com/jlowin/fastmcp/pull/679) +* Add gemini tutorial by [@jlowin](https://github.com/jlowin) in [#680](https://github.com/jlowin/fastmcp/pull/680) +* Fix : fix path error to CLI Documentation by [@yrangana](https://github.com/yrangana) in [#684](https://github.com/jlowin/fastmcp/pull/684) +* Update auth docs by [@jlowin](https://github.com/jlowin) in [#687](https://github.com/jlowin/fastmcp/pull/687) + +### Other Changes 🦾 + +* Remove deprecation notice by [@jlowin](https://github.com/jlowin) in [#677](https://github.com/jlowin/fastmcp/pull/677) +* Delete server.py by [@jlowin](https://github.com/jlowin) in [#681](https://github.com/jlowin/fastmcp/pull/681) +* Restore py.typed by [@jlowin](https://github.com/jlowin) in [#686](https://github.com/jlowin/fastmcp/pull/686) + +### New Contributors + +* [@yrangana](https://github.com/yrangana) made their first contribution in [#684](https://github.com/jlowin/fastmcp/pull/684) + +**Full Changelog**: [v2.6.0...v2.6.1](https://github.com/jlowin/fastmcp/compare/v2.6.0...v2.6.1) + + + + +## [v2.6.0: Blast Auth](https://github.com/jlowin/fastmcp/releases/tag/v2.6.0) + +### New Features 🎉 + +* Introduce MCP client oauth flow by [@jlowin](https://github.com/jlowin) in [#478](https://github.com/jlowin/fastmcp/pull/478) +* Support providing tools at init by [@jlowin](https://github.com/jlowin) in [#647](https://github.com/jlowin/fastmcp/pull/647) +* Simplify code for running servers in processes during tests by [@jlowin](https://github.com/jlowin) in [#649](https://github.com/jlowin/fastmcp/pull/649) +* Add basic bearer auth for server and client by [@jlowin](https://github.com/jlowin) in [#650](https://github.com/jlowin/fastmcp/pull/650) +* Support configuring bearer auth from env vars by [@jlowin](https://github.com/jlowin) in [#652](https://github.com/jlowin/fastmcp/pull/652) +* feat(tool): add support for excluding arguments from tool definition by [@deepak-stratforge](https://github.com/deepak-stratforge) in [#626](https://github.com/jlowin/fastmcp/pull/626) +* Add docs for server + client auth by [@jlowin](https://github.com/jlowin) in [#655](https://github.com/jlowin/fastmcp/pull/655) + +### Fixes 🐞 + +* fix: Support concurrency in FastMcpProxy (and Client) by [@Sillocan](https://github.com/Sillocan) in [#635](https://github.com/jlowin/fastmcp/pull/635) +* Ensure Client.close() cleans up client context appropriately by [@jlowin](https://github.com/jlowin) in [#643](https://github.com/jlowin/fastmcp/pull/643) +* Update client.mdx: ClientError namespace by [@mjkaye](https://github.com/mjkaye) in [#657](https://github.com/jlowin/fastmcp/pull/657) + +### Docs 📚 + +* Make FastMCPTransport support simulated Streamable HTTP Transport (didn't work) by [@jlowin](https://github.com/jlowin) in [#645](https://github.com/jlowin/fastmcp/pull/645) +* Document exclude\_args by [@jlowin](https://github.com/jlowin) in [#653](https://github.com/jlowin/fastmcp/pull/653) +* Update welcome by [@jlowin](https://github.com/jlowin) in [#673](https://github.com/jlowin/fastmcp/pull/673) +* Add Anthropic + Claude desktop integration guides by [@jlowin](https://github.com/jlowin) in [#674](https://github.com/jlowin/fastmcp/pull/674) +* Minor docs design updates by [@jlowin](https://github.com/jlowin) in [#676](https://github.com/jlowin/fastmcp/pull/676) + +### Other Changes 🦾 + +* Update test typing by [@jlowin](https://github.com/jlowin) in [#646](https://github.com/jlowin/fastmcp/pull/646) +* Add OpenAI integration docs by [@jlowin](https://github.com/jlowin) in [#660](https://github.com/jlowin/fastmcp/pull/660) + +### New Contributors + +* [@Sillocan](https://github.com/Sillocan) made their first contribution in [#635](https://github.com/jlowin/fastmcp/pull/635) +* [@deepak-stratforge](https://github.com/deepak-stratforge) made their first contribution in [#626](https://github.com/jlowin/fastmcp/pull/626) +* [@mjkaye](https://github.com/mjkaye) made their first contribution in [#657](https://github.com/jlowin/fastmcp/pull/657) + +**Full Changelog**: [v2.5.2...v2.6.0](https://github.com/jlowin/fastmcp/compare/v2.5.2...v2.6.0) + + + + +## [v2.5.2: Stayin' Alive](https://github.com/jlowin/fastmcp/releases/tag/v2.5.2) + +### New Features 🎉 + +* Add graceful error handling for unreachable mounted servers by [@davenpi](https://github.com/davenpi) in [#605](https://github.com/jlowin/fastmcp/pull/605) +* Improve type inference from client transport by [@jlowin](https://github.com/jlowin) in [#623](https://github.com/jlowin/fastmcp/pull/623) +* Add keep\_alive param to reuse subprocess by [@jlowin](https://github.com/jlowin) in [#624](https://github.com/jlowin/fastmcp/pull/624) + +### Fixes 🐞 + +* Fix handling tools without descriptions by [@jlowin](https://github.com/jlowin) in [#610](https://github.com/jlowin/fastmcp/pull/610) +* Don't print env vars to console when format is wrong by [@jlowin](https://github.com/jlowin) in [#615](https://github.com/jlowin/fastmcp/pull/615) +* Ensure behavior-affecting headers are excluded when forwarding proxies/openapi by [@jlowin](https://github.com/jlowin) in [#620](https://github.com/jlowin/fastmcp/pull/620) + +### Docs 📚 + +* Add notes about uv and claude desktop by [@jlowin](https://github.com/jlowin) in [#597](https://github.com/jlowin/fastmcp/pull/597) + +### Other Changes 🦾 + +* add init\_timeout for mcp client by [@jfouret](https://github.com/jfouret) in [#607](https://github.com/jlowin/fastmcp/pull/607) +* Add init\_timeout for mcp client (incl settings) by [@jlowin](https://github.com/jlowin) in [#609](https://github.com/jlowin/fastmcp/pull/609) +* Support for uppercase letters at the log level by [@ksawaray](https://github.com/ksawaray) in [#625](https://github.com/jlowin/fastmcp/pull/625) + +### New Contributors + +* [@jfouret](https://github.com/jfouret) made their first contribution in [#607](https://github.com/jlowin/fastmcp/pull/607) +* [@ksawaray](https://github.com/ksawaray) made their first contribution in [#625](https://github.com/jlowin/fastmcp/pull/625) + +**Full Changelog**: [v2.5.1...v2.5.2](https://github.com/jlowin/fastmcp/compare/v2.5.1...v2.5.2) + + + + +## [v2.5.1: Route Awakening (Part 2)](https://github.com/jlowin/fastmcp/releases/tag/v2.5.1) + +### Fixes 🐞 + +* Ensure content-length is always stripped from client headers by [@jlowin](https://github.com/jlowin) in [#589](https://github.com/jlowin/fastmcp/pull/589) + +### Docs 📚 + +* Fix redundant section of docs by [@jlowin](https://github.com/jlowin) in [#583](https://github.com/jlowin/fastmcp/pull/583) + +**Full Changelog**: [v2.5.0...v2.5.1](https://github.com/jlowin/fastmcp/compare/v2.5.0...v2.5.1) + + + + +## [v2.5.0: Route Awakening](https://github.com/jlowin/fastmcp/releases/tag/v2.5.0) + +This release introduces completely new tools for generating and customizing MCP servers from OpenAPI specs and FastAPI apps, including popular requests like mechanisms for determining what routes map to what MCP components; renaming routes; and customizing the generated MCP components. + +### New Features 🎉 + +* Add FastMCP 1.0 server support for in-memory Client / Testing by [@jlowin](https://github.com/jlowin) in [#539](https://github.com/jlowin/fastmcp/pull/539) +* Minor addition: add transport to stdio server in mcpconfig, with default by [@jlowin](https://github.com/jlowin) in [#555](https://github.com/jlowin/fastmcp/pull/555) +* Raise an error if a Client is created with no servers in config by [@jlowin](https://github.com/jlowin) in [#554](https://github.com/jlowin/fastmcp/pull/554) +* Expose model preferences in `Context.sample` for flexible model selection. by [@davenpi](https://github.com/davenpi) in [#542](https://github.com/jlowin/fastmcp/pull/542) +* Ensure custom routes are respected by [@jlowin](https://github.com/jlowin) in [#558](https://github.com/jlowin/fastmcp/pull/558) +* Add client method to send cancellation notifications by [@davenpi](https://github.com/davenpi) in [#563](https://github.com/jlowin/fastmcp/pull/563) +* Enhance route map logic for include/exclude OpenAPI routes by [@jlowin](https://github.com/jlowin) in [#564](https://github.com/jlowin/fastmcp/pull/564) +* Add tag-based route maps by [@jlowin](https://github.com/jlowin) in [#565](https://github.com/jlowin/fastmcp/pull/565) +* Add advanced control of openAPI route creation by [@jlowin](https://github.com/jlowin) in [#566](https://github.com/jlowin/fastmcp/pull/566) +* Make error masking configurable by [@jlowin](https://github.com/jlowin) in [#550](https://github.com/jlowin/fastmcp/pull/550) +* Ensure client headers are passed through to remote servers by [@jlowin](https://github.com/jlowin) in [#575](https://github.com/jlowin/fastmcp/pull/575) +* Use lowercase name for headers when comparing by [@jlowin](https://github.com/jlowin) in [#576](https://github.com/jlowin/fastmcp/pull/576) +* Permit more flexible name generation for OpenAPI servers by [@jlowin](https://github.com/jlowin) in [#578](https://github.com/jlowin/fastmcp/pull/578) +* Ensure that tools/templates/prompts are compatible with callable objects by [@jlowin](https://github.com/jlowin) in [#579](https://github.com/jlowin/fastmcp/pull/579) + +### Docs 📚 + +* Add version badge for prefix formats by [@jlowin](https://github.com/jlowin) in [#537](https://github.com/jlowin/fastmcp/pull/537) +* Add versioning note to docs by [@jlowin](https://github.com/jlowin) in [#551](https://github.com/jlowin/fastmcp/pull/551) +* Bump 2.3.6 references to 2.4.0 by [@jlowin](https://github.com/jlowin) in [#567](https://github.com/jlowin/fastmcp/pull/567) + +**Full Changelog**: [v2.4.0...v2.5.0](https://github.com/jlowin/fastmcp/compare/v2.4.0...v2.5.0) + + + + +## [v2.4.0: Config and Conquer](https://github.com/jlowin/fastmcp/releases/tag/v2.4.0) + +**Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route. + +### New Features 🎉 + +* Allow \* Methods and all routes as tools shortcuts by [@jlowin](https://github.com/jlowin) in [#520](https://github.com/jlowin/fastmcp/pull/520) +* Improved support for config dicts by [@jlowin](https://github.com/jlowin) in [#522](https://github.com/jlowin/fastmcp/pull/522) +* Support creating clients from MCP config dicts, including multi-server clients by [@jlowin](https://github.com/jlowin) in [#527](https://github.com/jlowin/fastmcp/pull/527) +* Make resource prefix format configurable by [@jlowin](https://github.com/jlowin) in [#534](https://github.com/jlowin/fastmcp/pull/534) + +### Fixes 🐞 + +* Avoid hanging on initializing server session by [@jlowin](https://github.com/jlowin) in [#523](https://github.com/jlowin/fastmcp/pull/523) + +### Breaking Changes 🛫 + +* Remove customizable separators; improve resource separator by [@jlowin](https://github.com/jlowin) in [#526](https://github.com/jlowin/fastmcp/pull/526) + +### Docs 📚 + +* Improve client documentation by [@jlowin](https://github.com/jlowin) in [#517](https://github.com/jlowin/fastmcp/pull/517) + +### Other Changes 🦾 + +* Ensure openapi path params are handled properly by [@jlowin](https://github.com/jlowin) in [#519](https://github.com/jlowin/fastmcp/pull/519) +* better error when missing lifespan by [@zzstoatzz](https://github.com/zzstoatzz) in [#521](https://github.com/jlowin/fastmcp/pull/521) + +**Full Changelog**: [v2.3.5...v2.4.0](https://github.com/jlowin/fastmcp/compare/v2.3.5...v2.4.0) + + + + +## [v2.3.5: Making Progress](https://github.com/jlowin/fastmcp/releases/tag/v2.3.5) + +### New Features 🎉 + +* support messages in progress notifications by [@rickygenhealth](https://github.com/rickygenhealth) in [#471](https://github.com/jlowin/fastmcp/pull/471) +* feat: Add middleware option in server.run by [@Maxi91f](https://github.com/Maxi91f) in [#475](https://github.com/jlowin/fastmcp/pull/475) +* Add lifespan property to app by [@jlowin](https://github.com/jlowin) in [#483](https://github.com/jlowin/fastmcp/pull/483) +* Update `fastmcp run` to work with remote servers by [@jlowin](https://github.com/jlowin) in [#491](https://github.com/jlowin/fastmcp/pull/491) +* Add FastMCP.as\_proxy() by [@jlowin](https://github.com/jlowin) in [#490](https://github.com/jlowin/fastmcp/pull/490) +* Infer sse transport from urls containing /sse by [@jlowin](https://github.com/jlowin) in [#512](https://github.com/jlowin/fastmcp/pull/512) +* Add progress handler to client by [@jlowin](https://github.com/jlowin) in [#513](https://github.com/jlowin/fastmcp/pull/513) +* Store the initialize result on the client by [@jlowin](https://github.com/jlowin) in [#509](https://github.com/jlowin/fastmcp/pull/509) + +### Fixes 🐞 + +* Remove patch and use upstream SSEServerTransport by [@jlowin](https://github.com/jlowin) in [#425](https://github.com/jlowin/fastmcp/pull/425) + +### Docs 📚 + +* Update transport docs by [@jlowin](https://github.com/jlowin) in [#458](https://github.com/jlowin/fastmcp/pull/458) +* update proxy docs + example by [@zzstoatzz](https://github.com/zzstoatzz) in [#460](https://github.com/jlowin/fastmcp/pull/460) +* doc(asgi): Change custom route example to PlainTextResponse by [@mcw0933](https://github.com/mcw0933) in [#477](https://github.com/jlowin/fastmcp/pull/477) +* Store FastMCP instance on app.state.fastmcp\_server by [@jlowin](https://github.com/jlowin) in [#489](https://github.com/jlowin/fastmcp/pull/489) +* Improve AGENTS.md overview by [@jlowin](https://github.com/jlowin) in [#492](https://github.com/jlowin/fastmcp/pull/492) +* Update release numbers for anticipated version by [@jlowin](https://github.com/jlowin) in [#516](https://github.com/jlowin/fastmcp/pull/516) + +### Other Changes 🦾 + +* run tests on all PRs by [@jlowin](https://github.com/jlowin) in [#468](https://github.com/jlowin/fastmcp/pull/468) +* add null check by [@zzstoatzz](https://github.com/zzstoatzz) in [#473](https://github.com/jlowin/fastmcp/pull/473) +* strict typing for `server.py` by [@zzstoatzz](https://github.com/zzstoatzz) in [#476](https://github.com/jlowin/fastmcp/pull/476) +* Doc(quickstart): Fix import statements by [@mai-nakagawa](https://github.com/mai-nakagawa) in [#479](https://github.com/jlowin/fastmcp/pull/479) +* Add labeler by [@jlowin](https://github.com/jlowin) in [#484](https://github.com/jlowin/fastmcp/pull/484) +* Fix flaky timeout test by increasing timeout (#474) by [@davenpi](https://github.com/davenpi) in [#486](https://github.com/jlowin/fastmcp/pull/486) +* Skipping `test_permission_error` if runner is root. by [@ZiadAmerr](https://github.com/ZiadAmerr) in [#502](https://github.com/jlowin/fastmcp/pull/502) +* allow passing full uvicorn config by [@zzstoatzz](https://github.com/zzstoatzz) in [#504](https://github.com/jlowin/fastmcp/pull/504) +* Skip timeout tests on windows by [@jlowin](https://github.com/jlowin) in [#514](https://github.com/jlowin/fastmcp/pull/514) + +### New Contributors + +* [@rickygenhealth](https://github.com/rickygenhealth) made their first contribution in [#471](https://github.com/jlowin/fastmcp/pull/471) +* [@Maxi91f](https://github.com/Maxi91f) made their first contribution in [#475](https://github.com/jlowin/fastmcp/pull/475) +* [@mcw0933](https://github.com/mcw0933) made their first contribution in [#477](https://github.com/jlowin/fastmcp/pull/477) +* [@mai-nakagawa](https://github.com/mai-nakagawa) made their first contribution in [#479](https://github.com/jlowin/fastmcp/pull/479) +* [@ZiadAmerr](https://github.com/ZiadAmerr) made their first contribution in [#502](https://github.com/jlowin/fastmcp/pull/502) + +**Full Changelog**: [v2.3.4...v2.3.5](https://github.com/jlowin/fastmcp/compare/v2.3.4...v2.3.5) + + + + +## [v2.3.4: Error Today, Gone Tomorrow](https://github.com/jlowin/fastmcp/releases/tag/v2.3.4) + +### New Features 🎉 + +* logging stack trace for easier debugging by [@jbkoh](https://github.com/jbkoh) in [#413](https://github.com/jlowin/fastmcp/pull/413) +* add missing StreamableHttpTransport in client exports by [@yihuang](https://github.com/yihuang) in [#408](https://github.com/jlowin/fastmcp/pull/408) +* Improve error handling for tools and resources by [@jlowin](https://github.com/jlowin) in [#434](https://github.com/jlowin/fastmcp/pull/434) +* feat: add support for removing tools from server by [@davenpi](https://github.com/davenpi) in [#437](https://github.com/jlowin/fastmcp/pull/437) +* Prune titles from JSONSchemas by [@jlowin](https://github.com/jlowin) in [#449](https://github.com/jlowin/fastmcp/pull/449) +* Declare toolsChanged capability for stdio server. by [@davenpi](https://github.com/davenpi) in [#450](https://github.com/jlowin/fastmcp/pull/450) +* Improve handling of exceptiongroups when raised in clients by [@jlowin](https://github.com/jlowin) in [#452](https://github.com/jlowin/fastmcp/pull/452) +* Add timeout support to client by [@jlowin](https://github.com/jlowin) in [#455](https://github.com/jlowin/fastmcp/pull/455) + +### Fixes 🐞 + +* Pin to mcp 1.8.1 to resolve callback deadlocks with SHTTP by [@jlowin](https://github.com/jlowin) in [#427](https://github.com/jlowin/fastmcp/pull/427) +* Add reprs for OpenAPI objects by [@jlowin](https://github.com/jlowin) in [#447](https://github.com/jlowin/fastmcp/pull/447) +* Ensure openapi defs for structured objects are loaded properly by [@jlowin](https://github.com/jlowin) in [#448](https://github.com/jlowin/fastmcp/pull/448) +* Ensure tests run against correct python version by [@jlowin](https://github.com/jlowin) in [#454](https://github.com/jlowin/fastmcp/pull/454) +* Ensure result is only returned if a new key was found by [@jlowin](https://github.com/jlowin) in [#456](https://github.com/jlowin/fastmcp/pull/456) + +### Docs 📚 + +* Add documentation for tool removal by [@jlowin](https://github.com/jlowin) in [#440](https://github.com/jlowin/fastmcp/pull/440) + +### Other Changes 🦾 + +* Deprecate passing settings to the FastMCP instance by [@jlowin](https://github.com/jlowin) in [#424](https://github.com/jlowin/fastmcp/pull/424) +* Add path prefix to test by [@jlowin](https://github.com/jlowin) in [#432](https://github.com/jlowin/fastmcp/pull/432) + +### New Contributors + +* [@jbkoh](https://github.com/jbkoh) made their first contribution in [#413](https://github.com/jlowin/fastmcp/pull/413) +* [@davenpi](https://github.com/davenpi) made their first contribution in [#437](https://github.com/jlowin/fastmcp/pull/437) + +**Full Changelog**: [v2.3.3...v2.3.4](https://github.com/jlowin/fastmcp/compare/v2.3.3...v2.3.4) + + + + +## [v2.3.3: SSE you later](https://github.com/jlowin/fastmcp/releases/tag/v2.3.3) + +This is a hotfix for a bug introduced in 2.3.2 that broke SSE servers + +### Fixes 🐞 + +* Fix bug that sets message path and sse path to same value by [@jlowin](https://github.com/jlowin) in [#405](https://github.com/jlowin/fastmcp/pull/405) + +### Docs 📚 + +* Update composition docs by [@jlowin](https://github.com/jlowin) in [#403](https://github.com/jlowin/fastmcp/pull/403) + +### Other Changes 🦾 + +* Add test for no prefix when importing by [@jlowin](https://github.com/jlowin) in [#404](https://github.com/jlowin/fastmcp/pull/404) + +**Full Changelog**: [v2.3.2...v2.3.3](https://github.com/jlowin/fastmcp/compare/v2.3.2...v2.3.3) + + + + +## [v2.3.2: Stuck in the Middleware With You](https://github.com/jlowin/fastmcp/releases/tag/v2.3.2) + +### New Features 🎉 + +* Allow users to pass middleware to starlette app constructors by [@jlowin](https://github.com/jlowin) in [#398](https://github.com/jlowin/fastmcp/pull/398) +* Deprecate transport-specific methods on FastMCP server by [@jlowin](https://github.com/jlowin) in [#401](https://github.com/jlowin/fastmcp/pull/401) + +### Docs 📚 + +* Update CLI docs by [@jlowin](https://github.com/jlowin) in [#402](https://github.com/jlowin/fastmcp/pull/402) + +### Other Changes 🦾 + +* Adding 23 tests for CLI by [@didier-durand](https://github.com/didier-durand) in [#394](https://github.com/jlowin/fastmcp/pull/394) + +**Full Changelog**: [v2.3.1...v2.3.2](https://github.com/jlowin/fastmcp/compare/v2.3.1...v2.3.2) + + + + +## [v2.3.1: For Good-nests Sake](https://github.com/jlowin/fastmcp/releases/tag/v2.3.1) + +This release primarily patches a long-standing bug with nested ASGI SSE servers. + +### Fixes 🐞 + +* Fix tool result serialization when the tool returns a list by [@strawgate](https://github.com/strawgate) in [#379](https://github.com/jlowin/fastmcp/pull/379) +* Ensure FastMCP handles nested SSE and SHTTP apps properly in ASGI frameworks by [@jlowin](https://github.com/jlowin) in [#390](https://github.com/jlowin/fastmcp/pull/390) + +### Docs 📚 + +* Update transport docs by [@jlowin](https://github.com/jlowin) in [#377](https://github.com/jlowin/fastmcp/pull/377) +* Add llms.txt to docs by [@jlowin](https://github.com/jlowin) in [#384](https://github.com/jlowin/fastmcp/pull/384) +* Fixing various text typos by [@didier-durand](https://github.com/didier-durand) in [#385](https://github.com/jlowin/fastmcp/pull/385) + +### Other Changes 🦾 + +* Adding a few tests to Image type by [@didier-durand](https://github.com/didier-durand) in [#387](https://github.com/jlowin/fastmcp/pull/387) +* Adding tests for TimedCache by [@didier-durand](https://github.com/didier-durand) in [#388](https://github.com/jlowin/fastmcp/pull/388) + +### New Contributors + +* [@didier-durand](https://github.com/didier-durand) made their first contribution in [#385](https://github.com/jlowin/fastmcp/pull/385) + +**Full Changelog**: [v2.3.0...v2.3.1](https://github.com/jlowin/fastmcp/compare/v2.3.0...v2.3.1) + + + + +## [v2.3.0: Stream Me Up, Scotty](https://github.com/jlowin/fastmcp/releases/tag/v2.3.0) + +### New Features 🎉 + +* Add streaming support for HTTP transport by [@jlowin](https://github.com/jlowin) in [#365](https://github.com/jlowin/fastmcp/pull/365) +* Support streaming HTTP transport in clients by [@jlowin](https://github.com/jlowin) in [#366](https://github.com/jlowin/fastmcp/pull/366) +* Add streaming support to CLI by [@jlowin](https://github.com/jlowin) in [#367](https://github.com/jlowin/fastmcp/pull/367) + +### Fixes 🐞 + +* Fix streaming transport initialization by [@jlowin](https://github.com/jlowin) in [#368](https://github.com/jlowin/fastmcp/pull/368) + +### Docs 📚 + +* Update transport documentation for streaming support by [@jlowin](https://github.com/jlowin) in [#369](https://github.com/jlowin/fastmcp/pull/369) + +**Full Changelog**: [v2.2.10...v2.3.0](https://github.com/jlowin/fastmcp/compare/v2.2.10...v2.3.0) + + + + +## [v2.2.10: That's JSON Bourne](https://github.com/jlowin/fastmcp/releases/tag/v2.2.10) + +### Fixes 🐞 + +* Disable automatic JSON parsing of tool args by [@jlowin](https://github.com/jlowin) in [#341](https://github.com/jlowin/fastmcp/pull/341) +* Fix prompt test by [@jlowin](https://github.com/jlowin) in [#342](https://github.com/jlowin/fastmcp/pull/342) + +### Other Changes 🦾 + +* Update docs.json by [@jlowin](https://github.com/jlowin) in [#338](https://github.com/jlowin/fastmcp/pull/338) +* Add test coverage + tests on 4 examples by [@alainivars](https://github.com/alainivars) in [#306](https://github.com/jlowin/fastmcp/pull/306) + +### New Contributors + +* [@alainivars](https://github.com/alainivars) made their first contribution in [#306](https://github.com/jlowin/fastmcp/pull/306) + +**Full Changelog**: [v2.2.9...v2.2.10](https://github.com/jlowin/fastmcp/compare/v2.2.9...v2.2.10) + + + + +## [v2.2.9: Str-ing the Pot (Hotfix)](https://github.com/jlowin/fastmcp/releases/tag/v2.2.9) + +This release is a hotfix for the issue detailed in #330 + +### Fixes 🐞 + +* Prevent invalid resource URIs by [@jlowin](https://github.com/jlowin) in [#336](https://github.com/jlowin/fastmcp/pull/336) +* Coerce numbers to str by [@jlowin](https://github.com/jlowin) in [#337](https://github.com/jlowin/fastmcp/pull/337) + +### Docs 📚 + +* Add client badge by [@jlowin](https://github.com/jlowin) in [#327](https://github.com/jlowin/fastmcp/pull/327) +* Update bug.yml by [@jlowin](https://github.com/jlowin) in [#328](https://github.com/jlowin/fastmcp/pull/328) + +### Other Changes 🦾 + +* Update quickstart.mdx example to include import by [@discdiver](https://github.com/discdiver) in [#329](https://github.com/jlowin/fastmcp/pull/329) + +### New Contributors + +* [@discdiver](https://github.com/discdiver) made their first contribution in [#329](https://github.com/jlowin/fastmcp/pull/329) + +**Full Changelog**: [v2.2.8...v2.2.9](https://github.com/jlowin/fastmcp/compare/v2.2.8...v2.2.9) + + + + +## [v2.2.8: Parse and Recreation](https://github.com/jlowin/fastmcp/releases/tag/v2.2.8) + +### New Features 🎉 + +* Replace custom parsing with TypeAdapter by [@jlowin](https://github.com/jlowin) in [#314](https://github.com/jlowin/fastmcp/pull/314) +* Handle \*args/\*\*kwargs appropriately for various components by [@jlowin](https://github.com/jlowin) in [#317](https://github.com/jlowin/fastmcp/pull/317) +* Add timeout-graceful-shutdown as a default config for SSE app by [@jlowin](https://github.com/jlowin) in [#323](https://github.com/jlowin/fastmcp/pull/323) +* Ensure prompts return descriptions by [@jlowin](https://github.com/jlowin) in [#325](https://github.com/jlowin/fastmcp/pull/325) + +### Fixes 🐞 + +* Ensure that tool serialization has a graceful fallback by [@jlowin](https://github.com/jlowin) in [#310](https://github.com/jlowin/fastmcp/pull/310) + +### Docs 📚 + +* Update docs for clarity by [@jlowin](https://github.com/jlowin) in [#312](https://github.com/jlowin/fastmcp/pull/312) + +### Other Changes 🦾 + +* Remove is\_async attribute by [@jlowin](https://github.com/jlowin) in [#315](https://github.com/jlowin/fastmcp/pull/315) +* Dry out retrieving context kwarg by [@jlowin](https://github.com/jlowin) in [#316](https://github.com/jlowin/fastmcp/pull/316) + +**Full Changelog**: [v2.2.7...v2.2.8](https://github.com/jlowin/fastmcp/compare/v2.2.7...v2.2.8) + + + + +## [v2.2.7: You Auth to Know Better](https://github.com/jlowin/fastmcp/releases/tag/v2.2.7) + +### New Features 🎉 + +* use pydantic\_core.to\_json by [@jlowin](https://github.com/jlowin) in [#290](https://github.com/jlowin/fastmcp/pull/290) +* Ensure openapi descriptions are included in tool details by [@jlowin](https://github.com/jlowin) in [#293](https://github.com/jlowin/fastmcp/pull/293) +* Bump mcp to 1.7.1 by [@jlowin](https://github.com/jlowin) in [#298](https://github.com/jlowin/fastmcp/pull/298) +* Add support for tool annotations by [@jlowin](https://github.com/jlowin) in [#299](https://github.com/jlowin/fastmcp/pull/299) +* Add auth support by [@jlowin](https://github.com/jlowin) in [#300](https://github.com/jlowin/fastmcp/pull/300) +* Add low-level methods to client by [@jlowin](https://github.com/jlowin) in [#301](https://github.com/jlowin/fastmcp/pull/301) +* Add method for retrieving current starlette request to FastMCP context by [@jlowin](https://github.com/jlowin) in [#302](https://github.com/jlowin/fastmcp/pull/302) +* get\_starlette\_request → get\_http\_request by [@jlowin](https://github.com/jlowin) in [#303](https://github.com/jlowin/fastmcp/pull/303) +* Support custom Serializer for Tools by [@strawgate](https://github.com/strawgate) in [#308](https://github.com/jlowin/fastmcp/pull/308) +* Support proxy mount by [@jlowin](https://github.com/jlowin) in [#309](https://github.com/jlowin/fastmcp/pull/309) + +### Other Changes 🦾 + +* Improve context injection type checks by [@jlowin](https://github.com/jlowin) in [#291](https://github.com/jlowin/fastmcp/pull/291) +* add readme to smarthome example by [@zzstoatzz](https://github.com/zzstoatzz) in [#294](https://github.com/jlowin/fastmcp/pull/294) + +**Full Changelog**: [v2.2.6...v2.2.7](https://github.com/jlowin/fastmcp/compare/v2.2.6...v2.2.7) + + + + +## [v2.2.6: The REST is History](https://github.com/jlowin/fastmcp/releases/tag/v2.2.6) + +### New Features 🎉 + +* Added feature : Load MCP server using config by [@sandipan1](https://github.com/sandipan1) in [#260](https://github.com/jlowin/fastmcp/pull/260) +* small typing fixes by [@zzstoatzz](https://github.com/zzstoatzz) in [#237](https://github.com/jlowin/fastmcp/pull/237) +* Expose configurable timeout for OpenAPI by [@jlowin](https://github.com/jlowin) in [#279](https://github.com/jlowin/fastmcp/pull/279) +* Lower websockets pin for compatibility by [@jlowin](https://github.com/jlowin) in [#286](https://github.com/jlowin/fastmcp/pull/286) +* Improve OpenAPI param handling by [@jlowin](https://github.com/jlowin) in [#287](https://github.com/jlowin/fastmcp/pull/287) + +### Fixes 🐞 + +* Ensure openapi tool responses are properly converted by [@jlowin](https://github.com/jlowin) in [#283](https://github.com/jlowin/fastmcp/pull/283) +* Fix OpenAPI examples by [@jlowin](https://github.com/jlowin) in [#285](https://github.com/jlowin/fastmcp/pull/285) +* Fix client docs for advanced features, add tests for logging by [@jlowin](https://github.com/jlowin) in [#284](https://github.com/jlowin/fastmcp/pull/284) + +### Other Changes 🦾 + +* add testing doc by [@jlowin](https://github.com/jlowin) in [#264](https://github.com/jlowin/fastmcp/pull/264) +* #267 Fix openapi template resource to support multiple path parameters by [@jeger-at](https://github.com/jeger-at) in [#278](https://github.com/jlowin/fastmcp/pull/278) + +### New Contributors + +* [@sandipan1](https://github.com/sandipan1) made their first contribution in [#260](https://github.com/jlowin/fastmcp/pull/260) +* [@jeger-at](https://github.com/jeger-at) made their first contribution in [#278](https://github.com/jlowin/fastmcp/pull/278) + +**Full Changelog**: [v2.2.5...v2.2.6](https://github.com/jlowin/fastmcp/compare/v2.2.5...v2.2.6) + + + + +## [v2.2.5: Context Switching](https://github.com/jlowin/fastmcp/releases/tag/v2.2.5) + +### New Features 🎉 + +* Add tests for tool return types; improve serialization behavior by [@jlowin](https://github.com/jlowin) in [#262](https://github.com/jlowin/fastmcp/pull/262) +* Support context injection in resources, templates, and prompts (like tools) by [@jlowin](https://github.com/jlowin) in [#263](https://github.com/jlowin/fastmcp/pull/263) + +### Docs 📚 + +* Update wildcards to 2.2.4 by [@jlowin](https://github.com/jlowin) in [#257](https://github.com/jlowin/fastmcp/pull/257) +* Update note in templates docs by [@jlowin](https://github.com/jlowin) in [#258](https://github.com/jlowin/fastmcp/pull/258) +* Significant documentation and test expansion for tool input types by [@jlowin](https://github.com/jlowin) in [#261](https://github.com/jlowin/fastmcp/pull/261) + +**Full Changelog**: [v2.2.4...v2.2.5](https://github.com/jlowin/fastmcp/compare/v2.2.4...v2.2.5) + + + + +## [v2.2.4: The Wild Side, Actually](https://github.com/jlowin/fastmcp/releases/tag/v2.2.4) + +The wildcard URI templates exposed in v2.2.3 were blocked by a server-level check which is removed in this release. + +### New Features 🎉 + +* Allow customization of inspector proxy port, ui port, and version by [@jlowin](https://github.com/jlowin) in [#253](https://github.com/jlowin/fastmcp/pull/253) + +### Fixes 🐞 + +* fix: unintended type convert by [@cutekibry](https://github.com/cutekibry) in [#252](https://github.com/jlowin/fastmcp/pull/252) +* Ensure openapi resources return valid responses by [@jlowin](https://github.com/jlowin) in [#254](https://github.com/jlowin/fastmcp/pull/254) +* Ensure servers expose template wildcards by [@jlowin](https://github.com/jlowin) in [#256](https://github.com/jlowin/fastmcp/pull/256) + +### Docs 📚 + +* Update README.md Grammar error by [@TechWithTy](https://github.com/TechWithTy) in [#249](https://github.com/jlowin/fastmcp/pull/249) + +### Other Changes 🦾 + +* Add resource template tests by [@jlowin](https://github.com/jlowin) in [#255](https://github.com/jlowin/fastmcp/pull/255) + +### New Contributors + +* [@TechWithTy](https://github.com/TechWithTy) made their first contribution in [#249](https://github.com/jlowin/fastmcp/pull/249) +* [@cutekibry](https://github.com/cutekibry) made their first contribution in [#252](https://github.com/jlowin/fastmcp/pull/252) + +**Full Changelog**: [v2.2.3...v2.2.4](https://github.com/jlowin/fastmcp/compare/v2.2.3...v2.2.4) + + + + +## [v2.2.3: The Wild Side](https://github.com/jlowin/fastmcp/releases/tag/v2.2.3) + +### New Features 🎉 + +* Add wildcard params for resource templates by [@jlowin](https://github.com/jlowin) in [#246](https://github.com/jlowin/fastmcp/pull/246) + +### Docs 📚 + +* Indicate that Image class is for returns by [@jlowin](https://github.com/jlowin) in [#242](https://github.com/jlowin/fastmcp/pull/242) +* Update mermaid diagram by [@jlowin](https://github.com/jlowin) in [#243](https://github.com/jlowin/fastmcp/pull/243) + +### Other Changes 🦾 + +* update version badges by [@jlowin](https://github.com/jlowin) in [#248](https://github.com/jlowin/fastmcp/pull/248) + +**Full Changelog**: [v2.2.2...v2.2.3](https://github.com/jlowin/fastmcp/compare/v2.2.2...v2.2.3) + + + + +## [v2.2.2: Prompt and Circumstance](https://github.com/jlowin/fastmcp/releases/tag/v2.2.2) + +### New Features 🎉 + +* Add prompt support by [@jlowin](https://github.com/jlowin) in [#235](https://github.com/jlowin/fastmcp/pull/235) + +### Fixes 🐞 + +* Ensure that resource templates are properly exposed by [@jlowin](https://github.com/jlowin) in [#238](https://github.com/jlowin/fastmcp/pull/238) + +### Docs 📚 + +* Update docs for prompts by [@jlowin](https://github.com/jlowin) in [#236](https://github.com/jlowin/fastmcp/pull/236) + +### Other Changes 🦾 + +* Add prompt tests by [@jlowin](https://github.com/jlowin) in [#239](https://github.com/jlowin/fastmcp/pull/239) + +**Full Changelog**: [v2.2.1...v2.2.2](https://github.com/jlowin/fastmcp/compare/v2.2.1...v2.2.2) + + + + +## [v2.2.1: Template for Success](https://github.com/jlowin/fastmcp/releases/tag/v2.2.1) + +### New Features 🎉 + +* Add resource templates by [@jlowin](https://github.com/jlowin) in [#230](https://github.com/jlowin/fastmcp/pull/230) + +### Fixes 🐞 + +* Ensure that resource templates are properly exposed by [@jlowin](https://github.com/jlowin) in [#231](https://github.com/jlowin/fastmcp/pull/231) + +### Docs 📚 + +* Update docs for resource templates by [@jlowin](https://github.com/jlowin) in [#232](https://github.com/jlowin/fastmcp/pull/232) + +### Other Changes 🦾 + +* Add resource template tests by [@jlowin](https://github.com/jlowin) in [#233](https://github.com/jlowin/fastmcp/pull/233) + +**Full Changelog**: [v2.2.0...v2.2.1](https://github.com/jlowin/fastmcp/compare/v2.2.0...v2.2.1) + + + + +## [v2.2.0: Compose Yourself](https://github.com/jlowin/fastmcp/releases/tag/v2.2.0) + +### New Features 🎉 + +* Add support for mounting FastMCP servers by [@jlowin](https://github.com/jlowin) in [#175](https://github.com/jlowin/fastmcp/pull/175) +* Add support for duplicate behavior == ignore by [@jlowin](https://github.com/jlowin) in [#169](https://github.com/jlowin/fastmcp/pull/169) + +### Breaking Changes 🛫 + +* Refactor MCP composition by [@jlowin](https://github.com/jlowin) in [#176](https://github.com/jlowin/fastmcp/pull/176) + +### Docs 📚 + +* Improve integration documentation by [@jlowin](https://github.com/jlowin) in [#184](https://github.com/jlowin/fastmcp/pull/184) +* Improve documentation by [@jlowin](https://github.com/jlowin) in [#185](https://github.com/jlowin/fastmcp/pull/185) + +### Other Changes 🦾 + +* Add transport kwargs for mcp.run() and fastmcp run by [@jlowin](https://github.com/jlowin) in [#161](https://github.com/jlowin/fastmcp/pull/161) +* Allow resource templates to have optional / excluded arguments by [@jlowin](https://github.com/jlowin) in [#164](https://github.com/jlowin/fastmcp/pull/164) +* Update resources.mdx by [@jlowin](https://github.com/jlowin) in [#165](https://github.com/jlowin/fastmcp/pull/165) + +### New Contributors + +* [@kongqi404](https://github.com/kongqi404) made their first contribution in [#181](https://github.com/jlowin/fastmcp/pull/181) + +**Full Changelog**: [v2.1.2...v2.2.0](https://github.com/jlowin/fastmcp/compare/v2.1.2...v2.2.0) + + + + +## [v2.1.2: Copy That, Good Buddy](https://github.com/jlowin/fastmcp/releases/tag/v2.1.2) + +The main improvement in this release is a fix that allows FastAPI / OpenAPI-generated servers to be mounted as sub-servers. + +### Fixes 🐞 + +* Ensure objects are copied properly and test mounting fastapi by [@jlowin](https://github.com/jlowin) in [#153](https://github.com/jlowin/fastmcp/pull/153) + +### Docs 📚 + +* Fix broken links in docs by [@jlowin](https://github.com/jlowin) in [#154](https://github.com/jlowin/fastmcp/pull/154) + +### Other Changes 🦾 + +* Update README.md by [@jlowin](https://github.com/jlowin) in [#149](https://github.com/jlowin/fastmcp/pull/149) +* Only apply log config to FastMCP loggers by [@jlowin](https://github.com/jlowin) in [#155](https://github.com/jlowin/fastmcp/pull/155) +* Update pyproject.toml by [@jlowin](https://github.com/jlowin) in [#156](https://github.com/jlowin/fastmcp/pull/156) + +**Full Changelog**: [v2.1.1...v2.1.2](https://github.com/jlowin/fastmcp/compare/v2.1.1...v2.1.2) + + + + +## [v2.1.1: Doc Holiday](https://github.com/jlowin/fastmcp/releases/tag/v2.1.1) + +FastMCP's docs are now available at gofastmcp.com. + +### Docs 📚 + +* Add docs by [@jlowin](https://github.com/jlowin) in [#136](https://github.com/jlowin/fastmcp/pull/136) +* Add docs link to readme by [@jlowin](https://github.com/jlowin) in [#137](https://github.com/jlowin/fastmcp/pull/137) +* Minor docs updates by [@jlowin](https://github.com/jlowin) in [#138](https://github.com/jlowin/fastmcp/pull/138) + +### Fixes 🐞 + +* fix branch name in example by [@zzstoatzz](https://github.com/zzstoatzz) in [#140](https://github.com/jlowin/fastmcp/pull/140) + +### Other Changes 🦾 + +* smart home example by [@zzstoatzz](https://github.com/zzstoatzz) in [#115](https://github.com/jlowin/fastmcp/pull/115) +* Remove mac os tests by [@jlowin](https://github.com/jlowin) in [#142](https://github.com/jlowin/fastmcp/pull/142) +* Expand support for various method interactions by [@jlowin](https://github.com/jlowin) in [#143](https://github.com/jlowin/fastmcp/pull/143) +* Update docs and add\_resource\_fn by [@jlowin](https://github.com/jlowin) in [#144](https://github.com/jlowin/fastmcp/pull/144) +* Update description by [@jlowin](https://github.com/jlowin) in [#145](https://github.com/jlowin/fastmcp/pull/145) +* Support openapi 3.0 and 3.1 by [@jlowin](https://github.com/jlowin) in [#147](https://github.com/jlowin/fastmcp/pull/147) + +**Full Changelog**: [v2.1.0...v2.1.1](https://github.com/jlowin/fastmcp/compare/v2.1.0...v2.1.1) + + + + +## [v2.1.0: Tag, You're It](https://github.com/jlowin/fastmcp/releases/tag/v2.1.0) + +The primary motivation for this release is the fix in #128 for Claude desktop compatibility, but the primary new feature of this release is per-object tags. Currently these are for bookkeeping only but will become useful in future releases. + +### New Features 🎉 + +* Add tags for all core MCP objects by [@jlowin](https://github.com/jlowin) in [#121](https://github.com/jlowin/fastmcp/pull/121) +* Ensure that openapi tags are transferred to MCP objects by [@jlowin](https://github.com/jlowin) in [#124](https://github.com/jlowin/fastmcp/pull/124) + +### Fixes 🐞 + +* Change default mounted tool separator from / to \_ by [@jlowin](https://github.com/jlowin) in [#128](https://github.com/jlowin/fastmcp/pull/128) +* Enter mounted app lifespans by [@jlowin](https://github.com/jlowin) in [#129](https://github.com/jlowin/fastmcp/pull/129) +* Fix CLI that called mcp instead of fastmcp by [@jlowin](https://github.com/jlowin) in [#128](https://github.com/jlowin/fastmcp/pull/128) + +### Breaking Changes 🛫 + +* Changed configuration for duplicate resources/tools/prompts by [@jlowin](https://github.com/jlowin) in [#121](https://github.com/jlowin/fastmcp/pull/121) +* Improve client return types by [@jlowin](https://github.com/jlowin) in [#123](https://github.com/jlowin/fastmcp/pull/123) + +### Other Changes 🦾 + +* Add tests for tags in server decorators by [@jlowin](https://github.com/jlowin) in [#122](https://github.com/jlowin/fastmcp/pull/122) +* Clean up server tests by [@jlowin](https://github.com/jlowin) in [#125](https://github.com/jlowin/fastmcp/pull/125) + +**Full Changelog**: [v2.0.0...v2.1.0](https://github.com/jlowin/fastmcp/compare/v2.0.0...v2.1.0) + + + + +## [v2.0.0: Second to None](https://github.com/jlowin/fastmcp/releases/tag/v2.0.0) + +### New Features 🎉 + +* Support mounting FastMCP instances as sub-MCPs by [@jlowin](https://github.com/jlowin) in [#99](https://github.com/jlowin/fastmcp/pull/99) +* Add in-memory client for calling FastMCP servers (and tests) by [@jlowin](https://github.com/jlowin) in [#100](https://github.com/jlowin/fastmcp/pull/100) +* Add MCP proxy server by [@jlowin](https://github.com/jlowin) in [#105](https://github.com/jlowin/fastmcp/pull/105) +* Update FastMCP for upstream changes by [@jlowin](https://github.com/jlowin) in [#107](https://github.com/jlowin/fastmcp/pull/107) +* Generate FastMCP servers from OpenAPI specs and FastAPI by [@jlowin](https://github.com/jlowin) in [#110](https://github.com/jlowin/fastmcp/pull/110) +* Reorganize all client / transports by [@jlowin](https://github.com/jlowin) in [#111](https://github.com/jlowin/fastmcp/pull/111) +* Add sampling and roots by [@jlowin](https://github.com/jlowin) in [#117](https://github.com/jlowin/fastmcp/pull/117) + +### Fixes 🐞 + +* Fix bug with tools that return lists by [@jlowin](https://github.com/jlowin) in [#116](https://github.com/jlowin/fastmcp/pull/116) + +### Other Changes 🦾 + +* Add back FastMCP CLI by [@jlowin](https://github.com/jlowin) in [#108](https://github.com/jlowin/fastmcp/pull/108) +* Update Readme for v2 by [@jlowin](https://github.com/jlowin) in [#112](https://github.com/jlowin/fastmcp/pull/112) +* fix deprecation warnings by [@zzstoatzz](https://github.com/zzstoatzz) in [#113](https://github.com/jlowin/fastmcp/pull/113) +* Readme by [@jlowin](https://github.com/jlowin) in [#118](https://github.com/jlowin/fastmcp/pull/118) +* FastMCP 2.0 by [@jlowin](https://github.com/jlowin) in [#119](https://github.com/jlowin/fastmcp/pull/119) + +**Full Changelog**: [v1.0...v2.0.0](https://github.com/jlowin/fastmcp/compare/v1.0...v2.0.0) + + + + +## [v1.0: It's Official](https://github.com/jlowin/fastmcp/releases/tag/v1.0) + +This release commemorates FastMCP 1.0, which is included in the official Model Context Protocol SDK: + +```python +from mcp.server.fastmcp import FastMCP +``` + +To the best of my knowledge, v1 is identical to the upstream version included with `mcp`. + +### Docs 📚 + +* Update readme to redirect to the official SDK by [@jlowin](https://github.com/jlowin) in [#79](https://github.com/jlowin/fastmcp/pull/79) + +### Other Changes 🦾 + +* fix: use Mount instead of Route for SSE message handling by [@samihamine](https://github.com/samihamine) in [#77](https://github.com/jlowin/fastmcp/pull/77) + +### New Contributors + +* [@samihamine](https://github.com/samihamine) made their first contribution in [#77](https://github.com/jlowin/fastmcp/pull/77) + +**Full Changelog**: [v0.4.1...v1.0](https://github.com/jlowin/fastmcp/compare/v0.4.1...v1.0) + + + + +## [v0.4.1: String Theory](https://github.com/jlowin/fastmcp/releases/tag/v0.4.1) + +### Fixes 🐞 + +* fix: handle strings containing numbers correctly by [@sd2k](https://github.com/sd2k) in [#63](https://github.com/jlowin/fastmcp/pull/63) + +### Docs 📚 + +* patch: Update pyproject.toml license by [@leonkozlowski](https://github.com/leonkozlowski) in [#67](https://github.com/jlowin/fastmcp/pull/67) + +### Other Changes 🦾 + +* Avoid new try\_eval\_type unavailable with older pydantic by [@jurasofish](https://github.com/jurasofish) in [#57](https://github.com/jlowin/fastmcp/pull/57) +* Decorator typing by [@jurasofish](https://github.com/jurasofish) in [#56](https://github.com/jlowin/fastmcp/pull/56) + +### New Contributors + +* [@leonkozlowski](https://github.com/leonkozlowski) made their first contribution in [#67](https://github.com/jlowin/fastmcp/pull/67) + +**Full Changelog**: [v0.4.0...v0.4.1](https://github.com/jlowin/fastmcp/compare/v0.4.0...v0.4.1) + + + + +## [v0.4.0: Nice to MIT You](https://github.com/jlowin/fastmcp/releases/tag/v0.4.0) + +This is a relatively small release in terms of features, but the version is bumped to 0.4 to reflect that the code is being relicensed from Apache 2.0 to MIT. This is to facilitate FastMCP's inclusion in the official MCP SDK. + +### New Features 🎉 + +* Add pyright + tests by [@jlowin](https://github.com/jlowin) in [#52](https://github.com/jlowin/fastmcp/pull/52) +* add pgvector memory example by [@zzstoatzz](https://github.com/zzstoatzz) in [#49](https://github.com/jlowin/fastmcp/pull/49) + +### Fixes 🐞 + +* fix: use stderr for logging by [@sd2k](https://github.com/sd2k) in [#51](https://github.com/jlowin/fastmcp/pull/51) + +### Docs 📚 + +* Update ai-labeler.yml by [@jlowin](https://github.com/jlowin) in [#48](https://github.com/jlowin/fastmcp/pull/48) +* Relicense from Apache 2.0 to MIT by [@jlowin](https://github.com/jlowin) in [#54](https://github.com/jlowin/fastmcp/pull/54) + +### Other Changes 🦾 + +* fix warning and flake by [@zzstoatzz](https://github.com/zzstoatzz) in [#47](https://github.com/jlowin/fastmcp/pull/47) + +### New Contributors + +* [@sd2k](https://github.com/sd2k) made their first contribution in [#51](https://github.com/jlowin/fastmcp/pull/51) + +**Full Changelog**: [v0.3.5...v0.4.0](https://github.com/jlowin/fastmcp/compare/v0.3.5...v0.4.0) + + + + +## [v0.3.5: Windows of Opportunity](https://github.com/jlowin/fastmcp/releases/tag/v0.3.5) + +This release is highlighted by the ability to handle complex JSON objects as MCP inputs and improved Windows compatibility. + +### New Features 🎉 + +* Set up multiple os tests by [@jlowin](https://github.com/jlowin) in [#44](https://github.com/jlowin/fastmcp/pull/44) +* Changes to accommodate windows users. by [@justjoehere](https://github.com/justjoehere) in [#42](https://github.com/jlowin/fastmcp/pull/42) +* Handle complex inputs by [@jurasofish](https://github.com/jurasofish) in [#31](https://github.com/jlowin/fastmcp/pull/31) + +### Docs 📚 + +* Make AI labeler more conservative by [@jlowin](https://github.com/jlowin) in [#46](https://github.com/jlowin/fastmcp/pull/46) + +### Other Changes 🦾 + +* Additional Windows Fixes for Dev running and for importing modules in a server by [@justjoehere](https://github.com/justjoehere) in [#43](https://github.com/jlowin/fastmcp/pull/43) + +### New Contributors + +* [@justjoehere](https://github.com/justjoehere) made their first contribution in [#42](https://github.com/jlowin/fastmcp/pull/42) +* [@jurasofish](https://github.com/jurasofish) made their first contribution in [#31](https://github.com/jlowin/fastmcp/pull/31) + +**Full Changelog**: [v0.3.4...v0.3.5](https://github.com/jlowin/fastmcp/compare/v0.3.4...v0.3.5) + + + + +## [v0.3.4: URL's Well That Ends Well](https://github.com/jlowin/fastmcp/releases/tag/v0.3.4) + +### Fixes 🐞 + +* Handle missing config file when installing by [@jlowin](https://github.com/jlowin) in [#37](https://github.com/jlowin/fastmcp/pull/37) +* Remove BaseURL reference and use AnyURL by [@jlowin](https://github.com/jlowin) in [#40](https://github.com/jlowin/fastmcp/pull/40) + +**Full Changelog**: [v0.3.3...v0.3.4](https://github.com/jlowin/fastmcp/compare/v0.3.3...v0.3.4) + + + + +## [v0.3.3: Dependence Day](https://github.com/jlowin/fastmcp/releases/tag/v0.3.3) + +### New Features 🎉 + +* Surge example by [@zzstoatzz](https://github.com/zzstoatzz) in [#29](https://github.com/jlowin/fastmcp/pull/29) +* Support Python dependencies in Server by [@jlowin](https://github.com/jlowin) in [#34](https://github.com/jlowin/fastmcp/pull/34) + +### Docs 📚 + +* add `Contributing` section to README by [@zzstoatzz](https://github.com/zzstoatzz) in [#32](https://github.com/jlowin/fastmcp/pull/32) + +**Full Changelog**: [v0.3.2...v0.3.3](https://github.com/jlowin/fastmcp/compare/v0.3.2...v0.3.3) + + + + +## [v0.3.2: Green with ENVy](https://github.com/jlowin/fastmcp/releases/tag/v0.3.2) + +### New Features 🎉 + +* Support env vars when installing by [@jlowin](https://github.com/jlowin) in [#27](https://github.com/jlowin/fastmcp/pull/27) + +### Docs 📚 + +* Remove top level env var by [@jlowin](https://github.com/jlowin) in [#28](https://github.com/jlowin/fastmcp/pull/28) + +**Full Changelog**: [v0.3.1...v0.3.2](https://github.com/jlowin/fastmcp/compare/v0.3.1...v0.3.2) + + + + +## [v0.3.1](https://github.com/jlowin/fastmcp/releases/tag/v0.3.1) + +### New Features 🎉 + +* Update README.md by [@jlowin](https://github.com/jlowin) in [#23](https://github.com/jlowin/fastmcp/pull/23) +* add rich handler and dotenv loading for settings by [@zzstoatzz](https://github.com/zzstoatzz) in [#22](https://github.com/jlowin/fastmcp/pull/22) +* print exception when server can't start by [@jlowin](https://github.com/jlowin) in [#25](https://github.com/jlowin/fastmcp/pull/25) + +### Docs 📚 + +* Update README.md by [@jlowin](https://github.com/jlowin) in [#24](https://github.com/jlowin/fastmcp/pull/24) + +### Other Changes 🦾 + +* Remove log by [@jlowin](https://github.com/jlowin) in [#26](https://github.com/jlowin/fastmcp/pull/26) + +**Full Changelog**: [v0.3.0...v0.3.1](https://github.com/jlowin/fastmcp/compare/v0.3.0...v0.3.1) + + + + +## [v0.3.0: Prompt and Circumstance](https://github.com/jlowin/fastmcp/releases/tag/v0.3.0) + +### New Features 🎉 + +* Update README by [@jlowin](https://github.com/jlowin) in [#3](https://github.com/jlowin/fastmcp/pull/3) +* Make log levels strings by [@jlowin](https://github.com/jlowin) in [#4](https://github.com/jlowin/fastmcp/pull/4) +* Make content method a function by [@jlowin](https://github.com/jlowin) in [#5](https://github.com/jlowin/fastmcp/pull/5) +* Add template support by [@jlowin](https://github.com/jlowin) in [#6](https://github.com/jlowin/fastmcp/pull/6) +* Refactor resources module by [@jlowin](https://github.com/jlowin) in [#7](https://github.com/jlowin/fastmcp/pull/7) +* Clean up cli imports by [@jlowin](https://github.com/jlowin) in [#8](https://github.com/jlowin/fastmcp/pull/8) +* Prepare to list templates by [@jlowin](https://github.com/jlowin) in [#11](https://github.com/jlowin/fastmcp/pull/11) +* Move image to separate module by [@jlowin](https://github.com/jlowin) in [#9](https://github.com/jlowin/fastmcp/pull/9) +* Add support for request context, progress, logging, etc. by [@jlowin](https://github.com/jlowin) in [#12](https://github.com/jlowin/fastmcp/pull/12) +* Add context tests and better runtime loads by [@jlowin](https://github.com/jlowin) in [#13](https://github.com/jlowin/fastmcp/pull/13) +* Refactor tools + resourcemanager by [@jlowin](https://github.com/jlowin) in [#14](https://github.com/jlowin/fastmcp/pull/14) +* func → fn everywhere by [@jlowin](https://github.com/jlowin) in [#15](https://github.com/jlowin/fastmcp/pull/15) +* Add support for prompts by [@jlowin](https://github.com/jlowin) in [#16](https://github.com/jlowin/fastmcp/pull/16) +* Create LICENSE by [@jlowin](https://github.com/jlowin) in [#18](https://github.com/jlowin/fastmcp/pull/18) +* Update cli file spec by [@jlowin](https://github.com/jlowin) in [#19](https://github.com/jlowin/fastmcp/pull/19) +* Update readmeUpdate README by [@jlowin](https://github.com/jlowin) in [#20](https://github.com/jlowin/fastmcp/pull/20) +* Use hatchling for version by [@jlowin](https://github.com/jlowin) in [#21](https://github.com/jlowin/fastmcp/pull/21) + +### Other Changes 🦾 + +* Add echo server by [@jlowin](https://github.com/jlowin) in [#1](https://github.com/jlowin/fastmcp/pull/1) +* Add github workflows by [@jlowin](https://github.com/jlowin) in [#2](https://github.com/jlowin/fastmcp/pull/2) +* typing updates by [@zzstoatzz](https://github.com/zzstoatzz) in [#17](https://github.com/jlowin/fastmcp/pull/17) + +### New Contributors + +* [@jlowin](https://github.com/jlowin) made their first contribution in [#1](https://github.com/jlowin/fastmcp/pull/1) +* [@zzstoatzz](https://github.com/zzstoatzz) made their first contribution in [#17](https://github.com/jlowin/fastmcp/pull/17) + +**Full Changelog**: [v0.2.0...v0.3.0](https://github.com/jlowin/fastmcp/compare/v0.2.0...v0.3.0) + + + + +## [v0.2.0](https://github.com/jlowin/fastmcp/releases/tag/v0.2.0) + +**Full Changelog**: [v0.1.0...v0.2.0](https://github.com/jlowin/fastmcp/compare/v0.1.0...v0.2.0) + + + + +## [v0.1.0](https://github.com/jlowin/fastmcp/releases/tag/v0.1.0) + +The very first release of FastMCP! 🎉 + +**Full Changelog**: [Initial commits](https://github.com/jlowin/fastmcp/commits/v0.1.0) + \ No newline at end of file diff --git a/docs/v2/clients/auth/bearer.mdx b/docs/v2/clients/auth/bearer.mdx new file mode 100644 index 000000000..0a5b57902 --- /dev/null +++ b/docs/v2/clients/auth/bearer.mdx @@ -0,0 +1,88 @@ +--- +title: Bearer Token Authentication +sidebarTitle: Bearer Auth +description: Authenticate your FastMCP client with a Bearer token. +icon: key +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + + +Bearer Token authentication is only relevant for HTTP-based transports. + + +You can configure your FastMCP client to use **bearer authentication** by supplying a valid access token. This is most appropriate for service accounts, long-lived API keys, CI/CD, applications where authentication is managed separately, or other non-interactive authentication methods. + +A Bearer token is a JSON Web Token (JWT) that is used to authenticate a request. It is most commonly used in the `Authorization` header of an HTTP request, using the `Bearer` scheme: + +```http +Authorization: Bearer +``` + + +## Client Usage + +The most straightforward way to use a pre-existing Bearer token is to provide it as a string to the `auth` parameter of the `fastmcp.Client` or transport instance. FastMCP will automatically format it correctly for the `Authorization` header and bearer scheme. + + +If you're using a string token, do not include the `Bearer` prefix. FastMCP will add it for you. + + +```python {5} +from fastmcp import Client + +async with Client( + "https://fastmcp.cloud/mcp", + auth="", +) as client: + await client.ping() +``` + +You can also supply a Bearer token to a transport instance, such as `StreamableHttpTransport` or `SSETransport`: + +```python {6} +from fastmcp import Client +from fastmcp.client.transports import StreamableHttpTransport + +transport = StreamableHttpTransport( + "http://fastmcp.cloud/mcp", + auth="", +) + +async with Client(transport) as client: + await client.ping() +``` + +## `BearerAuth` Helper + +If you prefer to be more explicit and not rely on FastMCP to transform your string token, you can use the `BearerAuth` class yourself, which implements the `httpx.Auth` interface. + +```python {6} +from fastmcp import Client +from fastmcp.client.auth import BearerAuth + +async with Client( + "https://fastmcp.cloud/mcp", + auth=BearerAuth(token=""), +) as client: + await client.ping() +``` + +## Custom Headers + +If the MCP server expects a custom header or token scheme, you can manually set the client's `headers` instead of using the `auth` parameter by setting them on your transport: + +```python {5} +from fastmcp import Client +from fastmcp.client.transports import StreamableHttpTransport + +async with Client( + transport=StreamableHttpTransport( + "https://fastmcp.cloud/mcp", + headers={"X-API-Key": ""}, + ), +) as client: + await client.ping() +``` diff --git a/docs/v2/clients/auth/oauth.mdx b/docs/v2/clients/auth/oauth.mdx new file mode 100644 index 000000000..9403d3063 --- /dev/null +++ b/docs/v2/clients/auth/oauth.mdx @@ -0,0 +1,132 @@ +--- +title: OAuth Authentication +sidebarTitle: OAuth +description: Authenticate your FastMCP client via OAuth 2.1. +icon: window +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + + +OAuth authentication is only relevant for HTTP-based transports and requires user interaction via a web browser. + + +When your FastMCP client needs to access an MCP server protected by OAuth 2.1, and the process requires user interaction (like logging in and granting consent), you should use the Authorization Code Flow. FastMCP provides the `fastmcp.client.auth.OAuth` helper to simplify this entire process. + +This flow is common for user-facing applications where the application acts on behalf of the user. + +## Client Usage + + +### Default Configuration + +The simplest way to use OAuth is to pass the string `"oauth"` to the `auth` parameter of the `Client` or transport instance. FastMCP will automatically configure the client to use OAuth with default settings: + +```python {4} +from fastmcp import Client + +# Uses default OAuth settings +async with Client("https://fastmcp.cloud/mcp", auth="oauth") as client: + await client.ping() +``` + + +### `OAuth` Helper + +To fully configure the OAuth flow, use the `OAuth` helper and pass it to the `auth` parameter of the `Client` or transport instance. `OAuth` manages the complexities of the OAuth 2.1 Authorization Code Grant with PKCE (Proof Key for Code Exchange) for enhanced security, and implements the full `httpx.Auth` interface. + +```python {2, 4, 6} +from fastmcp import Client +from fastmcp.client.auth import OAuth + +oauth = OAuth(mcp_url="https://fastmcp.cloud/mcp") + +async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client: + await client.ping() +``` + +#### `OAuth` Parameters + +- **`mcp_url`** (`str`): The full URL of the target MCP server endpoint. Used to discover OAuth server metadata +- **`scopes`** (`str | list[str]`, optional): OAuth scopes to request. Can be space-separated string or list of strings +- **`client_name`** (`str`, optional): Client name for dynamic registration. Defaults to `"FastMCP Client"` +- **`token_storage`** (`AsyncKeyValue`, optional): Storage backend for persisting OAuth tokens. Defaults to in-memory storage (tokens lost on restart). See [Token Storage](#token-storage) for encrypted storage options +- **`additional_client_metadata`** (`dict[str, Any]`, optional): Extra metadata for client registration +- **`callback_port`** (`int`, optional): Fixed port for OAuth callback server. If not specified, uses a random available port + + +## OAuth Flow + +The OAuth flow is triggered when you use a FastMCP `Client` configured to use OAuth. + + + +The client first checks the configured `token_storage` backend for existing, valid tokens for the target server. If one is found, it will be used to authenticate the client. + + +If no valid tokens exist, the client attempts to discover the OAuth server's endpoints using a well-known URI (e.g., `/.well-known/oauth-authorization-server`) based on the `mcp_url`. + + +If the OAuth server supports it and the client isn't already registered (or credentials aren't cached), the client performs dynamic client registration according to RFC 7591. + + +A temporary local HTTP server is started on an available port (or the port specified via `callback_port`). This server's address (e.g., `http://127.0.0.1:/callback`) acts as the `redirect_uri` for the OAuth flow. + + +The user's default web browser is automatically opened, directing them to the OAuth server's authorization endpoint. The user logs in and grants (or denies) the requested `scopes`. + + +Upon approval, the OAuth server redirects the user's browser to the local callback server with an `authorization_code`. The client captures this code and exchanges it with the OAuth server's token endpoint for an `access_token` (and often a `refresh_token`) using PKCE for security. + + +The obtained tokens are saved to the configured `token_storage` backend for future use, eliminating the need for repeated browser interactions. + + +The access token is automatically included in the `Authorization` header for requests to the MCP server. + + +If the access token expires, the client will automatically use the refresh token to get a new access token. + + + +## Token Storage + + + +By default, tokens are stored in memory and lost when your application restarts. For persistent storage, pass an `AsyncKeyValue`-compatible storage backend to the `token_storage` parameter. + + +**Security Consideration**: Use encrypted storage for production. MCP clients can accumulate OAuth credentials for many servers over time, and a compromised token store could expose access to multiple services. + + +```python +from fastmcp import Client +from fastmcp.client.auth import OAuth +from key_value.aio.stores.disk import DiskStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet +import os + +# Create encrypted disk storage +encrypted_storage = FernetEncryptionWrapper( + key_value=DiskStore(directory="~/.fastmcp/oauth-tokens"), + fernet=Fernet(os.environ["OAUTH_STORAGE_ENCRYPTION_KEY"]) +) + +oauth = OAuth( + mcp_url="https://fastmcp.cloud/mcp", + token_storage=encrypted_storage +) + +async with Client("https://fastmcp.cloud/mcp", auth=oauth) as client: + await client.ping() +``` + +You can use any `AsyncKeyValue`-compatible backend from the [key-value library](https://github.com/strawgate/py-key-value) including Redis, DynamoDB, and more. Wrap your storage in `FernetEncryptionWrapper` for encryption. + + +When selecting a storage backend, review the [py-key-value documentation](https://github.com/strawgate/py-key-value) to understand the maturity level and limitations of your chosen backend. Some backends may be in preview or have constraints that affect production suitability. + diff --git a/docs/v2/clients/client.mdx b/docs/v2/clients/client.mdx new file mode 100644 index 000000000..36f300030 --- /dev/null +++ b/docs/v2/clients/client.mdx @@ -0,0 +1,340 @@ +--- +title: The FastMCP Client +sidebarTitle: Overview +description: Programmatic client for interacting with MCP servers through a well-typed, Pythonic interface. +icon: user-robot +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +The central piece of MCP client applications is the `fastmcp.Client` class. This class provides a **programmatic interface** for interacting with any Model Context Protocol (MCP) server, handling protocol details and connection management automatically. + +The FastMCP Client is designed for deterministic, controlled interactions rather than autonomous behavior, making it ideal for: + +- **Testing MCP servers** during development +- **Building deterministic applications** that need reliable MCP interactions +- **Creating the foundation for agentic or LLM-based clients** with structured, type-safe operations + +All client operations require using the `async with` context manager for proper connection lifecycle management. + + + +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. + + +## Creating a Client + +Creating a client is straightforward. You provide a server source and the client automatically infers the appropriate transport mechanism. + +```python +import asyncio +from fastmcp import Client, FastMCP + +# In-memory server (ideal for testing) +server = FastMCP("TestServer") +client = Client(server) + +# HTTP server +client = Client("https://example.com/mcp") + +# Local Python script +client = Client("my_mcp_server.py") + +async def main(): + async with client: + # Basic server interaction + await client.ping() + + # List available operations + tools = await client.list_tools() + resources = await client.list_resources() + prompts = await client.list_prompts() + + # Execute operations + result = await client.call_tool("example_tool", {"param": "value"}) + print(result) + +asyncio.run(main()) +``` + +## Client-Transport Architecture + +The FastMCP Client separates concerns between protocol and connection: + +- **`Client`**: Handles MCP protocol operations (tools, resources, prompts) and manages callbacks +- **`Transport`**: Establishes and maintains the connection (WebSockets, HTTP, Stdio, in-memory) + +### Transport Inference + +The client automatically infers the appropriate transport based on the input: + +1. **`FastMCP` instance** → In-memory transport (perfect for testing) +2. **File path ending in `.py`** → Python Stdio transport +3. **File path ending in `.js`** → Node.js Stdio transport +4. **URL starting with `http://` or `https://`** → HTTP transport +5. **`MCPConfig` dictionary** → Multi-server client + +```python +from fastmcp import Client, FastMCP + +# Examples of transport inference +client_memory = Client(FastMCP("TestServer")) +client_script = Client("./server.py") +client_http = Client("https://api.example.com/mcp") +``` + + +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. + + +## Configuration-Based Clients + + + +Create clients from MCP configuration dictionaries, which can include multiple servers. While there is no official standard for MCP configuration format, FastMCP follows established conventions used by tools like Claude Desktop. + +### Configuration Format + +```python +config = { + "mcpServers": { + "server_name": { + # Remote HTTP/SSE server + "transport": "http", # or "sse" + "url": "https://api.example.com/mcp", + "headers": {"Authorization": "Bearer token"}, + "auth": "oauth" # or bearer token string + }, + "local_server": { + # Local stdio server + "transport": "stdio", + "command": "python", + "args": ["./server.py", "--verbose"], + "env": {"DEBUG": "true"}, + "cwd": "/path/to/server", + } + } +} +``` + +### Multi-Server Example + +```python +config = { + "mcpServers": { + "weather": {"url": "https://weather-api.example.com/mcp"}, + "assistant": {"command": "python", "args": ["./assistant_server.py"]} + } +} + +client = Client(config) + +async with client: + # Tools are prefixed with server names + weather_data = await client.call_tool("weather_get_forecast", {"city": "London"}) + response = await client.call_tool("assistant_answer_question", {"question": "What's the capital of France?"}) + + # Resources use prefixed URIs + icons = await client.read_resource("weather://weather/icons/sunny") + templates = await client.read_resource("resource://assistant/templates/list") +``` + +## Connection Lifecycle + +The client operates asynchronously and uses context managers for connection management: + +```python +async def example(): + client = Client("my_mcp_server.py") + + # Connection established here + async with client: + print(f"Connected: {client.is_connected()}") + + # Make multiple calls within the same session + tools = await client.list_tools() + result = await client.call_tool("greet", {"name": "World"}) + + # Connection closed automatically here + print(f"Connected: {client.is_connected()}") +``` + +## Operations + +FastMCP clients can interact with several types of server components: + +### Tools + +Tools are server-side functions that the client can execute with arguments. + +```python +async with client: + # List available tools + tools = await client.list_tools() + + # Execute a tool + result = await client.call_tool("multiply", {"a": 5, "b": 3}) + print(result.data) # 15 +``` + +See [Tools](/clients/tools) for detailed documentation. + +### Resources + +Resources are data sources that the client can read, either static or templated. + +```python +async with client: + # List available resources + resources = await client.list_resources() + + # Read a resource + content = await client.read_resource("file:///config/settings.json") + print(content[0].text) +``` + +See [Resources](/clients/resources) for detailed documentation. + +### Prompts + +Prompts are reusable message templates that can accept arguments. + +```python +async with client: + # List available prompts + prompts = await client.list_prompts() + + # Get a rendered prompt + messages = await client.get_prompt("analyze_data", {"data": [1, 2, 3]}) + print(messages.messages) +``` + +See [Prompts](/clients/prompts) for detailed documentation. + +### Server Connectivity + +Use `ping()` to verify the server is reachable: + +```python +async with client: + await client.ping() + print("Server is reachable") +``` + +### Initialization and Server Information + +When you enter the client context manager, the client automatically performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions. The result is available through the `initialize_result` property. + +```python +from fastmcp import Client, FastMCP + +mcp = FastMCP(name="MyServer", instructions="Use the greet tool to say hello!") + +@mcp.tool +def greet(name: str) -> str: + """Greet a user by name.""" + return f"Hello, {name}!" + +async with Client(mcp) as client: + # Initialization already happened automatically + print(f"Server: {client.initialize_result.serverInfo.name}") + print(f"Version: {client.initialize_result.serverInfo.version}") + print(f"Instructions: {client.initialize_result.instructions}") + print(f"Capabilities: {client.initialize_result.capabilities.tools}") +``` + +#### Manual Initialization Control + +In advanced scenarios, you might want precise control over when initialization happens. For example, you may need custom error handling, want to defer initialization until after other setup, or need to measure initialization timing separately. + +Disable automatic initialization and call `initialize()` manually: + +```python +from fastmcp import Client + +# Disable automatic initialization +client = Client("my_mcp_server.py", auto_initialize=False) + +async with client: + # Connection established, but not initialized yet + print(f"Connected: {client.is_connected()}") + print(f"Initialized: {client.initialize_result is not None}") # False + + # Initialize manually with custom timeout + result = await client.initialize(timeout=10.0) + print(f"Server: {result.serverInfo.name}") + + # Now ready for operations + tools = await client.list_tools() +``` + +The `initialize()` method is idempotent - calling it multiple times returns the cached result from the first successful call. + +## Client Configuration + +Clients can be configured with additional handlers and settings for specialized use cases. + +### Callback Handlers + +The client supports several callback handlers for advanced server interactions: + +```python +from fastmcp import Client +from fastmcp.client.logging import LogMessage + +async def log_handler(message: LogMessage): + print(f"Server log: {message.data}") + +async def progress_handler(progress: float, total: float | None, message: str | None): + print(f"Progress: {progress}/{total} - {message}") + +async def sampling_handler(messages, params, context): + # Integrate with your LLM service here + return "Generated response" + +client = Client( + "my_mcp_server.py", + log_handler=log_handler, + progress_handler=progress_handler, + sampling_handler=sampling_handler, + timeout=30.0 +) +``` + +The `Client` constructor accepts several configuration options: + +- `transport`: Transport instance or source for automatic inference +- `log_handler`: Handle server log messages +- `progress_handler`: Monitor long-running operations +- `sampling_handler`: Respond to server LLM requests +- `roots`: Provide local context to servers +- `timeout`: Default timeout for requests (in seconds) + +### Transport Configuration + +For detailed transport configuration (headers, authentication, environment variables), see the [Transports](/clients/transports) documentation. + +## Next Steps + +Explore the detailed documentation for each operation type: + +### Core Operations +- **[Tools](/clients/tools)** - Execute server-side functions and handle results +- **[Resources](/clients/resources)** - Access static and templated resources +- **[Prompts](/clients/prompts)** - Work with message templates and argument serialization + +### Advanced Features +- **[Logging](/clients/logging)** - Handle server log messages +- **[Progress](/clients/progress)** - Monitor long-running operations +- **[Sampling](/clients/sampling)** - Respond to server LLM requests +- **[Roots](/clients/roots)** - Provide local context to servers + +### Connection Details +- **[Transports](/clients/transports)** - Configure connection methods and parameters +- **[Authentication](/clients/auth/oauth)** - Set up OAuth and bearer token authentication + + +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. + \ No newline at end of file diff --git a/docs/v2/clients/elicitation.mdx b/docs/v2/clients/elicitation.mdx new file mode 100644 index 000000000..15b0e5030 --- /dev/null +++ b/docs/v2/clients/elicitation.mdx @@ -0,0 +1,125 @@ +--- +title: User Elicitation +sidebarTitle: Elicitation +description: Handle server-initiated user input requests with structured schemas. +icon: message-question +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +## What is Elicitation? + +Elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, servers can interactively ask users for information as needed - like prompting for missing parameters, requesting clarification, or gathering additional context. + +For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?" + +## How FastMCP Makes Elicitation Easy + +FastMCP's client provides a helpful abstraction layer that: + +- **Converts JSON schemas to Python types**: The raw MCP protocol uses JSON schemas, but FastMCP automatically converts these to Python dataclasses +- **Provides structured constructors**: Instead of manually building dictionaries that match the schema, you get dataclass constructors that ensure correct structure +- **Handles type conversion**: FastMCP takes care of converting between JSON representations and Python objects +- **Runtime introspection**: You can inspect the generated dataclass fields to understand the expected structure + +When you implement an elicitation handler, FastMCP gives you a dataclass type that matches the server's schema, making it easy to create properly structured responses without having to manually parse JSON schemas. + +## Elicitation Handler + +Provide an `elicitation_handler` function when creating the client. FastMCP automatically converts the server's JSON schema into a Python dataclass type, making it easy to construct the response: + +```python +from fastmcp import Client +from fastmcp.client.elicitation import ElicitResult + +async def elicitation_handler(message: str, response_type: type, params, context): + # Present the message to the user and collect input + user_input = input(f"{message}: ") + + # Create response using the provided dataclass type + # FastMCP converted the JSON schema to this Python type for you + response_data = response_type(value=user_input) + + # You can return data directly - FastMCP will implicitly accept the elicitation + return response_data + + # Or explicitly return an ElicitResult for more control + # return ElicitResult(action="accept", content=response_data) + +client = Client( + "my_mcp_server.py", + elicitation_handler=elicitation_handler, +) +``` + +### Handler Parameters + +The elicitation handler receives four parameters: + + + + The prompt message to display to the user + + + + A Python dataclass type that FastMCP created from the server's JSON schema. Use this to construct your response with proper typing and IDE support. If the server requests an empty object (indicating no response), this will be `None`. + + + + The original MCP elicitation request parameters, including the raw JSON schema in `params.requestedSchema` if you need it + + + + Request context containing metadata about the elicitation request + + + +### Response Actions + +The handler can return data directly (which implicitly accepts the elicitation) or an `ElicitResult` object for more control over the response action: + + + + How the user responded to the elicitation request + + + + The user's input data (required for "accept", omitted for "decline"/"cancel") + + + +**Action Types:** +- **`accept`**: User provided valid input - include their data in the `content` field +- **`decline`**: User chose not to provide the requested information - omit `content` +- **`cancel`**: User cancelled the entire operation - omit `content` + +## Basic Example + +```python +from fastmcp import Client +from fastmcp.client.elicitation import ElicitResult + +async def basic_elicitation_handler(message: str, response_type: type, params, context): + print(f"Server asks: {message}") + + # Simple text input for demonstration + user_response = input("Your response: ") + + if not user_response: + # For non-acceptance, use ElicitResult explicitly + return ElicitResult(action="decline") + + # Use the response_type dataclass to create a properly structured response + # FastMCP handles the conversion from JSON schema to Python type + # Return data directly - FastMCP will implicitly accept the elicitation + return response_type(value=user_response) + +client = Client( + "my_mcp_server.py", + elicitation_handler=basic_elicitation_handler +) +``` + + diff --git a/docs/v2/clients/logging.mdx b/docs/v2/clients/logging.mdx new file mode 100644 index 000000000..ffebdf87f --- /dev/null +++ b/docs/v2/clients/logging.mdx @@ -0,0 +1,111 @@ +--- +title: Server Logging +sidebarTitle: Logging +description: Receive and handle log messages from MCP servers. +icon: receipt +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +MCP servers can emit log messages to clients. The client can handle these logs through a log handler callback. + +## Log Handler + +Provide a `log_handler` function when creating the client. For robust logging, the log messages can be integrated with Python's standard `logging` module. + +```python +import logging +from fastmcp import Client +from fastmcp.client.logging import LogMessage + +# In a real app, you might configure this in your main entry point +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +# Get a logger for the module where the client is used +logger = logging.getLogger(__name__) + +# This mapping is useful for converting MCP level strings to Python's levels +LOGGING_LEVEL_MAP = logging.getLevelNamesMapping() + +async def log_handler(message: LogMessage): + """ + Handles incoming logs from the MCP server and forwards them + to the standard Python logging system. + """ + msg = message.data.get('msg') + extra = message.data.get('extra') + + # Convert the MCP log level to a Python log level + level = LOGGING_LEVEL_MAP.get(message.level.upper(), logging.INFO) + + # Log the message using the standard logging library + logger.log(level, msg, extra=extra) + + +client = Client( + "my_mcp_server.py", + log_handler=log_handler, +) +``` + +## Handling Structured Logs + +The `message.data` attribute is a dictionary that contains the log payload from the server. This enables structured logging, allowing you to receive rich, contextual information. + +The dictionary contains two keys: +- `msg`: The string log message. +- `extra`: A dictionary containing any extra data sent from the server. + +This structure is preserved even when logs are forwarded through a FastMCP proxy, making it a powerful tool for debugging complex, multi-server applications. + +### Handler Parameters + +The `log_handler` is called every time a log message is received. It receives a `LogMessage` object: + + + + + + The log level + + + + The logger name (optional, may be None) + + + + The log payload, containing `msg` and `extra` keys. + + + + + +```python +async def detailed_log_handler(message: LogMessage): + msg = message.data.get('msg') + extra = message.data.get('extra') + + if message.level == "error": + print(f"ERROR: {msg} | Details: {extra}") + elif message.level == "warning": + print(f"WARNING: {msg} | Details: {extra}") + else: + print(f"{message.level.upper()}: {msg}") +``` + +## Default Log Handling + +If you don't provide a custom `log_handler`, FastMCP's default handler routes server logs to the appropriate Python logging levels. The MCP levels are mapped as follows: `notice` → INFO; `alert` and `emergency` → CRITICAL. If the server includes a logger name, it is prefixed in the message, and any `extra` data is forwarded via the logging `extra` parameter. + +```python +client = Client("my_mcp_server.py") + +async with client: + # Server logs are forwarded at their proper severity (DEBUG/INFO/WARNING/ERROR/CRITICAL) + await client.call_tool("some_tool") +``` diff --git a/docs/clients/messages.mdx b/docs/v2/clients/messages.mdx similarity index 100% rename from docs/clients/messages.mdx rename to docs/v2/clients/messages.mdx diff --git a/docs/v2/clients/progress.mdx b/docs/v2/clients/progress.mdx new file mode 100644 index 000000000..ff3e0aa85 --- /dev/null +++ b/docs/v2/clients/progress.mdx @@ -0,0 +1,70 @@ +--- +title: Progress Monitoring +sidebarTitle: Progress +description: Handle progress notifications from long-running server operations. +icon: bars-progress +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +MCP servers can report progress during long-running operations. The client can receive these updates through a progress handler. + +## Progress Handler + +Set a progress handler when creating the client: + +```python +from fastmcp import Client + +async def my_progress_handler( + progress: float, + total: float | None, + message: str | None +) -> None: + if total is not None: + percentage = (progress / total) * 100 + print(f"Progress: {percentage:.1f}% - {message or ''}") + else: + print(f"Progress: {progress} - {message or ''}") + +client = Client( + "my_mcp_server.py", + progress_handler=my_progress_handler +) +``` + +### Handler Parameters + +The progress handler receives three parameters: + + + + + Current progress value + + + + Expected total value (may be None) + + + + Optional status message (may be None) + + + + +## 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 + ) +``` diff --git a/docs/v2/clients/prompts.mdx b/docs/v2/clients/prompts.mdx new file mode 100644 index 000000000..a6d6e155b --- /dev/null +++ b/docs/v2/clients/prompts.mdx @@ -0,0 +1,216 @@ +--- +title: Prompts +sidebarTitle: Prompts +description: Use server-side prompt templates with automatic argument serialization. +icon: message-lines +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +Prompts are reusable message templates exposed by MCP servers. They can accept arguments to generate personalized message sequences for LLM interactions. + +## Listing Prompts + +Use `list_prompts()` to retrieve all available prompt templates: + +```python +async with client: + prompts = await client.list_prompts() + # prompts -> list[mcp.types.Prompt] + + for prompt in prompts: + print(f"Prompt: {prompt.name}") + print(f"Description: {prompt.description}") + if prompt.arguments: + print(f"Arguments: {[arg.name for arg in prompt.arguments]}") + # Access tags and other metadata + if hasattr(prompt, '_meta') and prompt._meta: + fastmcp_meta = prompt._meta.get('_fastmcp', {}) + print(f"Tags: {fastmcp_meta.get('tags', [])}") +``` + +### Filtering by Tags + + + +You can use the `meta` field to filter prompts based on their tags: + +```python +async with client: + prompts = await client.list_prompts() + + # Filter prompts by tag + analysis_prompts = [ + prompt for prompt in prompts + if hasattr(prompt, '_meta') and prompt._meta and + prompt._meta.get('_fastmcp', {}) and + 'analysis' in prompt._meta.get('_fastmcp', {}).get('tags', []) + ] + + print(f"Found {len(analysis_prompts)} analysis prompts") +``` + + +The `_meta` field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a `_fastmcp` namespace (e.g., `_meta._fastmcp.tags`) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server's `include_fastmcp_meta` setting - when disabled, the `_fastmcp` namespace won't be included. Other MCP server implementations may not provide this metadata structure. + + +## 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 + + + +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}") +``` + + +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. + \ No newline at end of file diff --git a/docs/v2/clients/resources.mdx b/docs/v2/clients/resources.mdx new file mode 100644 index 000000000..b5120fdf1 --- /dev/null +++ b/docs/v2/clients/resources.mdx @@ -0,0 +1,204 @@ +--- +title: Resource Operations +sidebarTitle: Resources +description: Access static and templated resources from MCP servers. +icon: folder-open +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +Resources are data sources exposed by MCP servers. They can be static files or dynamic templates that generate content based on parameters. + +## Types of Resources + +MCP servers expose two types of resources: + +- **Static Resources**: Fixed content accessible via URI (e.g., configuration files, documentation) +- **Resource Templates**: Dynamic resources that accept parameters to generate content (e.g., API endpoints, database queries) + +## Listing Resources + +### Static Resources + +Use `list_resources()` to retrieve all static resources available on the server: + +```python +async with client: + resources = await client.list_resources() + # resources -> list[mcp.types.Resource] + + for resource in resources: + print(f"Resource URI: {resource.uri}") + print(f"Name: {resource.name}") + print(f"Description: {resource.description}") + print(f"MIME Type: {resource.mimeType}") + # Access tags and other metadata + if hasattr(resource, '_meta') and resource._meta: + fastmcp_meta = resource._meta.get('_fastmcp', {}) + print(f"Tags: {fastmcp_meta.get('tags', [])}") +``` + +### Resource Templates + +Use `list_resource_templates()` to retrieve available resource templates: + +```python +async with client: + templates = await client.list_resource_templates() + # templates -> list[mcp.types.ResourceTemplate] + + for template in templates: + print(f"Template URI: {template.uriTemplate}") + print(f"Name: {template.name}") + print(f"Description: {template.description}") + # Access tags and other metadata + if hasattr(template, '_meta') and template._meta: + fastmcp_meta = template._meta.get('_fastmcp', {}) + print(f"Tags: {fastmcp_meta.get('tags', [])}") +``` + +### Filtering by Tags + + + +You can use the `meta` field to filter resources based on their tags: + +```python +async with client: + resources = await client.list_resources() + + # Filter resources by tag + config_resources = [ + resource for resource in resources + if hasattr(resource, '_meta') and resource._meta and + resource._meta.get('_fastmcp', {}) and + 'config' in resource._meta.get('_fastmcp', {}).get('tags', []) + ] + + print(f"Found {len(config_resources)} config resources") +``` + + +The `_meta` field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a `_fastmcp` namespace (e.g., `_meta._fastmcp.tags`) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server's `include_fastmcp_meta` setting - when disabled, the `_fastmcp` namespace won't be included. Other MCP server implementations may not provide this metadata structure. + + +## 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" +``` + + +Resource URIs and their formats depend on the specific MCP server implementation. Check the server's documentation for available resources and their URI patterns. + \ No newline at end of file diff --git a/docs/v2/clients/roots.mdx b/docs/v2/clients/roots.mdx new file mode 100644 index 000000000..2a8d8c1f9 --- /dev/null +++ b/docs/v2/clients/roots.mdx @@ -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' + + + +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: + + +```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 +) +``` + + diff --git a/docs/v2/clients/sampling.mdx b/docs/v2/clients/sampling.mdx new file mode 100644 index 000000000..af1b65656 --- /dev/null +++ b/docs/v2/clients/sampling.mdx @@ -0,0 +1,258 @@ +--- +title: LLM Sampling +sidebarTitle: Sampling +description: Handle server-initiated LLM sampling requests. +icon: robot +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +MCP servers can request LLM completions from clients. The client handles these requests through a sampling handler callback. + +## Sampling Handler + +Provide a `sampling_handler` function when creating the client: + +```python +from fastmcp import Client +from fastmcp.client.sampling import ( + SamplingMessage, + SamplingParams, + RequestContext, +) + +async def sampling_handler( + messages: list[SamplingMessage], + params: SamplingParams, + context: RequestContext +) -> str: + # Your LLM integration logic here + # Extract text from messages and generate a response + return "Generated response based on the messages" + +client = Client( + "my_mcp_server.py", + sampling_handler=sampling_handler, +) +``` + +### Handler Parameters + +The sampling handler receives three parameters: + + + + + + The role of the message. + + + + The content of the message. + + TextContent is most common, and has a `.text` attribute. + + + + + + + + The messages to sample from + + + + The server's preferences for which model to select. The client MAY ignore + these preferences. + + + The hints to use for model selection. + + + + The cost priority for model selection. + + + + The speed priority for model selection. + + + + The intelligence priority for model selection. + + + + + + An optional system prompt the server wants to use for sampling. + + + + A request to include context from one or more MCP servers (including the caller), to + be attached to the prompt. + + + + The sampling temperature. + + + + The maximum number of tokens to sample. + + + + The stop sequences to use for sampling. + + + + Optional metadata to pass through to the LLM provider. + + + + Optional list of tools the LLM can use during sampling. See [Using the OpenAI Handler](#using-the-openai-handler). + + + + Optional control over tool usage behavior (`auto`, `required`, or `none`). + + + + + + + + Unique identifier for the MCP 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 +) +``` + + +If the client doesn't provide a sampling handler, servers can optionally configure a fallback handler. See [Server Sampling](/servers/sampling#sampling-fallback-handler) for details. + + +## Sampling Capabilities + +When you provide a `sampling_handler`, FastMCP automatically advertises full sampling capabilities to the server, including tool support. To disable tool support (for simpler handlers that don't support tools), pass `sampling_capabilities` explicitly: + +```python +from mcp.types import SamplingCapability + +client = Client( + "my_mcp_server.py", + sampling_handler=basic_handler, + sampling_capabilities=SamplingCapability(), # No tool support +) +``` + +## Built-in Handlers + +FastMCP provides built-in sampling handlers for OpenAI and Anthropic APIs. These handlers support the full sampling API including tool use, handling message conversion and response formatting automatically. + +### OpenAI Handler + + + +The OpenAI handler works with OpenAI's API and any OpenAI-compatible provider: + +```python +from fastmcp import Client +from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler + +client = Client( + "my_mcp_server.py", + sampling_handler=OpenAISamplingHandler(default_model="gpt-4o"), +) +``` + +For OpenAI-compatible APIs (like local models), pass a custom client: + +```python +from openai import AsyncOpenAI + +client = Client( + "my_mcp_server.py", + sampling_handler=OpenAISamplingHandler( + default_model="llama-3.1-70b", + client=AsyncOpenAI(base_url="http://localhost:8000/v1"), + ), +) +``` + + +Install the OpenAI handler with `pip install fastmcp[openai]`. + + +### Anthropic Handler + + + +The Anthropic handler uses Claude models via the Anthropic API: + +```python +from fastmcp import Client +from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler + +client = Client( + "my_mcp_server.py", + sampling_handler=AnthropicSamplingHandler(default_model="claude-sonnet-4-5"), +) +``` + +You can pass a custom client for advanced configuration: + +```python +from anthropic import AsyncAnthropic + +client = Client( + "my_mcp_server.py", + sampling_handler=AnthropicSamplingHandler( + default_model="claude-sonnet-4-5", + client=AsyncAnthropic(), # Uses ANTHROPIC_API_KEY env var + ), +) +``` + + +Install the Anthropic handler with `pip install fastmcp[anthropic]`. + + +### Tool Execution + +Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results. + + +To implement a custom sampling handler, see the [handler source code](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/client/sampling/handlers) as a reference. + \ No newline at end of file diff --git a/docs/v2/clients/tasks.mdx b/docs/v2/clients/tasks.mdx new file mode 100644 index 000000000..afac69e29 --- /dev/null +++ b/docs/v2/clients/tasks.mdx @@ -0,0 +1,138 @@ +--- +title: Background Tasks +sidebarTitle: Background Tasks +description: Execute operations asynchronously and track their progress +icon: clock +tag: "NEW" +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +The [MCP task protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) lets you request operations to run asynchronously. This returns a Task object immediately, letting you track progress, cancel operations, or await results. + +See [Server Background Tasks](/servers/tasks) for how to enable this on the server side. + +## Requesting Background Execution + +Pass `task=True` to run an operation as a background task. The call returns immediately with a Task object while the work executes on the server. + +```python +from fastmcp import Client + +async with Client(server) as client: + # Start a background task + task = await client.call_tool("slow_computation", {"duration": 10}, task=True) + + print(f"Task started: {task.task_id}") + + # Do other work while it runs... + + # Get the result when ready + result = await task.result() +``` + +This works with tools, resources, and prompts: + +```python +tool_task = await client.call_tool("my_tool", args, task=True) +resource_task = await client.read_resource("file://large.txt", task=True) +prompt_task = await client.get_prompt("my_prompt", args, task=True) +``` + +## Working with Task Objects + +All task types share a common interface for retrieving results, checking status, and receiving updates. + +To get the result, call `await task.result()` or simply `await task`. This blocks until the task completes and returns the result. You can also check status without blocking using `await task.status()`, which returns the current state (`"working"`, `"completed"`, `"failed"`, or `"cancelled"`) along with any progress message from the server. + +```python +task = await client.call_tool("analyze", {"text": "hello"}, task=True) + +# Check current status (non-blocking) +status = await task.status() +print(f"{status.status}: {status.statusMessage}") + +# Wait for result (blocking) +result = await task.result() +``` + +For more control over waiting, use `task.wait()` with an optional timeout or target state: + +```python +# Wait up to 30 seconds for completion +status = await task.wait(timeout=30.0) + +# Wait for a specific state +status = await task.wait(state="completed", timeout=30.0) +``` + +To cancel a running task, call `await task.cancel()`. + +### Real-Time Status Updates + +Register callbacks to receive status updates as the server reports progress. Both sync and async callbacks are supported. + +```python +def on_status_change(status): + print(f"Task {status.taskId}: {status.status} - {status.statusMessage}") + +task.on_status_change(on_status_change) + +# Async callbacks work too +async def on_status_async(status): + await log_status(status) + +task.on_status_change(on_status_async) +``` + +## Graceful Degradation + +You can always pass `task=True` regardless of whether the server supports background tasks. Per the MCP specification, servers without task support execute the operation immediately and return the result inline. The Task API provides a consistent interface either way. + +```python +task = await client.call_tool("my_tool", args, task=True) + +if task.returned_immediately: + print("Server executed immediately (no background support)") +else: + print("Running in background") + +# Either way, this works +result = await task.result() +``` + +This means you can write task-aware client code without worrying about server capabilities. + +## Complete Example + +```python +import asyncio +from fastmcp import Client + +async def main(): + async with Client(server) as client: + # Start background task + task = await client.call_tool( + "slow_computation", + {"duration": 10}, + task=True, + ) + + # Subscribe to updates + def on_update(status): + print(f"Progress: {status.statusMessage}") + + task.on_status_change(on_update) + + # Do other work while task runs + print("Doing other work...") + await asyncio.sleep(2) + + # Wait for completion and get result + result = await task.result() + print(f"Result: {result.content}") + +asyncio.run(main()) +``` diff --git a/docs/v2/clients/tools.mdx b/docs/v2/clients/tools.mdx new file mode 100644 index 000000000..372905bc5 --- /dev/null +++ b/docs/v2/clients/tools.mdx @@ -0,0 +1,295 @@ +--- +title: Tool Operations +sidebarTitle: Tools +description: Discover and execute server-side tools with the FastMCP client. +icon: wrench +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +Tools are executable functions exposed by MCP servers. The FastMCP client provides methods to discover available tools and execute them with arguments. + +## Discovering Tools + +Use `list_tools()` to retrieve all tools available on the server: + +```python +async with client: + tools = await client.list_tools() + # tools -> list[mcp.types.Tool] + + for tool in tools: + print(f"Tool: {tool.name}") + print(f"Description: {tool.description}") + if tool.inputSchema: + print(f"Parameters: {tool.inputSchema}") + # Access tags and other metadata + if hasattr(tool, 'meta') and tool.meta: + fastmcp_meta = tool.meta.get('_fastmcp', {}) + print(f"Tags: {fastmcp_meta.get('tags', [])}") +``` + +### Filtering by Tags + + + +You can use the `meta` field to filter tools based on their tags: + +```python +async with client: + tools = await client.list_tools() + + # Filter tools by tag + analysis_tools = [ + tool for tool in tools + if hasattr(tool, 'meta') and tool.meta and + tool.meta.get('_fastmcp', {}) and + 'analysis' in tool.meta.get('_fastmcp', {}).get('tags', []) + ] + + print(f"Found {len(analysis_tools)} analysis tools") +``` + + +The `meta` field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a `_fastmcp` namespace (e.g., `meta._fastmcp.tags`) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server's `include_fastmcp_meta` setting - when disabled, the `_fastmcp` namespace won't be included. Other MCP server implementations may not provide this metadata structure. + + +## Executing Tools + +### Basic Execution + +Execute a tool using `call_tool()` with the tool name and arguments: + +```python +async with client: + # Simple tool call + result = await client.call_tool("add", {"a": 5, "b": 3}) + # result -> CallToolResult with structured and unstructured data + + # Access structured data (automatically deserialized) + print(result.data) # 8 (int) or {"result": 8} for primitive types + + # Access traditional content blocks + print(result.content[0].text) # "8" (TextContent) +``` + +### Advanced Execution Options + +The `call_tool()` method supports additional parameters for timeout control and progress monitoring: + +```python +async with client: + # With timeout (aborts if execution takes longer than 2 seconds) + result = await client.call_tool( + "long_running_task", + {"param": "value"}, + timeout=2.0 + ) + + # With progress handler (to track execution progress) + result = await client.call_tool( + "long_running_task", + {"param": "value"}, + progress_handler=my_progress_handler + ) +``` + +**Parameters:** +- `name`: The tool name (string) +- `arguments`: Dictionary of arguments to pass to the tool (optional) +- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout) +- `progress_handler`: Progress callback function (optional, overrides client-level handler) +- `meta`: Dictionary of metadata to send with the request (optional, see below) + +## Sending Metadata + + + +The `meta` parameter sends ancillary information alongside tool calls. This can be used for various purposes like observability, debugging, client identification, or any context the server may need beyond the tool's primary arguments. + +```python +async with client: + result = await client.call_tool( + name="send_email", + arguments={ + "to": "user@example.com", + "subject": "Hello", + "body": "Welcome!" + }, + meta={ + "trace_id": "abc-123", + "request_source": "mobile_app" + } + ) +``` + +The structure and usage of `meta` is determined by your application. See [Client Metadata](/servers/context#client-metadata) in the server documentation to learn how to access this data in your tool implementations. + +## Handling Results + + + +Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes. + +### CallToolResult Properties + + + + **FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas. + + + + Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.) available from all MCP servers. + + + + Standard MCP structured JSON data as sent by the server, available from all MCP servers that support structured outputs. + + + + Boolean indicating if the tool execution failed. + + + +### Structured Data Access + +FastMCP's `.data` property provides fully hydrated Python objects, not just JSON dictionaries. This includes complex type reconstruction: + +```python +from datetime import datetime +from uuid import UUID + +async with client: + result = await client.call_tool("get_weather", {"city": "London"}) + + # FastMCP reconstructs complete Python objects from the server's output schema + weather = result.data # Server-defined WeatherReport object + print(f"Temperature: {weather.temperature}°C at {weather.timestamp}") + print(f"Station: {weather.station_id}") + print(f"Humidity: {weather.humidity}%") + + # The timestamp is a real datetime object, not a string! + assert isinstance(weather.timestamp, datetime) + assert isinstance(weather.station_id, UUID) + + # Compare with raw structured JSON (standard MCP) + print(f"Raw JSON: {result.structured_content}") + # {"temperature": 20, "timestamp": "2024-01-15T14:30:00Z", "station_id": "123e4567-..."} + + # Traditional content blocks (standard MCP) + print(f"Text content: {result.content[0].text}") +``` + +### Fallback Behavior + +For tools without output schemas or when deserialization fails, `.data` will be `None`: + +```python +async with client: + result = await client.call_tool("legacy_tool", {"param": "value"}) + + if result.data is not None: + # Structured output available and successfully deserialized + print(f"Structured: {result.data}") + else: + # No structured output or deserialization failed - use content blocks + for content in result.content: + if hasattr(content, 'text'): + print(f"Text result: {content.text}") + elif hasattr(content, 'data'): + print(f"Binary data: {len(content.data)} bytes") +``` + +### Primitive Type Unwrapping + + +FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object. + + +```python +async with client: + result = await client.call_tool("calculate_sum", {"a": 5, "b": 3}) + + # FastMCP client automatically unwraps for convenience + print(result.data) # 8 (int) - the original value + + # Raw structured content shows the server-side wrapping + print(result.structured_content) # {"result": 8} + + # Other MCP clients would need to manually access ["result"] + # value = result.structured_content["result"] # Not needed with FastMCP! +``` + +## Error Handling + +### Exception-Based Error Handling + +By default, `call_tool()` raises a `ToolError` if the tool execution fails: + +```python +from fastmcp.exceptions import ToolError + +async with client: + try: + result = await client.call_tool("potentially_failing_tool", {"param": "value"}) + print("Tool succeeded:", result.data) + except ToolError as e: + print(f"Tool failed: {e}") +``` + +### Manual Error Checking + +You can disable automatic error raising and manually check the result: + +```python +async with client: + result = await client.call_tool( + "potentially_failing_tool", + {"param": "value"}, + raise_on_error=False + ) + + if result.is_error: + print(f"Tool failed: {result.content[0].text}") + else: + print(f"Tool succeeded: {result.data}") +``` + +### Raw MCP Protocol Access + +For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object: + +```python +async with client: + result = await client.call_tool_mcp("potentially_failing_tool", {"param": "value"}) + # result -> mcp.types.CallToolResult + + if result.isError: + print(f"Tool failed: {result.content}") + else: + print(f"Tool succeeded: {result.content}") + # Note: No automatic deserialization with call_tool_mcp() +``` + +## Argument Handling + +Arguments are passed as a dictionary to the tool: + +```python +async with client: + # Simple arguments + result = await client.call_tool("greet", {"name": "World"}) + + # Complex arguments + result = await client.call_tool("process_data", { + "config": {"format": "json", "validate": True}, + "items": [1, 2, 3, 4, 5], + "metadata": {"source": "api", "version": "1.0"} + }) +``` + + +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). + \ No newline at end of file diff --git a/docs/v2/clients/transports.mdx b/docs/v2/clients/transports.mdx new file mode 100644 index 000000000..b7129acc2 --- /dev/null +++ b/docs/v2/clients/transports.mdx @@ -0,0 +1,383 @@ +--- +title: Client Transports +sidebarTitle: Transports +description: Configure how FastMCP Clients connect to and communicate with servers. +icon: link +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +The FastMCP `Client` communicates with MCP servers through transport objects that handle the underlying connection mechanics. While the client can automatically select a transport based on what you pass to it, instantiating transports explicitly gives you full control over configuration—environment variables, authentication, session management, and more. + +Think of transports as configurable adapters between your client code and MCP servers. Each transport type handles a different communication pattern: subprocesses with pipes, HTTP connections, or direct in-memory calls. + +## Choosing the Right Transport + +- **Use [STDIO Transport](#stdio-transport)** when you need to run local MCP servers with full control over their environment and lifecycle +- **Use [Remote Transports](#remote-transports)** when connecting to production services or shared MCP servers running independently +- **Use [In-Memory Transport](#in-memory-transport)** for testing FastMCP servers without subprocess or network overhead +- **Use [MCP JSON Configuration](#mcp-json-configuration-transport)** when you need to connect to multiple servers defined in configuration files + +## STDIO Transport + +STDIO (Standard Input/Output) transport communicates with MCP servers through subprocess pipes. This is the standard mechanism used by desktop clients like Claude Desktop and is the primary way to run local MCP servers. + +### The Client Runs the Server + + +**Critical Concept**: When using STDIO transport, your client actually launches and manages the server process. This is fundamentally different from network transports where you connect to an already-running server. Understanding this relationship is key to using STDIO effectively. + + +With STDIO transport, your client: +- Starts the server as a subprocess when you connect +- Manages the server's lifecycle (start, stop, restart) +- Controls the server's environment and configuration +- Communicates through stdin/stdout pipes + +This architecture enables powerful local integrations but requires understanding environment isolation and process management. + +### Environment Isolation + +STDIO servers run in isolated environments by default. This is a security feature enforced by the MCP protocol to prevent accidental exposure of sensitive data. + +When your client launches an MCP server: +- The server does NOT inherit your shell's environment variables +- API keys, paths, and other configuration must be explicitly passed +- The working directory and system paths may differ from your shell + +To pass environment variables to your server, use the `env` parameter: + +```python +from fastmcp import Client + +# If your server needs environment variables (like API keys), +# you must explicitly pass them: +client = Client( + "my_server.py", + env={"API_KEY": "secret", "DEBUG": "true"} +) + +# This won't work - the server runs in isolation: +# export API_KEY="secret" # in your shell +# client = Client("my_server.py") # server can't see API_KEY +``` + +### Basic Usage + +To use STDIO transport, you create a transport instance with the command and arguments needed to run your server: + +```python +from fastmcp.client.transports import StdioTransport + +transport = StdioTransport( + command="python", + args=["my_server.py"] +) +client = Client(transport) +``` + +You can configure additional settings like environment variables, working directory, or command arguments: + +```python +transport = StdioTransport( + command="python", + args=["my_server.py", "--verbose"], + env={"LOG_LEVEL": "DEBUG"}, + cwd="/path/to/server" +) +client = Client(transport) +``` + +For convenience, the client can also infer STDIO transport from file paths, but this doesn't allow configuration: + +```python +from fastmcp import Client + +client = Client("my_server.py") # Limited - no configuration options +``` + +### Environment Variables + +Since STDIO servers don't inherit your environment, you need strategies for passing configuration. Here are two common approaches: + +**Selective forwarding** passes only the variables your server actually needs: + +```python +import os +from fastmcp.client.transports import StdioTransport + +required_vars = ["API_KEY", "DATABASE_URL", "REDIS_HOST"] +env = { + var: os.environ[var] + for var in required_vars + if var in os.environ +} + +transport = StdioTransport( + command="python", + args=["server.py"], + env=env +) +client = Client(transport) +``` + +**Loading from .env files** keeps configuration separate from code: + +```python +from dotenv import dotenv_values +from fastmcp.client.transports import StdioTransport + +env = dotenv_values(".env") +transport = StdioTransport( + command="python", + args=["server.py"], + env=env +) +client = Client(transport) +``` + +### Session Persistence + +STDIO transports maintain sessions across multiple client contexts by default (`keep_alive=True`). This improves performance by reusing the same subprocess for multiple connections, but can be controlled when you need isolation. + +By default, the subprocess persists between connections: + +```python +from fastmcp.client.transports import StdioTransport + +transport = StdioTransport( + command="python", + args=["server.py"] +) +client = Client(transport) + +async def efficient_multiple_operations(): + async with client: + await client.ping() + + async with client: # Reuses the same subprocess + await client.call_tool("process_data", {"file": "data.csv"}) +``` + +For complete isolation between connections, disable session persistence: + +```python +transport = StdioTransport( + command="python", + args=["server.py"], + keep_alive=False +) +client = Client(transport) +``` + +Use `keep_alive=False` when you need complete isolation (e.g., in test suites) or when server state could cause issues between connections. + +### Specialized STDIO Transports + +FastMCP provides convenience transports that are thin wrappers around `StdioTransport` with pre-configured commands: + +- **`PythonStdioTransport`** - Uses `python` command for `.py` files +- **`NodeStdioTransport`** - Uses `node` command for `.js` files +- **`UvStdioTransport`** - Uses `uv` for Python packages (uses `env_vars` parameter) +- **`UvxStdioTransport`** - Uses `uvx` for Python packages (uses `env_vars` parameter) +- **`NpxStdioTransport`** - Uses `npx` for Node packages (uses `env_vars` parameter) + +For most use cases, instantiate `StdioTransport` directly with your desired command. These specialized transports are primarily useful for client inference shortcuts. + +## Remote Transports + +Remote transports connect to MCP servers running as web services. This is a fundamentally different model from STDIO transports—instead of your client launching and managing a server process, you connect to an already-running service that manages its own environment and lifecycle. + +### Streamable HTTP Transport + + + +Streamable HTTP is the recommended transport for production deployments, providing efficient bidirectional streaming over HTTP connections. + +- **Class:** `StreamableHttpTransport` +- **Server compatibility:** FastMCP servers running with `mcp run --transport http` + +The transport requires a URL and optionally supports custom headers for authentication and configuration: + +```python +from fastmcp.client.transports import StreamableHttpTransport + +# Basic connection +transport = StreamableHttpTransport(url="https://api.example.com/mcp") +client = Client(transport) + +# With custom headers for authentication +transport = StreamableHttpTransport( + url="https://api.example.com/mcp", + headers={ + "Authorization": "Bearer your-token-here", + "X-Custom-Header": "value" + } +) +client = Client(transport) +``` + +For convenience, FastMCP also provides authentication helpers: + +```python +from fastmcp.client.auth import BearerAuth + +client = Client( + "https://api.example.com/mcp", + auth=BearerAuth("your-token-here") +) +``` + +### SSE Transport (Legacy) + +Server-Sent Events transport is maintained for backward compatibility but is superseded by Streamable HTTP for new deployments. + +- **Class:** `SSETransport` +- **Server compatibility:** FastMCP servers running with `mcp run --transport sse` + +SSE transport supports the same configuration options as Streamable HTTP: + +```python +from fastmcp.client.transports import SSETransport + +transport = SSETransport( + url="https://api.example.com/sse", + headers={"Authorization": "Bearer token"} +) +client = Client(transport) +``` + +Use Streamable HTTP for new deployments unless you have specific infrastructure requirements for SSE. + +## In-Memory Transport + +In-memory transport connects directly to a FastMCP server instance within the same Python process. This eliminates both subprocess management and network overhead, making it ideal for testing and development. + +- **Class:** `FastMCPTransport` + + +Unlike STDIO transports, in-memory servers have full access to your Python process's environment. They share the same memory space and environment variables as your client code—no isolation or explicit environment passing required. + + +```python +from fastmcp import FastMCP, Client +import os + +mcp = FastMCP("TestServer") + +@mcp.tool +def greet(name: str) -> str: + prefix = os.environ.get("GREETING_PREFIX", "Hello") + return f"{prefix}, {name}!" + +client = Client(mcp) + +async with client: + result = await client.call_tool("greet", {"name": "World"}) +``` + +## MCP JSON Configuration Transport + + + +This transport supports the emerging MCP JSON configuration standard for defining multiple servers: + +- **Class:** `MCPConfigTransport` + +```python +config = { + "mcpServers": { + "weather": { + "url": "https://weather.example.com/mcp", + "transport": "http" + }, + "assistant": { + "command": "python", + "args": ["./assistant.py"], + "env": {"LOG_LEVEL": "INFO"} + } + } +} + +client = Client(config) + +async with client: + # Tools are namespaced by server + weather = await client.call_tool("weather_get_forecast", {"city": "NYC"}) + answer = await client.call_tool("assistant_ask", {"question": "What?"}) +``` + +### Tool Transformation with FastMCP and MCPConfig + +FastMCP supports basic tool transformations to be defined alongside the MCP Servers in the MCPConfig file. + +```python +config = { + "mcpServers": { + "weather": { + "url": "https://weather.example.com/mcp", + "transport": "http", + "tools": { } # <--- This is the tool transformation section + } + } +} +``` + +With these transformations, you can transform (change) the name, title, description, tags, enablement, and arguments of a tool. + +For each argument the tool takes, you can transform (change) the name, description, default, visibility, whether it's required, and you can provide example values. + +In the following example, we're transforming the `weather_get_forecast` tool to only retrieve the weather for `Miami` and hiding the `city` argument from the client. + +```python +tool_transformations = { + "weather_get_forecast": { + "name": "miami_weather", + "description": "Get the weather for Miami", + "arguments": { + "city": { + "name": "city", + "default": "Miami", + "hide": True, + } + } + } +} + +config = { + "mcpServers": { + "weather": { + "url": "https://weather.example.com/mcp", + "transport": "http", + "tools": tool_transformations + } + } +} +``` + +#### Allowlisting and Blocklisting Tools + +Tools can be allowlisted or blocklisted from the client by applying `tags` to the tools on the server. In the following example, we're allowlisting only tools marked with the `forecast` tag, all other tools will be unavailable to the client. + +```python +tool_transformations = { + "weather_get_forecast": { + "enabled": True, + "tags": ["forecast"] + } +} + + +config = { + "mcpServers": { + "weather": { + "url": "https://weather.example.com/mcp", + "transport": "http", + "tools": tool_transformations, + "include_tags": ["forecast"] + } + } +} +``` \ No newline at end of file diff --git a/docs/v2/community/showcase.mdx b/docs/v2/community/showcase.mdx new file mode 100644 index 000000000..fe8ec810d --- /dev/null +++ b/docs/v2/community/showcase.mdx @@ -0,0 +1,65 @@ +--- +title: 'Community Showcase' +description: 'High-quality projects and examples from the FastMCP community' +icon: 'users' +--- + +import { YouTubeEmbed } from '/snippets/youtube-embed.mdx' + +## Join the Community + + + Connect with other FastMCP developers, share your projects, and discuss ideas. + + +## Featured Projects + +Discover exemplary MCP servers and implementations created by our community. These projects demonstrate best practices and innovative uses of FastMCP. + +### Learning Resources + + + A comprehensive educational example demonstrating FastMCP best practices with professional dual-transport server implementation, interactive test client, and detailed documentation. + + +#### Video Tutorials + +**Build Remote MCP Servers w/ Python & FastMCP** - Claude Integrations Tutorial by Greg + Code + + + +**FastMCP — the best way to build an MCP server with Python** - Tutorial by ZazenCodes + + + +**Speedrun a MCP server for Claude Desktop (fastmcp)** - Tutorial by Nate from Prefect + + + +### Community Examples + +Have you built something interesting with FastMCP? We'd love to feature high-quality examples here! Start a [discussion on GitHub](https://github.com/jlowin/fastmcp/discussions) to share your project. + +## Contributing + +To get your project featured: + +1. Ensure your project demonstrates best practices +2. Include comprehensive documentation +3. Add clear usage examples +4. Open a discussion in our [GitHub Discussions](https://github.com/jlowin/fastmcp/discussions) + +We review submissions regularly and feature projects that provide value to the FastMCP community. + +## Further Reading + +- [Contrib Modules](/patterns/contrib) - Community-contributed modules that are distributed with FastMCP itself \ No newline at end of file diff --git a/docs/v2/deployment/fastmcp-cloud.mdx b/docs/v2/deployment/fastmcp-cloud.mdx new file mode 100644 index 000000000..f806e1386 --- /dev/null +++ b/docs/v2/deployment/fastmcp-cloud.mdx @@ -0,0 +1,89 @@ +--- +title: FastMCP Cloud +sidebarTitle: FastMCP Cloud +description: The fastest way to deploy your MCP server +icon: cloud +tag: NEW +--- + +[FastMCP Cloud](https://fastmcp.cloud) is a managed platform for hosting MCP servers, built by the FastMCP team. While the FastMCP framework will always be fully open-source, we created FastMCP Cloud to solve the deployment challenges we've seen developers face. Our goal is to provide the absolute fastest way to make your MCP server available to LLM clients like Claude and Cursor. + +FastMCP Cloud is a young product and we welcome your feedback. Please join our [Discord](https://discord.com/invite/aGsSC3yDF4) to share your thoughts and ideas, and you can expect to see new features and improvements every week. + + + +FastMCP Cloud supports both **FastMCP 2.0** servers and also **FastMCP 1.0** servers that were created with the official MCP Python SDK. + + + +FastMCP Cloud is completely free while in beta! + + +## Prerequisites + +To use FastMCP Cloud, you'll need a [GitHub](https://github.com) account. In addition, you'll need a GitHub repo that contains a FastMCP server instance. If you don't want to create one yet, you can proceed to [step 1](#step-1-create-a-project) and use the FastMCP Cloud quickstart repo. + +Your repo can be public or private, but must include at least a Python file that contains a FastMCP server instance. + +To ensure your file is compatible with FastMCP Cloud, you can run `fastmcp inspect ` to see what FastMCP Cloud will see when it runs your server. + + +If you have a `requirements.txt` or `pyproject.toml` in the repo, FastMCP Cloud will automatically detect your server's dependencies and install them for you. Note that your file *can* have an `if __name__ == "__main__"` block, but it will be ignored by FastMCP Cloud. + +For example, a minimal server file might look like: + +```python +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" +``` + +## Getting Started + +There are just three steps to deploying a server to FastMCP Cloud: + +### Step 1: Create a Project + +Visit [fastmcp.cloud](https://fastmcp.cloud) and sign in with your GitHub account. Then, create a project. Each project corresponds to a GitHub repo, and you can create one from either your own repo or using the FastMCP Cloud quickstart repo. + +FastMCP Cloud Quickstart Screen + +Next, you'll be prompted to configure your project. + +FastMCP Cloud Configuration Screen + +The configuration screen lets you specify: +- **Name**: The name of your project. This will be used to generate a unique URL for your server. +- **Entrypoint**: The Python file containing your FastMCP server (e.g., `echo.py`). This field has the same syntax as the `fastmcp run` command, for example `echo.py:my_server` to specify a specific object in the file. +- **Authentication**: If disabled, your server is open to the public. If enabled, only other members of your FastMCP Cloud organization will be able to connect. + +Note that FastMCP Cloud will automatically detect yours server's Python dependencies from either a `requirements.txt` or `pyproject.toml` file. + +### Step 2: Deploy Your Server + +Once you configure your project, FastMCP Cloud will: +1. Clone the repository +2. Build your FastMCP server +3. Deploy it to a unique URL +4. Make it immediately available for connections + +FastMCP Cloud Deployment Screen + +FastMCP Cloud will monitor your repo and redeploy your server whenever you push a change to the `main` branch. In addition, FastMCP Cloud will build and deploy servers for every PR your open, hosting them on unique URLs, so you can test changes before updating your production server. + +### Step 3: Connect to Your Server + +Once your server is deployed, it will be accessible at a URL like: + +``` +https://your-project-name.fastmcp.app/mcp +``` + +You should be able to connect to it as soon as you see the deployment succeed! FastMCP Cloud provides instant connection options for popular LLM clients: + +FastMCP Cloud Connection Screen + diff --git a/docs/v2/deployment/http.mdx b/docs/v2/deployment/http.mdx new file mode 100644 index 000000000..ca636cb17 --- /dev/null +++ b/docs/v2/deployment/http.mdx @@ -0,0 +1,736 @@ +--- +title: HTTP Deployment +sidebarTitle: HTTP Deployment +description: Deploy your FastMCP server over HTTP for remote access +icon: server +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + +STDIO transport is perfect for local development and desktop applications. But to unlock the full potential of MCP—centralized services, multi-client access, and network availability—you need remote HTTP deployment. + + +This guide walks you through deploying your FastMCP server as a remote MCP service that's accessible via a URL. Once deployed, your MCP server will be available over the network, allowing multiple clients to connect simultaneously and enabling integration with cloud-based LLM applications. This guide focuses specifically on remote MCP deployment, not local STDIO servers. + +## Choosing Your Approach + +FastMCP provides two ways to deploy your server as an HTTP service. Understanding the trade-offs helps you choose the right approach for your needs. + +The **direct HTTP server** approach is simpler and perfect for getting started quickly. You modify your server's `run()` method to use HTTP transport, and FastMCP handles all the web server configuration. This approach works well for standalone deployments where you want your MCP server to be the only service running on a port. + +The **ASGI application** approach gives you more control and flexibility. Instead of running the server directly, you create an ASGI application that can be served by Uvicorn. This approach is better when you need advanced server features like multiple workers, custom middleware, or when you're integrating with existing web applications. + +### Direct HTTP Server + +The simplest way to get your MCP server online is to use the built-in `run()` method with HTTP transport. This approach handles all the server configuration for you and is ideal when you want a standalone MCP server without additional complexity. + +```python server.py +from fastmcp import FastMCP + +mcp = FastMCP("My Server") + +@mcp.tool +def process_data(input: str) -> str: + """Process data on the server""" + return f"Processed: {input}" + +if __name__ == "__main__": + mcp.run(transport="http", host="0.0.0.0", port=8000) +``` + +Run your server with a simple Python command: +```bash +python server.py +``` + +Your server is now accessible at `http://localhost:8000/mcp` (or use your server's actual IP address for remote access). + +This approach is ideal when you want to get online quickly with minimal configuration. It's perfect for internal tools, development environments, or simple deployments where you don't need advanced server features. The built-in server handles all the HTTP details, letting you focus on your MCP implementation. + +### ASGI Application + +For production deployments, you'll often want more control over how your server runs. FastMCP can create a standard ASGI application that works with any ASGI server like Uvicorn, Gunicorn, or Hypercorn. This approach is particularly useful when you need to configure advanced server options, run multiple workers, or integrate with existing infrastructure. + +```python app.py +from fastmcp import FastMCP + +mcp = FastMCP("My Server") + +@mcp.tool +def process_data(input: str) -> str: + """Process data on the server""" + return f"Processed: {input}" + +# Create ASGI application +app = mcp.http_app() +``` + +Run with any ASGI server - here's an example with Uvicorn: +```bash +uvicorn app:app --host 0.0.0.0 --port 8000 +``` + +Your server is accessible at the same URL: `http://localhost:8000/mcp` (or use your server's actual IP address for remote access). + +The ASGI approach shines in production environments where you need reliability and performance. You can run multiple worker processes to handle concurrent requests, add custom middleware for logging or monitoring, integrate with existing deployment pipelines, or mount your MCP server as part of a larger application. + +## Configuring Your Server + +### Custom Path + +By default, your MCP server is accessible at `/mcp/` on your domain. You can customize this path to fit your URL structure or avoid conflicts with existing endpoints. This is particularly useful when integrating MCP into an existing application or following specific API conventions. + +```python +# Option 1: With mcp.run() +mcp.run(transport="http", host="0.0.0.0", port=8000, path="/api/mcp/") + +# Option 2: With ASGI app +app = mcp.http_app(path="/api/mcp/") +``` + +Now your server is accessible at `http://localhost:8000/api/mcp/`. + +### Authentication + + +Authentication is **highly recommended** for remote MCP servers. Some LLM clients require authentication for remote servers and will refuse to connect without it. + + +FastMCP supports multiple authentication methods to secure your remote server. See the [Authentication Overview](/servers/auth/authentication) for complete configuration options including Bearer tokens, JWT, and OAuth. + +If you're mounting an authenticated server under a path prefix, see [Mounting Authenticated Servers](#mounting-authenticated-servers) below for important routing considerations. + +### Health Checks + +Health check endpoints are essential for monitoring your deployed server and ensuring it's responding correctly. FastMCP allows you to add custom routes alongside your MCP endpoints, making it easy to implement health checks that work with both deployment approaches. + +```python +from starlette.responses import JSONResponse + +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(request): + return JSONResponse({"status": "healthy", "service": "mcp-server"}) +``` + +This health endpoint will be available at `http://localhost:8000/health` and can be used by load balancers, monitoring systems, or deployment platforms to verify your server is running. + +### Custom Middleware + + + + +Add custom Starlette middleware to your FastMCP ASGI apps: + +```python +from fastmcp import FastMCP +from starlette.middleware import Middleware +from starlette.middleware.cors import CORSMiddleware + +# Create your FastMCP server +mcp = FastMCP("MyServer") + +# Define middleware +middleware = [ + Middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], + ) +] + +# Create ASGI app with middleware +http_app = mcp.http_app(middleware=middleware) +``` + +### CORS for Browser-Based Clients + + +Most MCP clients, including those that you access through a browser like ChatGPT or Claude, don't need CORS configuration. Only enable CORS if you're working with an MCP client that connects directly from a browser, such as debugging tools or inspectors. + + +CORS (Cross-Origin Resource Sharing) is needed when JavaScript running in a web browser connects directly to your MCP server. This is different from using an LLM through a browser—in that case, the browser connects to the LLM service, and the LLM service connects to your MCP server (no CORS needed). + +Browser-based MCP clients that need CORS include: + +- **MCP Inspector** - Browser-based debugging tool for testing MCP servers +- **Custom browser-based MCP clients** - If you're building a web app that directly connects to MCP servers + +For these scenarios, add CORS middleware with the specific headers required for MCP protocol: + +```python +from fastmcp import FastMCP +from starlette.middleware import Middleware +from starlette.middleware.cors import CORSMiddleware + +mcp = FastMCP("MyServer") + +# Configure CORS for browser-based clients +middleware = [ + Middleware( + CORSMiddleware, + allow_origins=["*"], # Allow all origins; use specific origins for security + allow_methods=["GET", "POST", "DELETE", "OPTIONS"], + allow_headers=[ + "mcp-protocol-version", + "mcp-session-id", + "Authorization", + "Content-Type", + ], + expose_headers=["mcp-session-id"], + ) +] + +app = mcp.http_app(middleware=middleware) +``` + +**Key configuration details:** + +- **`allow_origins`**: Specify exact origins (e.g., `["http://localhost:3000"]`) rather than `["*"]` for production deployments +- **`allow_headers`**: Must include `mcp-protocol-version`, `mcp-session-id`, and `Authorization` (for authenticated servers) +- **`expose_headers`**: Must include `mcp-session-id` so JavaScript can read the session ID from responses and send it in subsequent requests + +Without `expose_headers=["mcp-session-id"]`, browsers will receive the session ID but JavaScript won't be able to access it, causing session management to fail. + + +**Production Security**: Never use `allow_origins=["*"]` in production. Specify the exact origins of your browser-based clients. Using wildcards exposes your server to unauthorized access from any website. + + +### SSE Polling for Long-Running Operations + + + + +This feature only applies to the **StreamableHTTP transport** (the default for `http_app()`). It does not apply to the legacy SSE transport (`transport="sse"`). + + +When running tools that take a long time to complete, you may encounter issues with load balancers or proxies terminating connections that stay idle too long. [SEP-1699](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699) introduces SSE polling to solve this by allowing the server to gracefully close connections and have clients automatically reconnect. + +To enable SSE polling, configure an `EventStore` when creating your HTTP application: + +```python +from fastmcp import FastMCP, Context +from fastmcp.server.event_store import EventStore + +mcp = FastMCP("My Server") + +@mcp.tool +async def long_running_task(ctx: Context) -> str: + """A task that takes several minutes to complete.""" + for i in range(100): + await ctx.report_progress(i, 100) + + # Periodically close the connection to avoid load balancer timeouts + # Client will automatically reconnect and resume receiving progress + if i % 30 == 0 and i > 0: + await ctx.close_sse_stream() + + await do_expensive_work() + + return "Done!" + +# Configure with EventStore for resumability +event_store = EventStore() +app = mcp.http_app( + event_store=event_store, + retry_interval=2000, # Client reconnects after 2 seconds +) +``` + +**How it works:** + +1. When `event_store` is configured, the server stores all events (progress updates, results) with unique IDs +2. Calling `ctx.close_sse_stream()` gracefully closes the HTTP connection +3. The client automatically reconnects with a `Last-Event-ID` header +4. The server replays any events the client missed during the disconnection + +The `retry_interval` parameter (in milliseconds) controls how long clients wait before reconnecting. Choose a value that balances responsiveness with server load. + + +`close_sse_stream()` is a no-op if called without an `EventStore` configured, so you can safely include it in tools that may run in different deployment configurations. + + +#### Custom Storage Backends + +By default, `EventStore` uses in-memory storage. For production deployments with multiple server instances, you can provide a custom storage backend using the `key_value` package: + +```python +from fastmcp.server.event_store import EventStore +from key_value.aio.stores.redis import RedisStore + +# Use Redis for distributed deployments +redis_store = RedisStore(url="redis://localhost:6379") +event_store = EventStore( + storage=redis_store, + max_events_per_stream=100, # Keep last 100 events per stream + ttl=3600, # Events expire after 1 hour +) + +app = mcp.http_app(event_store=event_store) +``` + +## Integration with Web Frameworks + +If you already have a web application running, you can add MCP capabilities by mounting a FastMCP server as a sub-application. This allows you to expose MCP tools alongside your existing API endpoints, sharing the same domain and infrastructure. The MCP server becomes just another route in your application, making it easy to manage and deploy. + +### Mounting in Starlette + +Mount your FastMCP server in a Starlette application: + +```python +from fastmcp import FastMCP +from starlette.applications import Starlette +from starlette.routing import Mount + +# Create your FastMCP server +mcp = FastMCP("MyServer") + +@mcp.tool +def analyze(data: str) -> dict: + return {"result": f"Analyzed: {data}"} + +# Create the ASGI app +mcp_app = mcp.http_app(path='/mcp') + +# Create a Starlette app and mount the MCP server +app = Starlette( + routes=[ + Mount("/mcp-server", app=mcp_app), + # Add other routes as needed + ], + lifespan=mcp_app.lifespan, +) +``` + +The MCP endpoint will be available at `/mcp-server/mcp/` of the resulting Starlette app. + + +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. + + +#### Nested Mounts + +You can create complex routing structures by nesting mounts: + +```python +from fastmcp import FastMCP +from starlette.applications import Starlette +from starlette.routing import Mount + +# Create your FastMCP server +mcp = FastMCP("MyServer") + +# Create the ASGI app +mcp_app = mcp.http_app(path='/mcp') + +# Create nested application structure +inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)]) +app = Starlette( + routes=[Mount("/outer", app=inner_app)], + lifespan=mcp_app.lifespan, +) +``` + +In this setup, the MCP server is accessible at the `/outer/inner/mcp/` path. + +### FastAPI Integration + +For FastAPI-specific integration patterns including both mounting MCP servers into FastAPI apps and generating MCP servers from FastAPI apps, see the [FastAPI Integration guide](/integrations/fastapi). + +Here's a quick example showing how to add MCP to an existing FastAPI application: + +```python +from fastapi import FastAPI +from fastmcp import FastMCP + +# Your existing API +api = FastAPI() + +@api.get("/api/status") +def status(): + return {"status": "ok"} + +# Create your MCP server +mcp = FastMCP("API Tools") + +@mcp.tool +def query_database(query: str) -> dict: + """Run a database query""" + return {"result": "data"} + +# Mount MCP at /mcp +api.mount("/mcp", mcp.http_app()) + +# Run with: uvicorn app:api --host 0.0.0.0 --port 8000 +``` + +Your existing API remains at `http://localhost:8000/api` while MCP is available at `http://localhost:8000/mcp`. + +## Mounting Authenticated Servers + + + + +This section only applies if you're **mounting an OAuth-protected FastMCP server under a path prefix** (like `/api`) inside another application using `Mount()`. + +If you're deploying your FastMCP server at root level without any `Mount()` prefix, the well-known routes are automatically included in `mcp.http_app()` and you don't need to do anything special. + + +OAuth specifications (RFC 8414 and RFC 9728) require discovery metadata to be accessible at well-known paths under the root level of your domain. When you mount an OAuth-protected FastMCP server under a path prefix like `/api`, this creates a routing challenge: your operational OAuth endpoints move under the prefix, but discovery endpoints must remain at the root. + + +**Common Mistakes to Avoid:** + +1. **Forgetting to mount `.well-known` routes at root** - FastMCP cannot do this automatically when your server is mounted under a path prefix. You must explicitly mount well-known routes at the root level. + +2. **Including mount prefix in both base_url AND mcp_path** - The mount prefix (like `/api`) should only be in `base_url`, not in `mcp_path`. Otherwise you'll get double paths. + + ✅ **Correct:** + ```python + base_url = "http://localhost:8000/api" + mcp_path = "/mcp" + # Result: /api/mcp + ``` + + ❌ **Wrong:** + ```python + base_url = "http://localhost:8000/api" + mcp_path = "/api/mcp" + # Result: /api/api/mcp (double prefix!) + ``` + +Follow the configuration instructions below to set up mounting correctly. + + + +**CORS Middleware Conflicts:** + +If you're integrating FastMCP into an existing application with its own CORS middleware, be aware that layering CORS middleware can cause conflicts (such as 404 errors on `.well-known` routes or OPTIONS requests). + +FastMCP and the MCP SDK already handle CORS for OAuth routes. If you need CORS on your own application routes, consider using the sub-app pattern: mount FastMCP and your routes as separate apps, each with their own middleware, rather than adding application-wide CORS middleware. + + +### Route Types + +OAuth-protected MCP servers expose two categories of routes: + +**Operational routes** handle the OAuth flow and MCP protocol: +- `/authorize` - OAuth authorization endpoint +- `/token` - Token exchange endpoint +- `/auth/callback` - OAuth callback handler +- `/mcp` - MCP protocol endpoint + +**Discovery routes** provide metadata for OAuth clients: +- `/.well-known/oauth-authorization-server` - Authorization server metadata +- `/.well-known/oauth-protected-resource/*` - Protected resource metadata + +When you mount your MCP app under a prefix, operational routes move with it, but discovery routes must stay at root level for RFC compliance. + +### Configuration Parameters + +Three parameters control where routes are located and how they combine: + +**`base_url`** tells clients where to find operational endpoints. This includes any Starlette `Mount()` path prefix (e.g., `/api`): + +```python +base_url="http://localhost:8000/api" # Includes mount prefix +``` + +**`mcp_path`** is the internal FastMCP endpoint path, which gets appended to `base_url`: + +```python +mcp_path="/mcp" # Internal MCP path, NOT the mount prefix +``` + +**`issuer_url`** (optional) controls the authorization server identity for OAuth discovery. Defaults to `base_url`. + +```python +# Usually not needed - just set base_url and it works +issuer_url="http://localhost:8000" # Only if you want root-level discovery +``` + +When `issuer_url` has a path (either explicitly or by defaulting from `base_url`), FastMCP creates path-aware discovery routes per RFC 8414. For example, if `base_url` is `http://localhost:8000/api`, the authorization server metadata will be at `/.well-known/oauth-authorization-server/api`. + +**Key Invariant:** `base_url + mcp_path = actual externally-accessible MCP URL` + +Example: +- `base_url`: `http://localhost:8000/api` (mount prefix `/api`) +- `mcp_path`: `/mcp` (internal path) +- Result: `http://localhost:8000/api/mcp` (final MCP endpoint) + +Note that the mount prefix (`/api` from `Mount("/api", ...)`) goes in `base_url`, while `mcp_path` is just the internal MCP route. Don't include the mount prefix in both places or you'll get `/api/api/mcp`. + +### Mounting Strategy + +When mounting an OAuth-protected server under a path prefix, declare your URLs upfront to make the relationships clear: + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.github import GitHubProvider +from starlette.applications import Starlette +from starlette.routing import Mount + +# Define the routing structure +ROOT_URL = "http://localhost:8000" +MOUNT_PREFIX = "/api" +MCP_PATH = "/mcp" +``` + +Create the auth provider with `base_url`: + +```python +auth = GitHubProvider( + client_id="your-client-id", + client_secret="your-client-secret", + base_url=f"{ROOT_URL}{MOUNT_PREFIX}", # Operational endpoints under prefix + # issuer_url defaults to base_url - path-aware discovery works automatically +) +``` + +Create the MCP app, which generates operational routes at the specified path: + +```python +mcp = FastMCP("Protected Server", auth=auth) +mcp_app = mcp.http_app(path=MCP_PATH) +``` + +Retrieve the discovery routes from the auth provider. The `mcp_path` argument should match the path used when creating the MCP app: + +```python +well_known_routes = auth.get_well_known_routes(mcp_path=MCP_PATH) +``` + +Finally, mount everything in the Starlette app with discovery routes at root and the MCP app under the prefix: + +```python +app = Starlette( + routes=[ + *well_known_routes, # Discovery routes at root level + Mount(MOUNT_PREFIX, app=mcp_app), # Operational routes under prefix + ], + lifespan=mcp_app.lifespan, +) +``` + +This configuration produces the following URL structure: + +- MCP endpoint: `http://localhost:8000/api/mcp` +- OAuth authorization: `http://localhost:8000/api/authorize` +- OAuth callback: `http://localhost:8000/api/auth/callback` +- Authorization server metadata: `http://localhost:8000/.well-known/oauth-authorization-server/api` +- Protected resource metadata: `http://localhost:8000/.well-known/oauth-protected-resource/api/mcp` + +Both discovery endpoints use path-aware URLs per RFC 8414 and RFC 9728, matching the `base_url` path. + +### Complete Example + +Here's a complete working example showing all the pieces together: + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.github import GitHubProvider +from starlette.applications import Starlette +from starlette.routing import Mount +import uvicorn + +# Define routing structure +ROOT_URL = "http://localhost:8000" +MOUNT_PREFIX = "/api" +MCP_PATH = "/mcp" + +# Create OAuth provider +auth = GitHubProvider( + client_id="your-client-id", + client_secret="your-client-secret", + base_url=f"{ROOT_URL}{MOUNT_PREFIX}", + # issuer_url defaults to base_url - path-aware discovery works automatically +) + +# Create MCP server +mcp = FastMCP("Protected Server", auth=auth) + +@mcp.tool +def analyze(data: str) -> dict: + return {"result": f"Analyzed: {data}"} + +# Create MCP app +mcp_app = mcp.http_app(path=MCP_PATH) + +# Get discovery routes for root level +well_known_routes = auth.get_well_known_routes(mcp_path=MCP_PATH) + +# Assemble the application +app = Starlette( + routes=[ + *well_known_routes, + Mount(MOUNT_PREFIX, app=mcp_app), + ], + lifespan=mcp_app.lifespan, +) + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +For more details on OAuth authentication, see the [Authentication guide](/servers/auth). + +## Production Deployment + +### Running with Uvicorn + +When deploying to production, you'll want to optimize your server for performance and reliability. Uvicorn provides several options to improve your server's capabilities: + +```bash +# Run with basic configuration +uvicorn app:app --host 0.0.0.0 --port 8000 + +# Run with multiple workers for production (requires stateless mode - see below) +uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4 +``` + +### Horizontal Scaling + + + +When deploying FastMCP behind a load balancer or running multiple server instances, you need to understand how the HTTP transport handles sessions and configure your server appropriately. + +#### Understanding Sessions + +By default, FastMCP's Streamable HTTP transport maintains server-side sessions. Sessions enable stateful MCP features like [elicitation](/servers/elicitation) and [sampling](/servers/sampling), where the server needs to maintain context across multiple requests from the same client. + +This works perfectly for single-instance deployments. However, sessions are stored in memory on each server instance, which creates challenges when scaling horizontally. + +#### Without Stateless Mode + +When running multiple server instances behind a load balancer (Traefik, nginx, HAProxy, Kubernetes, etc.), requests from the same client may be routed to different instances: + +1. Client connects to Instance A → session created on Instance A +2. Next request routes to Instance B → session doesn't exist → **request fails** + +You might expect sticky sessions (session affinity) to solve this, but they don't work reliably with MCP clients. + + +**Why sticky sessions don't work:** Most MCP clients—including Cursor and Claude Code—use `fetch()` internally and don't properly forward `Set-Cookie` headers. Without cookies, load balancers can't identify which instance should handle subsequent requests. This is a limitation in how these clients implement HTTP, not something you can fix with load balancer configuration. + + +#### Enabling Stateless Mode + +For horizontally scaled deployments, enable stateless HTTP mode. In stateless mode, each request creates a fresh transport context, eliminating the need for session affinity entirely. + +**Option 1: Via constructor** + +```python +from fastmcp import FastMCP + +mcp = FastMCP("My Server", stateless_http=True) + +@mcp.tool +def process(data: str) -> str: + return f"Processed: {data}" + +app = mcp.http_app() +``` + +**Option 2: Via `run()`** + +```python +if __name__ == "__main__": + mcp.run(transport="http", stateless_http=True) +``` + +**Option 3: Via environment variable** + +```bash +FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4 +``` + +### Environment Variables + +Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations. + +Here's an example using bearer token authentication (though OAuth is recommended for production): + +```python +import os +from fastmcp import FastMCP +from fastmcp.server.auth import BearerTokenAuth + +# Read configuration from environment +auth_token = os.environ.get("MCP_AUTH_TOKEN") +if auth_token: + auth = BearerTokenAuth(token=auth_token) + mcp = FastMCP("Production Server", auth=auth) +else: + mcp = FastMCP("Production Server") + +app = mcp.http_app() +``` + +Deploy with your secrets safely stored in environment variables: +```bash +MCP_AUTH_TOKEN=secret uvicorn app:app --host 0.0.0.0 --port 8000 +``` + +### OAuth Token Security + + + +If you're using the [OAuth Proxy](/servers/auth/oauth-proxy), FastMCP issues its own JWT tokens to clients instead of forwarding upstream provider tokens. This maintains proper OAuth 2.0 token boundaries. + +**Default Behavior (Development Only):** + +By default, FastMCP automatically manages cryptographic keys: +- **Mac/Windows**: Keys are generated and stored in your system keyring, surviving server restarts. Suitable **only** for development and local testing. +- **Linux**: Keys are ephemeral (random salt at startup), so tokens are invalidated on restart. + +This automatic approach is convenient for development but not suitable for production deployments. + +**For Production:** + +Production requires explicit key management to ensure tokens survive restarts and can be shared across multiple server instances. This requires the following two things working together: + +1. **Explicit JWT signing key** for signing tokens issued to clients +3. **Persistent network-accessible storage** for upstream tokens (wrapped in `FernetEncryptionWrapper` to encrypt sensitive data at rest) + +**Configuration:** + +Add two parameters to your auth provider: + +```python {8-12} +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +auth = GitHubProvider( + client_id=os.environ["GITHUB_CLIENT_ID"], + client_secret=os.environ["GITHUB_CLIENT_SECRET"], + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore(host="redis.example.com", port=6379), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ), + base_url="https://your-server.com" # use HTTPS +) +``` + +Both parameters are required for production. Without an explicit signing key, keys are signed using a key derived from the client_secret, which will cause invalidation upon rotation of the client secret. Without persistent storage, tokens are local to the server and won't be trusted across hosts. **Wrap your storage backend in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without encryption, tokens are stored in plaintext. + +For more details on the token architecture and key management, see [OAuth Proxy Key and Storage Management](/servers/auth/oauth-proxy#key-and-storage-management). + +## Testing Your Deployment + +Once your server is deployed, you'll need to verify it's accessible and functioning correctly. For comprehensive testing strategies including connectivity tests, client testing, and authentication testing, see the [Testing Your Server](/development/tests) guide. + +## Hosting Your Server + +This guide has shown you how to create an HTTP-accessible MCP server, but you'll still need a hosting provider to make it available on the internet. Your FastMCP server can run anywhere that supports Python web applications: + +- **Cloud VMs** (AWS EC2, Google Compute Engine, Azure VMs) +- **Container platforms** (Cloud Run, Container Instances, ECS) +- **Platform-as-a-Service** (Railway, Render, Vercel) +- **Edge platforms** (Cloudflare Workers) +- **Kubernetes clusters** (self-managed or managed) + +The key requirements are Python 3.10+ support and the ability to expose an HTTP port. Most providers will require you to package your server (requirements.txt, Dockerfile, etc.) according to their deployment format. For managed, zero-configuration deployment, see [FastMCP Cloud](/deployment/fastmcp-cloud). diff --git a/docs/v2/deployment/running-server.mdx b/docs/v2/deployment/running-server.mdx new file mode 100644 index 000000000..85595f92f --- /dev/null +++ b/docs/v2/deployment/running-server.mdx @@ -0,0 +1,258 @@ +--- +title: Running Your Server +sidebarTitle: Running Your Server +description: Learn how to run your FastMCP server locally for development and testing +icon: circle-play +--- + +FastMCP servers can be run in different ways depending on your needs. This guide focuses on running servers locally for development and testing. For production deployment to a URL, see the [HTTP Deployment](/deployment/http) guide. + +## The `run()` Method + +Every FastMCP server needs to be started to accept connections. The simplest way to run a server is by calling the `run()` method on your FastMCP instance. This method starts the server and blocks until it's stopped, handling all the connection management for you. + + +For maximum compatibility, it's best practice to place the `run()` call within an `if __name__ == "__main__":` block. This ensures the server starts only when the script is executed directly, not when imported as a module. + + +```python {9-10} my_server.py +from fastmcp import FastMCP + +mcp = FastMCP(name="MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + mcp.run() +``` + +You can now run this MCP server by executing `python my_server.py`. + +## Transport Protocols + +MCP servers communicate with clients through different transport protocols. Think of transports as the "language" your server speaks to communicate with clients. FastMCP supports three main transport protocols, each designed for specific use cases and deployment scenarios. + +The choice of transport determines how clients connect to your server, what network capabilities are available, and how many clients can connect simultaneously. Understanding these transports helps you choose the right approach for your application. + +### STDIO Transport (Default) + +STDIO (Standard Input/Output) is the default transport for FastMCP servers. When you call `run()` without arguments, your server uses STDIO transport. This transport communicates through standard input and output streams, making it perfect for command-line tools and desktop applications like Claude Desktop. + +With STDIO transport, the client spawns a new server process for each session and manages its lifecycle. The server reads MCP messages from stdin and writes responses to stdout. This is why STDIO servers don't stay running - they're started on-demand by the client. + +```python +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + mcp.run() # Uses STDIO transport by default +``` + +STDIO is ideal for: +- Local development and testing +- Claude Desktop integration +- Command-line tools +- Single-user applications + +### HTTP Transport (Streamable) + +HTTP transport turns your MCP server into a web service accessible via a URL. This transport uses the Streamable HTTP protocol, which allows clients to connect over the network. Unlike STDIO where each client gets its own process, an HTTP server can handle multiple clients simultaneously. + +The Streamable HTTP protocol provides full bidirectional communication between client and server, supporting all MCP operations including streaming responses. This makes it the recommended choice for network-based deployments. + +To use HTTP transport, specify it in the `run()` method along with networking options: + +```python +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + # Start an HTTP server on port 8000 + mcp.run(transport="http", host="127.0.0.1", port=8000) +``` + +Your server is now accessible at `http://localhost:8000/mcp`. This URL is the MCP endpoint that clients will connect to. HTTP transport enables: +- Network accessibility +- Multiple concurrent clients +- Integration with web infrastructure +- Remote deployment capabilities + +For production HTTP deployment with authentication and advanced configuration, see the [HTTP Deployment](/deployment/http) guide. + +### SSE Transport (Legacy) + +Server-Sent Events (SSE) transport was the original HTTP-based transport for MCP. While still supported for backward compatibility, it has limitations compared to the newer Streamable HTTP transport. SSE only supports server-to-client streaming, making it less efficient for bidirectional communication. + +```python +if __name__ == "__main__": + # SSE transport - use HTTP instead for new projects + mcp.run(transport="sse", host="127.0.0.1", port=8000) +``` + +We recommend using HTTP transport instead of SSE for all new projects. SSE remains available only for compatibility with older clients that haven't upgraded to Streamable HTTP. + +### Choosing the Right Transport + +Each transport serves different needs. STDIO is perfect when you need simple, local execution - it's what Claude Desktop and most command-line tools expect. HTTP transport is essential when you need network access, want to serve multiple clients, or plan to deploy your server remotely. SSE exists only for backward compatibility and shouldn't be used in new projects. + +Consider your deployment scenario: Are you building a tool for local use? STDIO is your best choice. Need a centralized service that multiple clients can access? HTTP transport is the way to go. + +## The FastMCP CLI + +FastMCP provides a powerful command-line interface for running servers without modifying the source code. The CLI can automatically find and run your server with different transports, manage dependencies, and handle development workflows: + +```bash +fastmcp run server.py +``` + +The CLI automatically finds a FastMCP instance in your file (named `mcp`, `server`, or `app`) and runs it with the specified options. This is particularly useful for testing different transports or configurations without changing your code. + +### Dependency Management + +The CLI integrates with `uv` to manage Python environments and dependencies: + +```bash +# Run with a specific Python version +fastmcp run server.py --python 3.11 + +# Run with additional packages +fastmcp run server.py --with pandas --with numpy + +# Run with dependencies from a requirements file +fastmcp run server.py --with-requirements requirements.txt + +# Combine multiple options +fastmcp run server.py --python 3.10 --with httpx --transport http + +# Run within a specific project directory +fastmcp run server.py --project /path/to/project +``` + + +When using `--python`, `--with`, `--project`, or `--with-requirements`, the server runs via `uv run` subprocess instead of using your local environment. + + +### Passing Arguments to Servers + +When servers accept command line arguments (using argparse, click, or other libraries), you can pass them after `--`: + +```bash +fastmcp run config_server.py -- --config config.json +fastmcp run database_server.py -- --database-path /tmp/db.sqlite --debug +``` + +This is useful for servers that need configuration files, database paths, API keys, or other runtime options. + +For more CLI features including development mode with the MCP Inspector, see the [CLI documentation](/patterns/cli). + +### Async Usage + +FastMCP servers are built on async Python, but the framework provides both synchronous and asynchronous APIs to fit your application's needs. The `run()` method we've been using is actually a synchronous wrapper around the async server implementation. + +For applications that are already running in an async context, FastMCP provides the `run_async()` method: + +```python {10-12} +from fastmcp import FastMCP +import asyncio + +mcp = FastMCP(name="MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" + +async def main(): + # Use run_async() in async contexts + await mcp.run_async(transport="http", port=8000) + +if __name__ == "__main__": + asyncio.run(main()) +``` + + +The `run()` method cannot be called from inside an async function because it creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running. + +Always use `run_async()` inside async functions and `run()` in synchronous contexts. + + +Both `run()` and `run_async()` accept the same transport arguments, so all the examples above apply to both methods. + +## Custom Routes + +When using HTTP transport, you might want to add custom web endpoints alongside your MCP server. This is useful for health checks, status pages, or simple APIs. FastMCP lets you add custom routes using the `@custom_route` decorator: + +```python +from fastmcp import FastMCP +from starlette.requests import Request +from starlette.responses import PlainTextResponse + +mcp = FastMCP("MyServer") + +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(request: Request) -> PlainTextResponse: + return PlainTextResponse("OK") + +@mcp.tool +def process(data: str) -> str: + return f"Processed: {data}" + +if __name__ == "__main__": + mcp.run(transport="http") # Health check at http://localhost:8000/health +``` + +Custom routes are served by the same web server as your MCP endpoint. They're available at the root of your domain while the MCP endpoint is at `/mcp/`. For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/http#integration-with-web-frameworks). + +## Alternative Initialization Patterns + +The `if __name__ == "__main__"` pattern works well for standalone scripts, but some deployment scenarios require different approaches. FastMCP handles these cases automatically. + +### CLI-Only Servers + +When using the FastMCP CLI, you don't need the `if __name__` block at all. The CLI will find your FastMCP instance and run it: + +```python +# server.py +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") # CLI looks for 'mcp', 'server', or 'app' + +@mcp.tool +def process(data: str) -> str: + return f"Processed: {data}" + +# No if __name__ block needed - CLI will find and run 'mcp' +``` + +### ASGI Applications + +For ASGI deployment (running with Uvicorn or similar), you'll want to create an ASGI application object. This approach is common in production deployments where you need more control over the server configuration: + +```python +# app.py +from fastmcp import FastMCP + +def create_app(): + mcp = FastMCP("MyServer") + + @mcp.tool + def process(data: str) -> str: + return f"Processed: {data}" + + return mcp.http_app() + +app = create_app() # Uvicorn will use this +``` + +See the [HTTP Deployment](/deployment/http) guide for more ASGI deployment patterns. \ No newline at end of file diff --git a/docs/v2/deployment/server-configuration.mdx b/docs/v2/deployment/server-configuration.mdx new file mode 100644 index 000000000..789a8005b --- /dev/null +++ b/docs/v2/deployment/server-configuration.mdx @@ -0,0 +1,640 @@ +--- +title: "Project Configuration" +sidebarTitle: "Project Configuration" +description: Use fastmcp.json for portable, declarative project configuration +icon: file-code +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +FastMCP supports declarative configuration through `fastmcp.json` files. This is the canonical and preferred way to configure FastMCP projects, providing a single source of truth for server settings, dependencies, and deployment options that replaces complex command-line arguments. + +The `fastmcp.json` file is designed to be a portable description of your server configuration that can be shared across environments and teams. When running from a `fastmcp.json` file, you can override any configuration values using CLI arguments. + +## Overview + +The `fastmcp.json` configuration file allows you to define all aspects of your FastMCP server in a structured, shareable format. Instead of remembering command-line arguments or writing shell scripts, you declare your server's configuration once and use it everywhere. + +When you have a `fastmcp.json` file, running your server becomes as simple as: + +```bash +# Run the server using the configuration +fastmcp run fastmcp.json + +# Or if fastmcp.json exists in the current directory +fastmcp run +``` + +This configuration approach ensures reproducible deployments across different environments, from local development to production servers. It works seamlessly with Claude Desktop, VS Code extensions, and any MCP-compatible client. + +## File Structure + +The `fastmcp.json` configuration answers three fundamental questions about your server: + +- **Source** = WHERE does your server code live? +- **Environment** = WHAT environment setup does it require? +- **Deployment** = HOW should the server run? + +This conceptual model helps you understand the purpose of each configuration section and organize your settings effectively. The configuration file maps directly to these three concerns: + +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + // WHERE: Location of your server code + "type": "filesystem", // Optional, defaults to "filesystem" + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + // WHAT: Environment setup and dependencies + "type": "uv", // Optional, defaults to "uv" + "python": ">=3.10", + "dependencies": ["pandas", "numpy"] + }, + "deployment": { + // HOW: Runtime configuration + "transport": "stdio", + "log_level": "INFO" + } +} +``` + +Only the `source` field is required. The `environment` and `deployment` sections are optional and provide additional configuration when needed. + +### JSON Schema Support + +FastMCP provides JSON schemas for IDE autocomplete and validation. Add the schema reference to your `fastmcp.json` for enhanced developer experience: + +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + } +} +``` + +Two schema URLs are available: +- **Version-specific**: `https://gofastmcp.com/public/schemas/fastmcp.json/v1.json` +- **Latest version**: `https://gofastmcp.com/public/schemas/fastmcp.json/latest.json` + +Modern IDEs like VS Code will automatically provide autocomplete suggestions, validation, and inline documentation when the schema is specified. + +### Source Configuration + +The source configuration determines **WHERE** your server code lives. It tells FastMCP how to find and load your server, whether it's a local Python file, a remote repository, or hosted in the cloud. This section is required and forms the foundation of your configuration. + + + + The server source configuration that determines where your server code lives. + + + The source type identifier that determines which implementation to use. Currently supports `"filesystem"` for local files. Future releases will add support for `"git"` and `"cloud"` source types. + + + + When `type` is `"filesystem"` (or omitted), the source points to a local Python file containing your FastMCP server: + + + Path to the Python file containing your FastMCP server. + + + + Name of the server instance or factory function within the module: + - Can be a FastMCP server instance (e.g., `mcp = FastMCP("MyServer")`) + - Can be a function with no arguments that returns a FastMCP server + - If not specified, FastMCP searches for common names: `mcp`, `server`, or `app` + + + **Example:** + ```json + "source": { + "type": "filesystem", + "path": "src/server.py", + "entrypoint": "mcp" + } + ``` + + Note: File paths are resolved relative to the configuration file's location. + + + + + +**Future Source Types** + +Future releases will support additional source types: +- **Git repositories** (`type: "git"`) for loading server code directly from version control +- **FastMCP Cloud** (`type: "cloud"`) for hosted servers with automatic scaling and management + + +### Environment Configuration + +The environment configuration determines **WHAT** environment setup your server requires. It controls the build-time setup of your Python environment, ensuring your server runs with the exact Python version and dependencies it requires. This section creates isolated, reproducible environments across different systems. + +FastMCP uses an extensible environment system with a base `Environment` class that can be implemented by different environment providers. Currently, FastMCP supports the `UVEnvironment` for Python environment management using `uv`'s powerful dependency resolver. + + + + Optional environment configuration. When specified, FastMCP uses the appropriate environment implementation to set up your server's runtime. + + + The environment type identifier that determines which implementation to use. Currently supports `"uv"` for Python environments managed by uv. If omitted, defaults to `"uv"`. + + + + When `type` is `"uv"` (or omitted), the environment uses uv to manage Python dependencies: + + + Python version constraint. Examples: + - Exact version: `"3.12"` + - Minimum version: `">=3.10"` + - Version range: `">=3.10,<3.13"` + + + + List of pip packages with optional version specifiers (PEP 508 format). + ```json + "dependencies": ["pandas>=2.0", "requests", "httpx"] + ``` + + + + Path to a requirements.txt file, resolved relative to the config file location. + ```json + "requirements": "requirements.txt" + ``` + + + + Path to a project directory containing pyproject.toml for uv project management. + ```json + "project": "." + ``` + + + + List of paths to packages to install in editable/development mode. Useful for local development when you want changes to be reflected immediately. Supports multiple packages for monorepo setups or shared libraries. + ```json + "editable": ["."] + ``` + Or with multiple packages: + ```json + "editable": [".", "../shared-lib", "/path/to/another-package"] + ``` + + + **Example:** + ```json + "environment": { + "type": "uv", + "python": ">=3.10", + "dependencies": ["pandas", "numpy"], + "editable": ["."] + } + ``` + + Note: When any UVEnvironment field is specified, FastMCP automatically creates an isolated environment using `uv` before running your server. + + + + +When environment configuration is provided, FastMCP: +1. Detects the environment type (defaults to `"uv"` if not specified) +2. Creates an isolated environment using the appropriate provider +3. Installs the specified dependencies +4. Runs your server in this clean environment + +This build-time setup ensures your server always has the dependencies it needs, without polluting your system Python or conflicting with other projects. + + +**Future Environment Types** + +Similar to source types, future releases may support additional environment types for different runtime requirements, such as Docker containers or language-specific environments beyond Python. + + +### Deployment Configuration + +The deployment configuration controls **HOW** your server runs. It defines the runtime behavior including network settings, environment variables, and execution context. These settings determine how your server operates when it executes, from transport protocols to logging levels. + +Environment variables are included in this section because they're runtime configuration that affects how your server behaves when it executes, not how its environment is built. The deployment configuration is applied every time your server starts, controlling its operational characteristics. + + + + Optional runtime configuration for the server. + + + + Protocol for client communication: + - `"stdio"`: Standard input/output for desktop clients + - `"http"`: Network-accessible HTTP server + - `"sse"`: Server-sent events + + + + Network interface to bind (HTTP transport only): + - `"127.0.0.1"`: Local connections only + - `"0.0.0.0"`: All network interfaces + + + + Port number for HTTP transport. + + + + URL path for the MCP endpoint when using HTTP transport. + + + + Server logging verbosity. Options: + - `"DEBUG"`: Detailed debugging information + - `"INFO"`: General informational messages + - `"WARNING"`: Warning messages + - `"ERROR"`: Error messages only + - `"CRITICAL"`: Critical errors only + + + + Environment variables to set when running the server. Supports `${VAR_NAME}` syntax for runtime interpolation. + ```json + "env": { + "API_KEY": "secret-key", + "DATABASE_URL": "postgres://${DB_USER}@${DB_HOST}/mydb" + } + ``` + + + + Working directory for the server process. Relative paths are resolved from the config file location. + + + + Command-line arguments to pass to the server, passed after `--` to the server's argument parser. + ```json + "args": ["--config", "server-config.json"] + ``` + + + + + +#### Environment Variable Interpolation + +The `env` field in deployment configuration supports runtime interpolation of environment variables using `${VAR_NAME}` syntax. This enables dynamic configuration based on your deployment environment: + +```json +{ + "deployment": { + "env": { + "API_URL": "https://api.${ENVIRONMENT}.example.com", + "DATABASE_URL": "postgres://${DB_USER}:${DB_PASS}@${DB_HOST}/myapp", + "CACHE_KEY": "myapp_${ENVIRONMENT}_${VERSION}" + } + } +} +``` + +When the server starts, FastMCP replaces `${ENVIRONMENT}`, `${DB_USER}`, etc. with values from your system's environment variables. If a variable doesn't exist, the placeholder is preserved as-is. + +**Example**: If your system has `ENVIRONMENT=production` and `DB_HOST=db.example.com`: +```json +// Configuration +{ + "deployment": { + "env": { + "API_URL": "https://api.${ENVIRONMENT}.example.com", + "DB_HOST": "${DB_HOST}" + } + } +} + +// Result at runtime +{ + "API_URL": "https://api.production.example.com", + "DB_HOST": "db.example.com" +} +``` + +This feature is particularly useful for: +- Deploying the same configuration across development, staging, and production +- Keeping sensitive values out of configuration files +- Building dynamic URLs and connection strings +- Creating environment-specific prefixes or suffixes + +## Usage with CLI Commands + +FastMCP automatically detects and uses a file specifically named `fastmcp.json` in the current directory, making server execution simple and consistent. Files with FastMCP configuration format but different names are not auto-detected and must be specified explicitly: + +```bash +# Auto-detect fastmcp.json in current directory +cd my-project +fastmcp run # No arguments needed! + +# Or specify a configuration file explicitly +fastmcp run prod.fastmcp.json + +# Skip environment setup when already in a uv environment +fastmcp run fastmcp.json --skip-env + +# Skip source preparation when source is already prepared +fastmcp run fastmcp.json --skip-source + +# Skip both environment and source preparation +fastmcp run fastmcp.json --skip-env --skip-source +``` + +### Pre-building Environments + +You can use `fastmcp project prepare` to create a persistent uv project with all dependencies pre-installed: + +```bash +# Create a persistent environment +fastmcp project prepare fastmcp.json --output-dir ./env + +# Use the pre-built environment to run the server +fastmcp run fastmcp.json --project ./env +``` + +This pattern separates environment setup (slow) from server execution (fast), useful for deployment scenarios. + +### Using an Existing Environment + +By default, FastMCP creates an isolated environment with `uv` based on your configuration. When you already have a suitable Python environment, use the `--skip-env` flag to skip environment creation: + +```bash +fastmcp run fastmcp.json --skip-env +``` + +**When you already have an environment:** +- You're in an activated virtual environment with all dependencies installed +- You're inside a Docker container with pre-installed dependencies +- You're in a CI/CD pipeline that pre-builds the environment +- You're using a system-wide installation with all required packages +- You're in a uv-managed environment (prevents infinite recursion) + +This flag tells FastMCP: "I already have everything installed, just run the server." + +### Using an Existing Source + +When working with source types that require preparation (future support for git repositories or cloud sources), use the `--skip-source` flag when you already have the source code available: + +```bash +fastmcp run fastmcp.json --skip-source +``` + +**When you already have the source:** +- You've previously cloned a git repository and don't need to re-fetch +- You have a cached copy of a cloud-hosted server +- You're in a CI/CD pipeline where source checkout is a separate step +- You're iterating locally on already-downloaded code + +This flag tells FastMCP: "I already have the source code, skip any download/clone steps." + +Note: For filesystem sources (local Python files), this flag has no effect since they don't require preparation. + +The configuration file works with all FastMCP commands: +- **`run`** - Start the server in production mode +- **`dev`** - Launch with the Inspector UI for development +- **`inspect`** - View server capabilities and configuration +- **`install`** - Install to Claude Desktop, Cursor, or other MCP clients + +When no file argument is provided, FastMCP searches the current directory for `fastmcp.json`. This means you can simply navigate to your project directory and run `fastmcp run` to start your server with all its configured settings. + +### CLI Override Behavior + +Command-line arguments take precedence over configuration file values, allowing ad-hoc adjustments without modifying the file: + +```bash +# Config specifies port 3000, CLI overrides to 8080 +fastmcp run fastmcp.json --port 8080 + +# Config specifies stdio, CLI overrides to HTTP +fastmcp run fastmcp.json --transport http + +# Add extra dependencies not in config +fastmcp run fastmcp.json --with requests --with httpx +``` + +This precedence order enables: +- Quick testing of different settings +- Environment-specific overrides in deployment scripts +- Debugging with increased log levels +- Temporary configuration changes + +### Custom Naming Patterns + +You can use different configuration files for different environments: + +- `fastmcp.json` - Default configuration +- `dev.fastmcp.json` - Development settings +- `prod.fastmcp.json` - Production settings +- `test_fastmcp.json` - Test configuration + +Any file with "fastmcp.json" in the name is recognized as a configuration file. + +## Examples + + + + +A minimal configuration for a simple server: + +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + } +} +``` +This configuration explicitly specifies the server entrypoint (`mcp`), making it clear which server instance or factory function to use. Uses all defaults: STDIO transport, no special dependencies, standard logging. + + + +A configuration optimized for local development: + +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + // WHERE does the server live? + "source": { + "path": "src/server.py", + "entrypoint": "app" + }, + // WHAT dependencies does it need? + "environment": { + "type": "uv", + "python": "3.12", + "dependencies": ["fastmcp[dev]"], + "editable": "." + }, + // HOW should it run? + "deployment": { + "transport": "http", + "host": "127.0.0.1", + "port": 8000, + "log_level": "DEBUG", + "env": { + "DEBUG": "true", + "ENV": "development" + } + } +} +``` + + + +A production-ready configuration with full dependency management: + +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + // WHERE does the server live? + "source": { + "path": "app/main.py", + "entrypoint": "mcp_server" + }, + // WHAT dependencies does it need? + "environment": { + "python": "3.11", + "requirements": "requirements/production.txt", + "project": "." + }, + // HOW should it run? + "deployment": { + "transport": "http", + "host": "0.0.0.0", + "port": 3000, + "path": "/api/mcp/", + "log_level": "INFO", + "env": { + "ENV": "production", + "API_BASE_URL": "https://api.example.com", + "DATABASE_URL": "postgresql://user:pass@db.example.com/prod" + }, + "cwd": "/app", + "args": ["--workers", "4"] + } +} +``` + + + +Configuration for a data analysis server with scientific packages: + +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "analysis_server.py", + "entrypoint": "mcp" + }, + "environment": { + "python": "3.11", + "dependencies": [ + "pandas>=2.0", + "numpy", + "scikit-learn", + "matplotlib", + "jupyterlab" + ] + }, + "deployment": { + "transport": "stdio", + "env": { + "MATPLOTLIB_BACKEND": "Agg", + "DATA_PATH": "./datasets" + } + } +} +``` + + + +You can maintain multiple configuration files for different environments: + +**dev.fastmcp.json**: +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "deployment": { + "transport": "http", + "log_level": "DEBUG" + } +} +``` + +**prod.fastmcp.json**: +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "requirements": "requirements/production.txt" + }, + "deployment": { + "transport": "http", + "host": "0.0.0.0", + "log_level": "WARNING" + } +} +``` + +Run different configurations: +```bash +fastmcp run dev.fastmcp.json # Development +fastmcp run prod.fastmcp.json # Production +``` + + + +## Migrating from CLI Arguments + +If you're currently using command-line arguments or shell scripts, migrating to `fastmcp.json` simplifies your workflow. Here's how common CLI patterns map to configuration: + +**CLI Command**: +```bash +uv run --with pandas --with requests \ + fastmcp run server.py \ + --transport http \ + --port 8000 \ + --log-level INFO +``` + +**Equivalent fastmcp.json**: +```json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "dependencies": ["pandas", "requests"] + }, + "deployment": { + "transport": "http", + "port": 8000, + "log_level": "INFO" + } +} +``` + +Now simply run: +```bash +fastmcp run # Automatically finds and uses fastmcp.json +``` + +The configuration file approach provides better documentation, easier sharing, and consistent execution across different environments while maintaining the flexibility to override settings when needed. \ No newline at end of file diff --git a/docs/v2/development/contributing.mdx b/docs/v2/development/contributing.mdx new file mode 100644 index 000000000..6be0ffba6 --- /dev/null +++ b/docs/v2/development/contributing.mdx @@ -0,0 +1,187 @@ +--- +title: "Contributing" +description: "Development workflow for FastMCP contributors" +icon: code-pull-request +--- + +Contributing to FastMCP means joining a community that values clean, maintainable code and thoughtful API design. All contributions are valued - from fixing typos in documentation to implementing major features. + +## Issues + +### Issue First, Code Second + +**Every pull request requires a corresponding issue - no exceptions.** This requirement creates a collaborative space where approach, scope, and alignment are established before code is written. Issues serve as design documents where maintainers and contributors discuss implementation strategy, identify potential conflicts with existing patterns, and ensure proposed changes advance FastMCP's vision. + +**FastMCP is an opinionated framework, not a kitchen sink.** The maintainers have strong beliefs about what FastMCP should and shouldn't do. Just because something takes N lines of code and you want it in fewer lines doesn't mean FastMCP should take on the maintenance burden or endorse that pattern. This is judged at the maintainers' discretion. + +Use issues to understand scope BEFORE opening PRs. The issue discussion determines whether a feature belongs in core, contrib, or not at all. + +### Writing Good Issues + +FastMCP is an extremely highly-trafficked repository maintained by a very small team. Issues that appear to transfer burden to maintainers without any effort to validate the problem will be closed. Please help the maintainers help you by always providing a minimal reproducible example and clearly describing the problem. + +**LLM-generated issues will be closed immediately.** Issues that contain paragraphs of unnecessary explanation, verbose problem descriptions, or obvious LLM authorship patterns obfuscate the actual problem and transfer burden to maintainers. + +Write clear, concise issues that: +- State the problem directly +- Provide a minimal reproducible example +- Skip unnecessary background or context +- Take responsibility for clear communication + +Issues may be labeled "Invalid" simply due to confusion caused by verbosity or not adhering to the guidelines outlined here. + +## Pull Requests + +PRs that deviate from FastMCP's core principles will be rejected regardless of implementation quality. **PRs are NOT for iterating on ideas** - they should only be opened for ideas that already have a bias toward acceptance based on issue discussion. + + +### Development Environment + +#### Installation + +To contribute to FastMCP, you'll need to set up a development environment with all necessary tools and dependencies. + +```bash +# Clone the repository +git clone https://github.com/jlowin/fastmcp.git +cd fastmcp + +# Install all dependencies including dev tools +uv sync + +# Install prek hooks +uv run prek install +``` + +In addition, some development commands require [just](https://github.com/casey/just) to be installed. + +Prek hooks will run automatically on every commit to catch issues before they reach CI. If you see failures, fix them before committing - never commit broken code expecting to fix it later. + +### Development Standards + +#### Scope + +Large pull requests create review bottlenecks and quality risks. Unless you're fixing a discrete bug or making an incredibly well-scoped change, keep PRs small and focused. + +A PR that changes 50 lines across 3 files can be thoroughly reviewed in minutes. A PR that changes 500 lines across 20 files requires hours of careful analysis and often hides subtle issues. + +Breaking large features into smaller PRs: +- Creates better review experiences +- Makes git history clear +- Simplifies debugging with bisect +- Reduces merge conflicts +- Gets your code merged faster + +#### Code Quality + +FastMCP values clarity over cleverness. Every line you write will be maintained by someone else - possibly years from now, possibly without context about your decisions. + +**PRs can be rejected for two opposing reasons:** +1. **Insufficient quality** - Code that doesn't meet our standards for clarity, maintainability, or idiomaticity +2. **Overengineering** - Code that is overbearing, unnecessarily complex, or tries to be too clever + +The focus is on idiomatic, high-quality Python. FastMCP uses patterns like `NotSet` type as an alternative to `None` in certain situations - follow existing patterns. + +#### Required Practices + +**Full type annotations** on all functions and methods. They catch bugs before runtime and serve as inline documentation. + +**Async/await patterns** for all I/O operations. Even if your specific use case doesn't need concurrency, consistency means users can compose features without worrying about blocking operations. + +**Descriptive names** make code self-documenting. `auth_token` is clear; `tok` requires mental translation. + +**Specific exception types** make error handling predictable. Catching `ValueError` tells readers exactly what error you expect. Never use bare `except` clauses. + +#### Anti-Patterns to Avoid + +**Complex one-liners** are hard to debug and modify. Break operations into clear steps. + +**Mutable default arguments** cause subtle bugs. Use `None` as the default and create the mutable object inside the function. + +**Breaking established patterns** confuses readers. If you must deviate, discuss in the issue first. + +### Prek Checks + +```bash +# Runs automatically on commit, or manually: +uv run prek run --all-files +``` + +This runs three critical tools: +- **Ruff**: Linting and formatting +- **Prettier**: Code formatting +- **ty**: Static type checking + +Pytest runs separately as a distinct workflow step after prek checks pass. CI will reject PRs that fail these checks. Always run them locally first. + +### Testing + +Tests are documentation that shows how features work. Good tests give reviewers confidence and help future maintainers understand intent. + +```bash +# Run specific test directory +uv run pytest tests/server/ -v + +# Run all tests before submitting PR +uv run pytest +``` + +Every new feature needs tests. See the [Testing Guide](/development/tests) for patterns and requirements. + +### Documentation + +A feature doesn't exist unless it's documented. Note that FastMCP's hosted documentation always tracks the main branch - users who want historical documentation can clone the repo, checkout a specific tag, and host it themselves. + +```bash +# Preview documentation locally +just docs +``` + +Documentation requirements: +- **Explain concepts in prose first** - Code without context is just syntax +- **Complete, runnable examples** - Every code block should be copy-pasteable +- **Register in docs.json** - Makes pages appear in navigation +- **Version badges** - Mark when features were added using `` + +#### SDK Documentation + +FastMCP's SDK documentation is auto-generated from the source code docstrings and type annotations. It is automatically updated on every merge to main by a GitHub Actions workflow, so users are *not* responsible for keeping the documentation up to date. However, to generate it proactively, you can use the following command: + +```bash +just api-ref-all +``` + +### Submitting Your PR + +#### Before Submitting + +1. **Run all checks**: `uv run prek run --all-files && uv run pytest` +2. **Keep scope small**: One feature or fix per PR +3. **Write clear description**: Your PR description becomes permanent documentation +4. **Update docs**: Include documentation for API changes + +#### PR Description + +Write PR descriptions that explain: +- What problem you're solving +- Why you chose this approach +- Any trade-offs or alternatives considered +- Migration path for breaking changes + +Focus on the "why" - the code shows the "what". Keep it concise but complete. + +#### What We Look For + +**Framework Philosophy**: FastMCP is NOT trying to do all things or provide all shortcuts. Features are rejected when they don't align with the framework's vision, even if perfectly implemented. The burden of proof is on the PR to demonstrate value. + +**Code Quality**: We verify code follows existing patterns. Consistency reduces cognitive load. When every module works similarly, developers understand new code quickly. + +**Test Coverage**: Not every line needs testing, but every behavior does. Tests document intent and protect against regressions. + +**Breaking Changes**: May be acceptable in minor versions but must be clearly documented. See the [versioning policy](/development/releases#versioning-policy). + +## Special Modules + +**`contrib`**: Community-maintained patterns and utilities. Original authors maintain their contributions. Not representative of the core framework. + +**`experimental`**: Maintainer-developed features that may preview future functionality. Can break or be deleted at any time without notice. Pin your FastMCP version when using these features. \ No newline at end of file diff --git a/docs/v2/development/releases.mdx b/docs/v2/development/releases.mdx new file mode 100644 index 000000000..346462736 --- /dev/null +++ b/docs/v2/development/releases.mdx @@ -0,0 +1,79 @@ +--- +title: "Releases" +description: "FastMCP versioning and release process" +icon: "truck-fast" +--- + +FastMCP releases frequently to deliver features quickly in the rapidly evolving MCP ecosystem. We use semantic versioning pragmatically - the Model Context Protocol is young, patterns are still emerging, and waiting for perfect stability would mean missing opportunities to empower developers with better tools. + +## Versioning Policy + +### Semantic Versioning + +**Major (x.0.0)**: Complete API redesigns + +Major versions represent fundamental shifts. FastMCP 2.x is entirely different from 1.x in both implementation and design philosophy. + +**Minor (2.x.0)**: New features and evolution + + +Unlike traditional semantic versioning, minor versions **may** include [breaking changes](#breaking-changes) when necessary for the ecosystem's evolution. This flexibility is essential in a young ecosystem where perfect backwards compatibility would prevent important improvements. + + +FastMCP always targets the most current MCP Protocol version. Breaking changes in the MCP spec or MCP SDK automatically flow through to FastMCP - we prioritize staying current with the latest features and conventions over maintaining compatibility with older protocol versions. + +**Patch (2.0.x)**: Bug fixes and refinements + +Patch versions contain only bug fixes without breaking changes. These are safe updates you can apply with confidence. + +### Breaking Changes + +We permit breaking changes in minor versions because the MCP ecosystem is rapidly evolving. Refusing to break problematic APIs would accumulate design debt that eventually makes the framework unusable. Each breaking change represents a deliberate decision to keep FastMCP aligned with the ecosystem's evolution. + +When breaking changes occur: +- They only happen in minor versions (e.g., 2.3.x to 2.4.0) +- Release notes explain what changed and how to migrate +- We provide deprecation warnings at least 1 minor version in advance when possible +- Changes must substantially benefit users to justify disruption + +The public API is what's covered by our compatibility guarantees - these are the parts of FastMCP you can rely on to remain stable within a minor version. The public API consists of: +- `FastMCP` server class, `Client` class, and FastMCP `Context` +- Core MCP components: `Tool`, `Prompt`, `Resource`, `ResourceTemplate`, and transports +- Their public methods and documented behaviors + +Everything else (utilities, private methods, internal modules) may change without notice. This boundary lets us refactor internals and improve implementation details without breaking your code. For production stability, pin to specific versions. + + +The `fastmcp.server.auth` module was introduced in 2.12.0 and is exempted from this policy temporarily, meaning it is *expected* to have breaking changes even on patch versions. This is because auth is a rapidly evolving part of the MCP spec and it would be dangerous to be beholden to old decisions. Please pin your FastMCP version if using authentication in production. + +We expect this exemption to last through at least the 2.12.x and 2.13.x release series. + + +### Production Use + +Pin to exact versions: +``` +fastmcp==2.11.0 # Good +fastmcp>=2.11.0 # Bad - will install breaking changes +``` + +## Creating Releases + +Our release process is intentionally simple: + +1. Create GitHub release with tag `vMAJOR.MINOR.PATCH` (e.g., `v2.11.0`) +2. Generate release notes automatically, and curate or add additional editorial information as needed +3. GitHub releases automatically trigger PyPI deployments + +This automation lets maintainers focus on code quality rather than release mechanics. + +### Release Cadence + +We follow a feature-driven release cadence rather than a fixed schedule. Minor versions ship approximately every 3-4 weeks when significant functionality is ready. + +Patch releases ship promptly for: +- Critical bug fixes +- Security updates (immediate release) +- Regression fixes + +This approach means you get improvements as soon as they're ready rather than waiting for arbitrary release dates. diff --git a/docs/v2/development/tests.mdx b/docs/v2/development/tests.mdx new file mode 100644 index 000000000..d47d3d092 --- /dev/null +++ b/docs/v2/development/tests.mdx @@ -0,0 +1,396 @@ +--- +title: "Tests" +description: "Testing patterns and requirements for FastMCP" +icon: vial +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + +Good tests are the foundation of reliable software. In FastMCP, we treat tests as first-class documentation that demonstrates how features work while protecting against regressions. Every new capability needs comprehensive tests that demonstrate correctness. + +## FastMCP Tests + +### Running Tests + +```bash +# Run all tests +uv run pytest + +# Run specific test file +uv run pytest tests/server/test_auth.py + +# Run with coverage +uv run pytest --cov=fastmcp + +# Skip integration tests for faster runs +uv run pytest -m "not integration" + +# Skip tests that spawn processes +uv run pytest -m "not integration and not client_process" +``` + +Tests should complete in under 1 second unless marked as integration tests. This speed encourages running them frequently, catching issues early. + +### Test Organization + +Our test organization mirrors the `src/` directory structure, creating a predictable mapping between code and tests. When you're working on `src/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files. + +### Test Markers + +We use pytest markers to categorize tests that require special resources or take longer to run: + +```python +@pytest.mark.integration +async def test_github_api_integration(): + """Test GitHub API integration with real service.""" + token = os.getenv("FASTMCP_GITHUB_TOKEN") + if not token: + pytest.skip("FASTMCP_GITHUB_TOKEN not available") + + # Test against real GitHub API + client = GitHubClient(token) + repos = await client.list_repos("jlowin") + assert "fastmcp" in [repo.name for repo in repos] + +@pytest.mark.client_process +async def test_stdio_transport(): + """Test STDIO transport with separate process.""" + # This spawns a subprocess + async with Client("python examples/simple_echo.py") as client: + result = await client.call_tool("echo", {"message": "test"}) + assert result.content[0].text == "test" +``` + +## Writing Tests + + +### Test Requirements + +Following these practices creates maintainable, debuggable test suites that serve as both documentation and regression protection. + +#### Single Behavior Per Test + +Each test should verify exactly one behavior. When it fails, you need to know immediately what broke. A test that checks five things gives you five potential failure points to investigate. A test that checks one thing points directly to the problem. + + + +```python Good: Atomic Test +async def test_tool_registration(): + """Test that tools are properly registered with the server.""" + mcp = FastMCP("test-server") + + @mcp.tool + def add(a: int, b: int) -> int: + return a + b + + tools = mcp.list_tools() + assert len(tools) == 1 + assert tools[0].name == "add" +``` + +```python Bad: Multi-Behavior Test +async def test_server_functionality(): + """Test multiple server features at once.""" + mcp = FastMCP("test-server") + + # Tool registration + @mcp.tool + def add(a: int, b: int) -> int: + return a + b + + # Resource creation + @mcp.resource("config://app") + def get_config(): + return {"version": "1.0"} + + # Authentication setup + mcp.auth = BearerTokenProvider({"token": "user"}) + + # What exactly are we testing? If this fails, what broke? + assert mcp.list_tools() + assert mcp.list_resources() + assert mcp.auth is not None +``` + + + +#### Self-Contained Setup + +Every test must create its own setup. Tests should be runnable in any order, in parallel, or in isolation. When a test fails, you should be able to run just that test to reproduce the issue. + + + +```python Good: Self-Contained +async def test_tool_execution_with_error(): + """Test that tool errors are properly handled.""" + mcp = FastMCP("test-server") + + @mcp.tool + def divide(a: int, b: int) -> float: + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + + async with Client(mcp) as client: + with pytest.raises(Exception): + await client.call_tool("divide", {"a": 10, "b": 0}) +``` + +```python Bad: Test Dependencies +# Global state that tests depend on +test_server = None + +def test_setup_server(): + """Setup for other tests.""" + global test_server + test_server = FastMCP("shared-server") + +def test_server_works(): + """Test server functionality.""" + # Depends on test_setup_server running first + assert test_server is not None +``` + + + +#### Clear Intent + +Test names and assertions should make the verified behavior obvious. A developer reading your test should understand what feature it validates and how that feature should behave. + +```python +async def test_authenticated_tool_requires_valid_token(): + """Test that authenticated users can access protected tools.""" + mcp = FastMCP("test-server") + mcp.auth = BearerTokenProvider({"secret-token": "test-user"}) + + @mcp.tool + def protected_action() -> str: + return "success" + + async with Client(mcp, auth=BearerAuth("secret-token")) as client: + result = await client.call_tool("protected_action", {}) + assert result.content[0].text == "success" +``` + +#### Using Fixtures + +Use fixtures to create reusable data, server configurations, or other resources for your tests. Note that you should **not** open FastMCP clients in your fixtures as it can create hard-to-diagnose issues with event loops. + +```python +import pytest +from fastmcp import FastMCP, Client + +@pytest.fixture +def weather_server(): + server = FastMCP("WeatherServer") + + @server.tool + def get_temperature(city: str) -> dict: + temps = {"NYC": 72, "LA": 85, "Chicago": 68} + return {"city": city, "temp": temps.get(city, 70)} + + return server + +async def test_temperature_tool(weather_server): + async with Client(weather_server) as client: + result = await client.call_tool("get_temperature", {"city": "LA"}) + assert result.data == {"city": "LA", "temp": 85} +``` + +#### Effective Assertions + +Assertions should be specific and provide context on failure. When a test fails during CI, the assertion message should tell you exactly what went wrong. + +```python +# Basic assertion - minimal context on failure +assert result.status == "success" + +# Better - explains what was expected +assert result.status == "success", f"Expected successful operation, got {result.status}: {result.error}" +``` + +Try not to have too many assertions in a single test unless you truly need to check various aspects of the same behavior. In general, assertions of different behaviors should be in separate tests. + +#### Inline Snapshots + +FastMCP uses `inline-snapshot` for testing complex data structures. On first run of `pytest --inline-snapshot=create` with an empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses. + +```python +from inline_snapshot import snapshot + +async def test_tool_schema_generation(): + """Test that tool schemas are generated correctly.""" + mcp = FastMCP("test-server") + + @mcp.tool + def calculate_tax(amount: float, rate: float = 0.1) -> dict: + """Calculate tax on an amount.""" + return {"amount": amount, "tax": amount * rate, "total": amount * (1 + rate)} + + tools = mcp.list_tools() + schema = tools[0].inputSchema + + # First run: snapshot() is empty, gets auto-populated + # Subsequent runs: compares against stored snapshot + assert schema == snapshot({ + "type": "object", + "properties": { + "amount": {"type": "number"}, + "rate": {"type": "number", "default": 0.1} + }, + "required": ["amount"] + }) +``` + +### In-Memory Testing + +FastMCP uses in-memory transport for testing, where servers and clients communicate directly. The majority of functionality can be tested in a deterministic fashion this way. We use more complex setups only when testing transports themselves. + +The in-memory transport runs the real MCP protocol implementation without network overhead. Instead of deploying your server or managing network connections, you pass your server instance directly to the client. Everything runs in the same Python process - you can set breakpoints anywhere and step through with your debugger. + +```python +from fastmcp import FastMCP, Client + +# Create your server +server = FastMCP("WeatherServer") + +@server.tool +def get_temperature(city: str) -> dict: + """Get current temperature for a city""" + temps = {"NYC": 72, "LA": 85, "Chicago": 68} + return {"city": city, "temp": temps.get(city, 70)} + +async def test_weather_operations(): + # Pass server directly - no deployment needed + async with Client(server) as client: + result = await client.call_tool("get_temperature", {"city": "NYC"}) + assert result.data == {"city": "NYC", "temp": 72} +``` + +This pattern makes tests deterministic and fast - typically completing in milliseconds rather than seconds. + +### Mocking External Dependencies + +FastMCP servers are standard Python objects, so you can mock external dependencies using your preferred approach: + +```python +from unittest.mock import AsyncMock + +async def test_database_tool(): + server = FastMCP("DataServer") + + # Mock the database + mock_db = AsyncMock() + mock_db.fetch_users.return_value = [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"} + ] + + @server.tool + async def list_users() -> list: + return await mock_db.fetch_users() + + async with Client(server) as client: + result = await client.call_tool("list_users", {}) + assert len(result.data) == 2 + assert result.data[0]["name"] == "Alice" + mock_db.fetch_users.assert_called_once() +``` + +### Testing Network Transports + +While in-memory testing covers most unit testing needs, you'll occasionally need to test actual network transports like HTTP or SSE. FastMCP provides two approaches: in-process async servers (preferred), and separate subprocess servers (for special cases). + +#### In-Process Network Testing (Preferred) + + + +For most network transport tests, use `run_server_async` as an async context manager. This runs the server as a task in the same process, providing fast, deterministic tests with full debugger support: + +```python +import pytest +from fastmcp import FastMCP, Client +from fastmcp.client.transports import StreamableHttpTransport +from fastmcp.utilities.tests import run_server_async + +def create_test_server() -> FastMCP: + """Create a test server instance.""" + server = FastMCP("TestServer") + + @server.tool + def greet(name: str) -> str: + return f"Hello, {name}!" + + return server + +@pytest.fixture +async def http_server() -> str: + """Start server in-process for testing.""" + server = create_test_server() + async with run_server_async(server) as url: + yield url + +async def test_http_transport(http_server: str): + """Test actual HTTP transport behavior.""" + async with Client( + transport=StreamableHttpTransport(http_server) + ) as client: + result = await client.ping() + assert result is True + + greeting = await client.call_tool("greet", {"name": "World"}) + assert greeting.data == "Hello, World!" +``` + +The `run_server_async` context manager automatically handles server lifecycle and cleanup. This approach is faster than subprocess-based testing and provides better error messages. + +#### Subprocess Testing (Special Cases) + +For tests that require complete process isolation (like STDIO transport or testing subprocess behavior), use `run_server_in_process`: + +```python +import pytest +from fastmcp.utilities.tests import run_server_in_process +from fastmcp import FastMCP, Client +from fastmcp.client.transports import StreamableHttpTransport + +def run_server(host: str, port: int) -> None: + """Function to run in subprocess.""" + server = FastMCP("TestServer") + + @server.tool + def greet(name: str) -> str: + return f"Hello, {name}!" + + server.run(host=host, port=port) + +@pytest.fixture +async def http_server(): + """Fixture that runs server in subprocess.""" + with run_server_in_process(run_server, transport="http") as url: + yield f"{url}/mcp" + +async def test_http_transport(http_server: str): + """Test actual HTTP transport behavior.""" + async with Client( + transport=StreamableHttpTransport(http_server) + ) as client: + result = await client.ping() + assert result is True +``` + +The `run_server_in_process` utility handles server lifecycle, port allocation, and cleanup automatically. Use this only when subprocess isolation is truly necessary, as it's slower and harder to debug than in-process testing. FastMCP uses the `client_process` marker to isolate these tests in CI. + +### Documentation Testing + +Documentation requires the same validation as code. The `just docs` command launches a local Mintlify server that renders your documentation exactly as users will see it: + +```bash +# Start local documentation server with hot reload +just docs + +# Or run Mintlify directly +mintlify dev +``` + +The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it. \ No newline at end of file diff --git a/docs/v2/development/upgrade-guide.mdx b/docs/v2/development/upgrade-guide.mdx new file mode 100644 index 000000000..f72f1087f --- /dev/null +++ b/docs/v2/development/upgrade-guide.mdx @@ -0,0 +1,153 @@ +--- +title: Upgrade Guide +sidebarTitle: Upgrade Guide +description: Migration instructions for upgrading between FastMCP versions +icon: up +tag: NEW +--- + +This guide provides migration instructions for breaking changes and major updates when upgrading between FastMCP versions. + +## v2.14.0 + +### OpenAPI Parser Promotion + +The experimental OpenAPI parser is now the standard implementation. The legacy parser has been removed. + +**If you were using the legacy parser:** No code changes required. The new parser is a drop-in replacement with improved architecture. + +**If you were using the experimental parser:** Update your imports from the experimental module to the standard location: + + +```python Before +from fastmcp.experimental.server.openapi import FastMCPOpenAPI, RouteMap, MCPType +``` + +```python After +from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap, MCPType +``` + + +The experimental imports will continue working temporarily but will show deprecation warnings. The `FASTMCP_EXPERIMENTAL_ENABLE_NEW_OPENAPI_PARSER` environment variable is no longer needed and can be removed. + +### Deprecated Features Removed + +The following deprecated features have been removed in v2.14.0: + +**BearerAuthProvider** (deprecated in v2.11): + +```python Before +from fastmcp.server.auth.providers.bearer import BearerAuthProvider +``` + +```python After +from fastmcp.server.auth.providers.jwt import JWTVerifier +``` + + +**Context.get_http_request()** (deprecated in v2.2.11): + +```python Before +request = context.get_http_request() +``` + +```python After +from fastmcp.server.dependencies import get_http_request +request = get_http_request() +``` + + +**Top-level Image import** (deprecated in v2.8.1): + +```python Before +from fastmcp import Image +``` + +```python After +from fastmcp.utilities.types import Image +``` + + +**FastMCP dependencies parameter** (deprecated in v2.11.4): + +```python Before +mcp = FastMCP("server", dependencies=["requests", "pandas"]) +``` + +```json After +{ + "environment": { + "dependencies": ["requests", "pandas"] + } +} +``` + + +**Legacy resource prefix format**: The `resource_prefix_format` parameter and "protocol" format have been removed. Only the "path" format is supported (this was already the default). + +**FastMCPProxy client parameter**: + +```python Before +proxy = FastMCPProxy(client=my_client) +``` + +```python After +proxy = FastMCPProxy(client_factory=lambda: my_client) +``` + + +**output_schema=False**: + +```python Before +@mcp.tool(output_schema=False) +def my_tool() -> str: + return "result" +``` + +```python After +@mcp.tool(output_schema=None) +def my_tool() -> str: + return "result" +``` + + +## v2.13.0 + +### OAuth Token Key Management + +The OAuth proxy now issues its own JWT tokens to clients instead of forwarding upstream provider tokens. This improves security by maintaining proper token audience boundaries. + +**What changed:** + +The OAuth proxy now implements a token factory pattern - it receives tokens from your OAuth provider (GitHub, Google, etc.), encrypts and stores them, then issues its own FastMCP JWT tokens to clients. This requires cryptographic keys for JWT signing and token encryption. + +**Default behavior (development):** + +By default, FastMCP automatically manages keys based on your platform: +- **Mac/Windows**: Keys are auto-managed via system keyring, surviving server restarts with zero configuration. Suitable **only** for development and local testing. +- **Linux**: Keys are ephemeral (random salt at startup, regenerated on each restart). + +This works fine for development and testing where re-authentication after restart is acceptable. + +**For production:** + +Production deployments must provide explicit keys and use persistent storage. Add these three things: + +```python +auth = GitHubProvider( + client_id=os.environ["GITHUB_CLIENT_ID"], + client_secret=os.environ["GITHUB_CLIENT_SECRET"], + base_url="https://your-server.com", + + # Explicit keys (required for production) + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + + # Persistent network storage (required for production) + client_storage=RedisStore(host="redis.example.com", port=6379) +) +``` + +**More information:** +- [OAuth Token Security](/deployment/http#oauth-token-security) - Complete production setup guide +- [Key and Storage Management](/servers/auth/oauth-proxy#key-and-storage-management) - Detailed explanation of defaults and production requirements +- [OAuth Proxy Parameters](/servers/auth/oauth-proxy#configuration-parameters) - Parameter documentation diff --git a/docs/v2/getting-started/installation.mdx b/docs/v2/getting-started/installation.mdx new file mode 100644 index 000000000..ff9bb929a --- /dev/null +++ b/docs/v2/getting-started/installation.mdx @@ -0,0 +1,100 @@ +--- +title: Installation +icon: arrow-down-to-line +--- +## Install FastMCP + +We recommend using [uv](https://docs.astral.sh/uv/getting-started/installation/) to install and manage FastMCP. + +If you plan to use FastMCP in your project, you can add it as a dependency with: + +```bash +uv add fastmcp +``` + +Alternatively, you can install it directly with `pip` or `uv pip`: + + ```bash uv + uv pip install fastmcp + ``` + + ```bash pip + pip install fastmcp + ``` + + + +**FastMCP 3.0** is in development and may include breaking changes. To avoid unexpected issues, pin your dependency to v2: `fastmcp<3` + + +### Verify Installation + +To verify that FastMCP is installed correctly, you can run the following command: + +```bash +fastmcp version +``` + +You should see output like the following: + +```bash +$ fastmcp version + +FastMCP version: 2.11.3 +MCP version: 1.12.4 +Python version: 3.12.2 +Platform: macOS-15.3.1-arm64-arm-64bit +FastMCP root path: ~/Developer/fastmcp +``` + +### Dependency Licensing + + +FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations. + +If this is a concern, you can install Cyclopts v5 alpha which removes this dependency: + +```bash +pip install "cyclopts>=5.0.0a1" +``` + +Alternatively, wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details. + +## Upgrading from the Official MCP SDK + +Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient. + + +```python {5} +# Before +# from mcp.server.fastmcp import FastMCP + +# After +from fastmcp import FastMCP + +mcp = FastMCP("My MCP Server") +``` + + +Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities. + + +## Versioning Policy + +FastMCP follows semantic versioning with pragmatic adaptations for the rapidly evolving MCP ecosystem. Breaking changes may occur in minor versions (e.g., 2.3.x to 2.4.0) when necessary to stay current with the MCP Protocol. + +For production use, always pin to exact versions: +``` +fastmcp==2.11.0 # Good +fastmcp>=2.11.0 # Bad - will install breaking changes +``` + +See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy. + +## Contributing to FastMCP + +Interested in contributing to FastMCP? See the [Contributing Guide](/development/contributing) for details on: +- Setting up your development environment +- Running tests and pre-commit hooks +- Submitting issues and pull requests +- Code standards and review process diff --git a/docs/v2/getting-started/quickstart.mdx b/docs/v2/getting-started/quickstart.mdx new file mode 100644 index 000000000..1c5c2330e --- /dev/null +++ b/docs/v2/getting-started/quickstart.mdx @@ -0,0 +1,136 @@ +--- +title: Quickstart +icon: rocket-launch +--- + +Welcome! This guide will help you quickly set up FastMCP, run your first MCP server, and deploy a server to FastMCP Cloud. + +If you haven't already installed FastMCP, follow the [installation instructions](/getting-started/installation). + +## Create a FastMCP Server + +A FastMCP server is a collection of tools, resources, and other MCP components. To create a server, start by instantiating the `FastMCP` class. + +Create a new file called `my_server.py` and add the following code: + +```python my_server.py +from fastmcp import FastMCP + +mcp = FastMCP("My MCP Server") +``` + + +That's it! You've created a FastMCP server, albeit a very boring one. Let's add a tool to make it more interesting. + + +## Add a Tool + +To add a tool that returns a simple greeting, write a function and decorate it with `@mcp.tool` to register it with the server: + +```python my_server.py {5-7} +from fastmcp import FastMCP + +mcp = FastMCP("My MCP Server") + +@mcp.tool +def greet(name: str) -> str: + return f"Hello, {name}!" +``` + + +## Run the Server + +The simplest way to run your FastMCP server is to call its `run()` method. You can choose between different transports, like `stdio` for local servers, or `http` for remote access: + + + +```python my_server.py (stdio) {9, 10} +from fastmcp import FastMCP + +mcp = FastMCP("My MCP Server") + +@mcp.tool +def greet(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + mcp.run() +``` + +```python my_server.py (HTTP) {9, 10} +from fastmcp import FastMCP + +mcp = FastMCP("My MCP Server") + +@mcp.tool +def greet(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + + + +This lets us run the server with `python my_server.py`. The stdio transport is the traditional way to connect MCP servers to clients, while the HTTP transport enables remote connections. + + +Why do we need the `if __name__ == "__main__":` block? + +The `__main__` block is recommended for consistency and compatibility, ensuring your server works with all MCP clients that execute your server file as a script. Users who will exclusively run their server with the FastMCP CLI can omit it, as the CLI imports the server object directly. + + +### Using the FastMCP CLI + +You can also use the `fastmcp run` command to start your server. Note that the FastMCP CLI **does not** execute the `__main__` block of your server file. Instead, it imports your server object and runs it with whatever transport and options you provide. + +For example, to run this server with the default stdio transport (no matter how you called `mcp.run()`), you can use the following command: +```bash +fastmcp run my_server.py:mcp +``` + +To run this server with the HTTP transport, you can use the following command: +```bash +fastmcp run my_server.py:mcp --transport http --port 8000 +``` + +## Call Your Server + +Once your server is running with HTTP transport, you can connect to it with a FastMCP client or any LLM client that supports the MCP protocol: + +```python my_client.py +import asyncio +from fastmcp import Client + +client = Client("http://localhost:8000/mcp") + +async def call_tool(name: str): + async with client: + result = await client.call_tool("greet", {"name": name}) + print(result) + +asyncio.run(call_tool("Ford")) +``` + +Note that: +- FastMCP clients are asynchronous, so we need to use `asyncio.run` to run the client +- We must enter a client context (`async with client:`) before using the client +- You can make multiple client calls within the same context + +## Deploy to FastMCP Cloud + +[FastMCP Cloud](https://fastmcp.cloud) is a hosting service run by the FastMCP team at [Prefect](https://www.prefect.io/fastmcp). It is optimized to deploy authenticated FastMCP servers as quickly as possible, giving you a secure URL that you can plug into any LLM client. + + +FastMCP Cloud is **free for personal servers** and offers simple pay-as-you-go pricing for teams. + + +To deploy your server, you'll need a [GitHub account](https://github.com). Once you have one, you can deploy your server in three steps: + +1. Push your `my_server.py` file to a GitHub repository +2. Sign in to [FastMCP Cloud](https://fastmcp.cloud) with your GitHub account +3. Create a new project from your repository and enter `my_server.py:mcp` as the server entrypoint + +That's it! FastMCP Cloud will build and deploy your server, making it available at a URL like `https://your-project.fastmcp.app/mcp`. You can chat with it to test its functionality, or connect to it from any LLM client that supports the MCP protocol. + +For more details, see the [FastMCP Cloud guide](/deployment/fastmcp-cloud). diff --git a/docs/v2/getting-started/welcome.mdx b/docs/v2/getting-started/welcome.mdx new file mode 100644 index 000000000..a0a4bf583 --- /dev/null +++ b/docs/v2/getting-started/welcome.mdx @@ -0,0 +1,115 @@ +--- +title: "Welcome to FastMCP 2.0!" +sidebarTitle: "Welcome!" +description: The fast, Pythonic way to build MCP servers and clients. +icon: hand-wave +--- + +'F' logo on a watercolor background +'F' logo on a watercolor background + + +**FastMCP is the standard framework for building MCP applications.** The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) provides a standardized way to connect LLMs to tools and data, and FastMCP makes it production-ready with clean, Pythonic code: + +```python {1} +from fastmcp import FastMCP + +mcp = FastMCP("Demo 🚀") + +@mcp.tool +def add(a: int, b: int) -> int: + """Add two numbers""" + return a + b + +if __name__ == "__main__": + mcp.run() +``` + +## Beyond Basic MCP + +FastMCP pioneered Python MCP development, and FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024. + +**This is FastMCP 2.0,** the actively maintained version that extends far beyond basic protocol implementation. While the SDK provides core functionality, FastMCP 2.0 delivers everything needed for production: advanced MCP patterns (server composition, proxying, OpenAPI/FastAPI generation, tool transformation), enterprise auth (Google, GitHub, Azure, Auth0, WorkOS, and more), deployment tools, testing frameworks, and comprehensive client libraries. + +Ready to build? Start with our [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart). + +FastMCP is made with 💙 by [Prefect](https://www.prefect.io/). + + +**FastMCP 3.0** is in development and may include breaking changes. To avoid unexpected issues, pin your dependency to v2: `fastmcp<3` + + +## What is MCP? + +The Model Context Protocol lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can: + +- Expose data through `Resources` (think of these sort of like GET endpoints; they are used to load information into the LLM's context) +- Provide functionality through `Tools` (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) +- Define interaction patterns through `Prompts` (reusable templates for LLM interactions) +- And more! + +FastMCP provides a high-level, Pythonic interface for building, managing, and interacting with these servers. + +## Why FastMCP? + +FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need — FastMCP handles the rest. + +🚀 **Fast**: High-level interface means less code and faster development + +🍀 **Simple**: Build MCP servers with minimal boilerplate + +🐍 **Pythonic**: Feels natural to Python developers + +🔍 **Complete**: Everything for production — enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more + +FastMCP provides the shortest path from idea to production. Deploy locally, to the cloud with [FastMCP Cloud](https://fastmcp.cloud) (free for personal servers), or to your own infrastructure. + + +**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 2.13.1`) to indicate when they were introduced. Note that this may include features that are not yet released. + + +## LLM-Friendly Docs + +The FastMCP documentation is available in multiple LLM-friendly formats: + +### MCP Server + +The FastMCP docs are accessible via MCP! The server URL is `https://gofastmcp.com/mcp`. + +In fact, you can use FastMCP to search the FastMCP docs: + +```python +import asyncio +from fastmcp import Client + +async def main(): + async with Client("https://gofastmcp.com/mcp") as client: + result = await client.call_tool( + name="SearchFastMcp", + arguments={"query": "deploy a FastMCP server"} + ) + print(result) + +asyncio.run(main()) +``` + +### Text Formats + +The docs are also available in [llms.txt format](https://llmstxt.org/): +- [llms.txt](https://gofastmcp.com/llms.txt) - A sitemap listing all documentation pages +- [llms-full.txt](https://gofastmcp.com/llms-full.txt) - The entire documentation in one file (may exceed context windows) + +Any page can be accessed as markdown by appending `.md` to the URL. For example, this page becomes `https://gofastmcp.com/getting-started/welcome.md`. + +You can also copy any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard. diff --git a/docs/v2/integrations/anthropic.mdx b/docs/v2/integrations/anthropic.mdx new file mode 100644 index 000000000..b8156ac95 --- /dev/null +++ b/docs/v2/integrations/anthropic.mdx @@ -0,0 +1,228 @@ +--- +title: Anthropic API 🤝 FastMCP +sidebarTitle: Anthropic API +description: Connect FastMCP servers to the Anthropic API +icon: message-code +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + +Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API. + + +Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector). + + +## Create a Server + +First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + +## Deploy the Server + +Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports. + +For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server. + +Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet: + + +```bash FastMCP server +python server.py +``` + +```bash ngrok +ngrok http 8000 +``` + + + +This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks. + + +## Call the Server + +To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP): + +```bash +pip install anthropic +``` + +You'll also need to authenticate with Anthropic. You can do this by setting the `ANTHROPIC_API_KEY` environment variable. Consult the Anthropic SDK documentation for more information. + +```bash +export ANTHROPIC_API_KEY="your-api-key" +``` + +Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment. **At this time you must also include the `extra_headers` parameter with the `anthropic-beta` header.** + +```python {5, 13-22} +import anthropic +from rich import print + +# Your server URL (replace with your actual URL) +url = 'https://your-server-url.com' + +client = anthropic.Anthropic() + +response = client.beta.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=1000, + messages=[{"role": "user", "content": "Roll a few dice!"}], + mcp_servers=[ + { + "type": "url", + "url": f"{url}/mcp/", + "name": "dice-server", + } + ], + extra_headers={ + "anthropic-beta": "mcp-client-2025-04-04" + } +) + +print(response.content) +``` + +If you run this code, you'll see something like the following output: + +```text +I'll roll some dice for you! Let me use the dice rolling tool. + +I rolled 3 dice and got: 4, 2, 6 + +The results were 4, 2, and 6. Would you like me to roll again or roll a different number of dice? +``` + + +## Authentication + + + +The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it. + +### Server Authentication + +The simplest way to add authentication to the server is to use a bearer token scheme. + +For this example, we'll quickly generate our own tokens with FastMCP's `RSAKeyPair` utility, but this may not be appropriate for production use. For more details, see the complete server-side [Token Verification](/servers/auth/token-verification) documentation. + +We'll start by creating an RSA key pair to sign and verify tokens. + +```python +from fastmcp.server.auth.providers.jwt import RSAKeyPair + +key_pair = RSAKeyPair.generate() +access_token = key_pair.create_token(audience="dice-server") +``` + + +FastMCP's `RSAKeyPair` utility is for development and testing only. + + +Next, we'll create a `JWTVerifier` to authenticate the server. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth import JWTVerifier + +auth = JWTVerifier( + public_key=key_pair.public_key, + audience="dice-server", +) + +mcp = FastMCP(name="Dice Roller", auth=auth) +``` + +Here is a complete example that you can copy/paste. For simplicity and the purposes of this example only, it will print the token to the console. **Do NOT do this in production!** + +```python server.py [expandable] +from fastmcp import FastMCP +from fastmcp.server.auth import JWTVerifier +from fastmcp.server.auth.providers.jwt import RSAKeyPair +import random + +key_pair = RSAKeyPair.generate() +access_token = key_pair.create_token(audience="dice-server") + +auth = JWTVerifier( + public_key=key_pair.public_key, + audience="dice-server", +) + +mcp = FastMCP(name="Dice Roller", auth=auth) + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n") + mcp.run(transport="http", port=8000) +``` + +### Client Authentication + +If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated. + +```python +Error code: 400 - { + "type": "error", + "error": { + "type": "invalid_request_error", + "message": "MCP server 'dice-server' requires authentication. Please provide an authorization_token.", + }, +} +``` + +To authenticate the client, you can pass the token using the `authorization_token` parameter in your MCP server configuration: + +```python {8, 21} +import anthropic +from rich import print + +# Your server URL (replace with your actual URL) +url = 'https://your-server-url.com' + +# Your access token (replace with your actual token) +access_token = 'your-access-token' + +client = anthropic.Anthropic() + +response = client.beta.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=1000, + messages=[{"role": "user", "content": "Roll a few dice!"}], + mcp_servers=[ + { + "type": "url", + "url": f"{url}/mcp/", + "name": "dice-server", + "authorization_token": access_token + } + ], + extra_headers={ + "anthropic-beta": "mcp-client-2025-04-04" + } +) + +print(response.content) +``` + +You should now see the dice roll results in the output. diff --git a/docs/v2/integrations/auth0.mdx b/docs/v2/integrations/auth0.mdx new file mode 100644 index 000000000..160af738f --- /dev/null +++ b/docs/v2/integrations/auth0.mdx @@ -0,0 +1,277 @@ +--- +title: Auth0 OAuth 🤝 FastMCP +sidebarTitle: Auth0 +description: Secure your FastMCP server with Auth0 OAuth +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **Auth0 OAuth**. While Auth0 does have support for Dynamic Client Registration, it is not enabled by default so this integration uses the [**OIDC Proxy**](/servers/auth/oidc-proxy) pattern to bridge Auth0's dynamic OIDC configuration with MCP's authentication requirements. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. An **[Auth0 Account](https://auth0.com/)** with access to create Applications +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Create an Auth0 Application + +Create an Application in your Auth0 settings to get the credentials needed for authentication: + + + + Go to **Applications → Applications** in your Auth0 account. + + Click **"+ Create Application"** to create a new application. + + + + - **Name**: Choose a name users will recognize (e.g., "My FastMCP Server") + - **Choose an application type**: Choose "Single Page Web Applications" + - Click **Create** to create the application + + + + Select the "Settings" tab for your application, then find the "Application URIs" section. + + - **Allowed Callback URLs**: Your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`) + - Click **Save** to save your changes + + + The callback URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. + + + + If you want to use a custom callback path (e.g., `/auth/auth0/callback`), make sure to set the same path in both your Auth0 Application settings and the `redirect_path` parameter when configuring the Auth0Provider. + + + + + After creating the app, in the "Basic Information" section you'll see: + + - **Client ID**: A public identifier like `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB` + - **Client Secret**: A private hidden value that should always be stored securely + + + Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production. + + + + + Go to **Applications → APIs** in your Auth0 account. + + - Find the API that you want to use for your application + - **API Audience**: A URL that uniquely identifies the API + + + Store this along with of the credentials above. Never commit this to version control. Use environment variables or a secrets manager in production. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `Auth0Provider`. + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.auth0 import Auth0Provider + +# The Auth0Provider utilizes Auth0 OIDC configuration +auth_provider = Auth0Provider( + config_url="https://.../.well-known/openid-configuration", # Your Auth0 configuration URL + client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB", # Your Auth0 application Client ID + client_secret="vPYqbjemq...", # Your Auth0 application Client Secret + audience="https://...", # Your Auth0 API audience + base_url="http://localhost:8000", # Must match your application configuration + # redirect_path="/auth/callback" # Default value, customize if needed +) + +mcp = FastMCP(name="Auth0 Secured App", auth=auth_provider) + +# Add a protected tool to test authentication +@mcp.tool +async def get_token_info() -> dict: + """Returns information about the Auth0 token.""" + from fastmcp.server.dependencies import get_access_token + + token = get_access_token() + + return { + "issuer": token.claims.get("iss"), + "audience": token.claims.get("aud"), + "scope": token.claims.get("scope") + } +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by Auth0 authentication. + +### Testing with a Client + +Create a test client that authenticates with your Auth0-protected server: + +```python test_client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle Auth0 OAuth flows + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open Auth0 login in your browser + print("✓ Authenticated with Auth0!") + + # Test the protected tool + result = await client.call_tool("get_token_info") + print(f"Auth0 audience: {result['audience']}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to Auth0's authorization page +2. After you authorize the app, you'll be redirected back +3. The client receives the token and can make authenticated requests + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.auth0 import Auth0Provider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Production setup with encrypted persistent token storage +auth_provider = Auth0Provider( + config_url="https://.../.well-known/openid-configuration", + client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB", + client_secret="vPYqbjemq...", + audience="https://...", + base_url="https://your-production-domain.com", + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production Auth0 App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Environment Variables + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the Auth0 provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.auth0.Auth0Provider` to use Auth0 authentication. + + + +### Auth0-Specific Configuration + +These environment variables provide default values for the Auth0 provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Auth0 Application Configuration URL (e.g., `https://.../.well-known/openid-configuration`) + + + +Your Auth0 Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`) + + + +Your Auth0 Application Client Secret (e.g., `vPYqbjemq...`) + + + +Your Auth0 API Audience + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +Redirect path configured in your Auth0 Application + + + +Comma-, space-, or JSON-separated list of required AUth0 scopes (e.g., `openid email` or `["openid","email"]`) + + + +Example `.env` file: +```bash +# Use the Auth0 provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider + +# Auth0 configuration and credentials +FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration +FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB +FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq... +FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://... +FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES=openid,email +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="Auth0 Secured App") + +@mcp.tool +async def search_logs() -> list[str]: + """Search the service logs.""" + # Your tool implementation here + pass +``` diff --git a/docs/v2/integrations/authkit.mdx b/docs/v2/integrations/authkit.mdx new file mode 100644 index 000000000..2072638fa --- /dev/null +++ b/docs/v2/integrations/authkit.mdx @@ -0,0 +1,133 @@ +--- +title: AuthKit 🤝 FastMCP +sidebarTitle: AuthKit +description: Secure your FastMCP server with AuthKit by WorkOS +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using WorkOS's **AuthKit**, a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where AuthKit handles user login and your FastMCP server validates the tokens. + + +## Configuration +### Prerequisites + +Before you begin, you will need: +1. A **[WorkOS Account](https://workos.com/)** and a new **Project**. +2. An **[AuthKit](https://www.authkit.com/)** instance configured within your WorkOS project. +3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`). + +### Step 1: AuthKit Configuration + +In your WorkOS Dashboard, enable AuthKit and configure the following settings: + + + + Go to **Applications → Configuration** and enable **Dynamic Client Registration**. This allows MCP clients register with your application automatically. + + ![Enable Dynamic Client Registration](./images/authkit/enable_dcr.png) + + + + Find your **AuthKit Domain** on the configuration page. It will look like `https://your-project-12345.authkit.app`. You'll need this for your FastMCP server configuration. + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server file and use the `AuthKitProvider` to handle all the OAuth integration automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.workos import AuthKitProvider + +# The AuthKitProvider automatically discovers WorkOS endpoints +# and configures JWT token validation +auth_provider = AuthKitProvider( + authkit_domain="https://your-project-12345.authkit.app", + base_url="http://localhost:8000" # Use your actual server URL +) + +mcp = FastMCP(name="AuthKit Secured App", auth=auth_provider) +``` + +## Testing + +To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the `authkit_domain` and `base_url` with your actual values!), you can run the following command: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Now, you can use a FastMCP client to test that you can reach your server after authenticating: + +```python +from fastmcp import Client +import asyncio + +async def main(): + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + assert await client.ping() + +if __name__ == "__main__": + asyncio.run(main()) +``` + + +## Environment Variables + + + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the AuthKit provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.workos.AuthKitProvider` to use AuthKit authentication. + + + +### AuthKit-Specific Configuration + +These environment variables provide default values for the AuthKit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your AuthKit domain (e.g., `https://your-project-12345.authkit.app`) + + + +Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development) + + + +Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid", "profile", "email"]`) + + + +Example `.env` file: +```bash +# Use the AuthKit provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider + +# AuthKit configuration +FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project-12345.authkit.app +FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_REQUIRED_SCOPES=openid,profile,email +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="AuthKit Secured App") +``` \ No newline at end of file diff --git a/docs/v2/integrations/aws-cognito.mdx b/docs/v2/integrations/aws-cognito.mdx new file mode 100644 index 000000000..965791c38 --- /dev/null +++ b/docs/v2/integrations/aws-cognito.mdx @@ -0,0 +1,365 @@ +--- +title: AWS Cognito OAuth 🤝 FastMCP +sidebarTitle: AWS Cognito +description: Secure your FastMCP server with AWS Cognito user pools +icon: aws +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **AWS Cognito user pools**. Since AWS Cognito doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge AWS Cognito's traditional OAuth with MCP's authentication requirements. It also includes robust JWT token validation, ensuring enterprise-grade authentication. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. An **[AWS Account](https://aws.amazon.com/)** with access to create AWS Cognito user pools +2. Basic familiarity with AWS Cognito concepts (user pools, app clients) +3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Create an AWS Cognito User Pool and App Client + +Set up AWS Cognito user pool with an app client to get the credentials needed for authentication: + + + + Go to the **[AWS Cognito Console](https://console.aws.amazon.com/cognito/)** and ensure you're in your desired AWS region. + + Select **"User pools"** from the side navigation (click on the hamburger icon at the top left in case you don't see any), and click **"Create user pool"** to create a new user pool. + + + + AWS Cognito now provides a streamlined setup experience: + + 1. **Application type**: Select **"Traditional web application"** (this is the correct choice for FastMCP server-side authentication) + 2. **Name your application**: Enter a descriptive name (e.g., `FastMCP Server`) + + The traditional web application type automatically configures: + - Server-side authentication with client secrets + - Authorization code grant flow + - Appropriate security settings for confidential clients + + + Choose "Traditional web application" rather than SPA, Mobile app, or Machine-to-machine options. This ensures proper OAuth 2.0 configuration for FastMCP. + + + + + AWS will guide you through configuration options: + + - **Sign-in identifiers**: Choose how users will sign in (email, username, or phone) + - **Required attributes**: Select any additional user information you need + - **Return URL**: Add your callback URL (e.g., `http://localhost:8000/auth/callback` for development) + + + The simplified interface handles most OAuth security settings automatically based on your application type selection. + + + + + Review your configuration and click **"Create user pool"**. + + After creation, you'll see your user pool details. Save these important values: + - **User pool ID** (format: `eu-central-1_XXXXXXXXX`) + - **Client ID** (found under → "Applications" → "App clients" in the side navigation → \ → "App client information") + - **Client Secret** (found under → "Applications" → "App clients" in the side navigation → \ → "App client information") + + + The user pool ID and app client credentials are all you need for FastMCP configuration. + + + + + Under "Login pages" in your app client's settings, you can double check and adjust the OAuth configuration: + + - **Allowed callback URLs**: Add your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`) + - **Allowed sign-out URLs**: Optional, for logout functionality + - **OAuth 2.0 grant types**: Ensure "Authorization code grant" is selected + - **OpenID Connect scopes**: Select scopes your application needs (e.g., `openid`, `email`, `profile`) + + + For local development, you can use `http://localhost` URLs. For production, you must use HTTPS. + + + + + AWS Cognito requires a resource server entry to support OAuth with protected resources. Without this, token exchange will fail with an `invalid_grant` error. + + Navigate to **"Branding" → "Domain"** in the side navigation, then: + + 1. Click **"Create resource server"** + 2. **Resource server name**: Enter a descriptive name (e.g., `My MCP Server`) + 3. **Resource server identifier**: Enter your MCP endpoint URL exactly as it will be accessed (e.g., `http://localhost:8000/mcp` for development, or `https://your-server.com/mcp` for production) + 4. Click **"Create resource server"** + + + The resource server identifier must exactly match your `base_url + mcp_path`. For the default configuration with `base_url="http://localhost:8000"` and `path="/mcp"`, use `http://localhost:8000/mcp`. + + + + + After setup, you'll have: + + - **User Pool ID**: Format like `eu-central-1_XXXXXXXXX` + - **Client ID**: Your application's client identifier + - **Client Secret**: Generated client secret (keep secure) + - **AWS Region**: Where Your AWS Cognito user pool is located + + + Store these credentials securely. Never commit them to version control. Use environment variables or AWS Secrets Manager in production. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `AWSCognitoProvider`, which handles AWS Cognito's JWT tokens and user claims automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.aws import AWSCognitoProvider +from fastmcp.server.dependencies import get_access_token + +# The AWSCognitoProvider handles JWT validation and user claims +auth_provider = AWSCognitoProvider( + user_pool_id="eu-central-1_XXXXXXXXX", # Your AWS Cognito user pool ID + aws_region="eu-central-1", # AWS region (defaults to eu-central-1) + client_id="your-app-client-id", # Your app client ID + client_secret="your-app-client-secret", # Your app client Secret + base_url="http://localhost:8000", # Must match your callback URL + # redirect_path="/auth/callback" # Default value, customize if needed +) + +mcp = FastMCP(name="AWS Cognito Secured App", auth=auth_provider) + +# Add a protected tool to test authentication +@mcp.tool +async def get_access_token_claims() -> dict: + """Get the authenticated user's access token claims.""" + token = get_access_token() + return { + "sub": token.claims.get("sub"), + "username": token.claims.get("username"), + "cognito:groups": token.claims.get("cognito:groups", []), + } +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by AWS Cognito OAuth authentication. + +### Testing with a Client + +Create a test client that authenticates with Your AWS Cognito-protected server: + +```python test_client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle AWS Cognito OAuth + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open AWS Cognito login in your browser + print("✓ Authenticated with AWS Cognito!") + + # Test the protected tool + print("Calling protected tool: get_access_token_claims") + result = await client.call_tool("get_access_token_claims") + user_data = result.data + print("Available access token claims:") + print(f"- sub: {user_data.get('sub', 'N/A')}") + print(f"- username: {user_data.get('username', 'N/A')}") + print(f"- cognito:groups: {user_data.get('cognito:groups', [])}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to AWS Cognito's hosted UI login page +2. After you sign in (or sign up), you'll be redirected back to your MCP server +3. The client receives the JWT token and can make authenticated requests + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.aws import AWSCognitoProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Production setup with encrypted persistent token storage +auth_provider = AWSCognitoProvider( + user_pool_id="eu-central-1_XXXXXXXXX", + aws_region="eu-central-1", + client_id="your-app-client-id", + client_secret="your-app-client-secret", + base_url="https://your-production-domain.com", + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production AWS Cognito App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + +## Environment Variables + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the AWS Cognito provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.aws.AWSCognitoProvider` to use AWS Cognito authentication. + + + +### AWS Cognito-Specific Configuration + +These environment variables provide default values for the AWS Cognito provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your AWS Cognito user pool ID (e.g., `eu-central-1_XXXXXXXXX`) + + + +AWS region where your AWS Cognito user pool is located + + + +Your AWS Cognito app client ID + + + +Your AWS Cognito app client secret + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +One of the redirect paths configured in your AWS Cognito app client + + + +Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid","email","profile"]`) + + + +Example `.env` file: +```bash +# Use the AWS Cognito provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.aws.AWSCognitoProvider + +# AWS Cognito credentials +FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID=eu-central-1_XXXXXXXXX +FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION=eu-central-1 +FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID=your-app-client-id +FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET=your-app-client-secret +FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES=openid,email,profile +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_access_token + +# Authentication is automatically configured from environment +mcp = FastMCP(name="AWS Cognito Secured App") + +@mcp.tool +async def get_access_token_claims() -> dict: + """Get the authenticated user's access token claims.""" + token = get_access_token() + return { + "sub": token.claims.get("sub"), + "username": token.claims.get("username"), + "cognito:groups": token.claims.get("cognito:groups", []), + } +``` + +## Features + +### JWT Token Validation + +The AWS Cognito provider includes robust JWT token validation: + +- **Signature Verification**: Validates tokens against AWS Cognito's public keys (JWKS) +- **Expiration Checking**: Automatically rejects expired tokens +- **Issuer Validation**: Ensures tokens come from your specific AWS Cognito user pool +- **Scope Enforcement**: Verifies required OAuth scopes are present + +### User Claims and Groups + +Access rich user information from AWS Cognito JWT tokens: + +```python +from fastmcp.server.dependencies import get_access_token + +@mcp.tool +async def admin_only_tool() -> str: + """A tool only available to admin users.""" + token = get_access_token() + user_groups = token.claims.get("cognito:groups", []) + + if "admin" not in user_groups: + raise ValueError("This tool requires admin access") + + return "Admin access granted!" +``` + +### Enterprise Integration + +Perfect for enterprise environments with: + +- **Single Sign-On (SSO)**: Integrate with corporate identity providers +- **Multi-Factor Authentication (MFA)**: Leverage AWS Cognito's built-in MFA +- **User Groups**: Role-based access control through AWS Cognito groups +- **Custom Attributes**: Access custom user attributes defined in your AWS Cognito user pool +- **Compliance**: Meet enterprise security and compliance requirements \ No newline at end of file diff --git a/docs/v2/integrations/azure.mdx b/docs/v2/integrations/azure.mdx new file mode 100644 index 000000000..e18843abf --- /dev/null +++ b/docs/v2/integrations/azure.mdx @@ -0,0 +1,391 @@ +--- +title: Azure (Microsoft Entra ID) OAuth 🤝 FastMCP +sidebarTitle: Azure (Entra ID) +description: Secure your FastMCP server with Azure/Microsoft Entra OAuth +icon: microsoft +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **Azure OAuth** (Microsoft Entra ID). Since Azure doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Azure's traditional OAuth with MCP's authentication requirements. FastMCP validates Azure JWTs against your application's client_id. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. An **[Azure Account](https://portal.azure.com/)** with access to create App registrations +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) +3. Your Azure tenant ID (found in Azure Portal under Microsoft Entra ID) + +### Step 1: Create an Azure App Registration + +Create an App registration in Azure Portal to get the credentials needed for authentication: + + + + Go to the [Azure Portal](https://portal.azure.com) and navigate to **Microsoft Entra ID → App registrations**. + + Click **"New registration"** to create a new application. + + + + Fill in the application details: + + - **Name**: Choose a name users will recognize (e.g., "My FastMCP Server") + - **Supported account types**: Choose based on your needs: + - **Single tenant**: Only users in your organization + - **Multitenant**: Users in any Microsoft Entra directory + - **Multitenant + personal accounts**: Any Microsoft account + - **Redirect URI**: Select "Web" and enter your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`) + + + The redirect URI must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. For local development, Azure allows `http://localhost` URLs. For production, you must use HTTPS. + + + + If you want to use a custom callback path (e.g., `/auth/azure/callback`), make sure to set the same path in both your Azure App registration and the `redirect_path` parameter when configuring the AzureProvider. + + + - **Expose an API**: Configure your Application ID URI and define scopes + - Go to **Expose an API** in the App registration sidebar. + - Click **Set** next to "Application ID URI" and choose one of: + - Keep the default `api://{client_id}` + - Set a custom value, following the supported formats (see [Identifier URI restrictions](https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions)) + - Click **Add a scope** and create a scope your app will require, for example: + - Scope name: `read` (or `write`, etc.) + - Admin consent display name/description: as appropriate for your org + - Who can consent: as needed (Admins only or Admins and users) + + - **Configure Access Token Version**: Ensure your app uses access token v2 + - Go to **Manifest** in the App registration sidebar. + - Find the `requestedAccessTokenVersion` property and set it to `2`: + ```json + "api": { + "requestedAccessTokenVersion": 2 + } + ``` + - Click **Save** at the top of the manifest editor. + + + Access token v2 is required for FastMCP's Azure integration to work correctly. If this is not set, you may encounter authentication errors. + + + + In FastMCP's `AzureProvider`, set `identifier_uri` to your Application ID URI (optional; defaults to `api://{client_id}`) and set `required_scopes` to the unprefixed scope names (e.g., `read`, `write`). During authorization, FastMCP automatically prefixes scopes with your `identifier_uri`. + + + + + + + + After registration, navigate to **Certificates & secrets** in your app's settings. + + - Click **"New client secret"** + - Add a description (e.g., "FastMCP Server") + - Choose an expiration period + - Click **"Add"** + + + Copy the secret value immediately - it won't be shown again! You'll need to create a new secret if you lose it. + + + + + From the **Overview** page of your app registration, note: + + - **Application (client) ID**: A UUID like `835f09b6-0f0f-40cc-85cb-f32c5829a149` + - **Directory (tenant) ID**: A UUID like `08541b6e-646d-43de-a0eb-834e6713d6d5` + - **Client Secret**: The value you copied in the previous step + + + Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `AzureProvider`, which handles Azure's OAuth flow automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.azure import AzureProvider + +# The AzureProvider handles Azure's token format and validation +auth_provider = AzureProvider( + client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149", # Your Azure App Client ID + client_secret="your-client-secret", # Your Azure App Client Secret + tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", # Your Azure Tenant ID (REQUIRED) + base_url="http://localhost:8000", # Must match your App registration + required_scopes=["your-scope"], # At least one scope REQUIRED - name of scope from your App + # identifier_uri defaults to api://{client_id} + # identifier_uri="api://your-api-id", + # Optional: request additional upstream scopes in the authorize request + # additional_authorize_scopes=["User.Read", "offline_access", "openid", "email"], + # redirect_path="/auth/callback" # Default value, customize if needed + # base_authority="login.microsoftonline.us" # For Azure Government (default: login.microsoftonline.com) +) + +mcp = FastMCP(name="Azure Secured App", auth=auth_provider) + +# Add a protected tool to test authentication +@mcp.tool +async def get_user_info() -> dict: + """Returns information about the authenticated Azure user.""" + from fastmcp.server.dependencies import get_access_token + + token = get_access_token() + # The AzureProvider stores user data in token claims + return { + "azure_id": token.claims.get("sub"), + "email": token.claims.get("email"), + "name": token.claims.get("name"), + "job_title": token.claims.get("job_title"), + "office_location": token.claims.get("office_location") + } +``` + + +**Important**: The `tenant_id` parameter is **REQUIRED**. Azure no longer supports using "common" for new applications due to security requirements. You must use one of: + +- **Your specific tenant ID**: Found in Azure Portal (e.g., `08541b6e-646d-43de-a0eb-834e6713d6d5`) +- **"organizations"**: For work and school accounts only +- **"consumers"**: For personal Microsoft accounts only + +Using your specific tenant ID is recommended for better security and control. + + + +**Important**: The `required_scopes` parameter is **REQUIRED** and must include at least one scope. Azure's OAuth API requires the `scope` parameter in all authorization requests - you cannot authenticate without specifying at least one scope. Use the unprefixed scope names from your Azure App registration (e.g., `["read", "write"]`). These scopes must be created under **Expose an API** in your App registration. + + +### Scope Handling + +FastMCP automatically prefixes `required_scopes` with your `identifier_uri` (e.g., `api://your-client-id`) since these are your custom API scopes. Scopes in `additional_authorize_scopes` are sent as-is since they target external resources like Microsoft Graph. + +**`required_scopes`** — Your custom API scopes, defined in Azure "Expose an API": + +| You write | Sent to Azure | Validated on tokens | +|-----------|---------------|---------------------| +| `mcp-read` | `api://xxx/mcp-read` | ✓ | +| `my.scope` | `api://xxx/my.scope` | ✓ | +| `openid` | `openid` | ✗ (OIDC scope) | +| `api://xxx/read` | `api://xxx/read` | ✓ | + +**`additional_authorize_scopes`** — External scopes (e.g., Microsoft Graph) for server-side use: + +| You write | Sent to Azure | Validated on tokens | +|-----------|---------------|---------------------| +| `User.Read` | `User.Read` | ✗ | +| `Mail.Send` | `Mail.Send` | ✗ | + + +**Why aren't `additional_authorize_scopes` validated?** Azure issues separate tokens per resource. The access token FastMCP receives is for *your API*—Graph scopes aren't in its `scp` claim. To call Graph APIs, your server uses the upstream Azure token in an on-behalf-of (OBO) flow. + + + +OIDC scopes (`openid`, `profile`, `email`, `offline_access`) are never prefixed and excluded from validation because Azure doesn't include them in access token `scp` claims. + + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by Azure OAuth authentication. + +### Testing with a Client + +Create a test client that authenticates with your Azure-protected server: + +```python test_client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle Azure OAuth + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open Azure login in your browser + print("✓ Authenticated with Azure!") + + # Test the protected tool + result = await client.call_tool("get_user_info") + print(f"Azure user: {result['email']}") + print(f"Name: {result['name']}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to Microsoft's authorization page +2. Sign in with your Microsoft account (work, school, or personal based on your tenant configuration) +3. Grant the requested permissions +4. After authorization, you'll be redirected back +5. The client receives the token and can make authenticated requests + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.azure import AzureProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Production setup with encrypted persistent token storage +auth_provider = AzureProvider( + client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149", + client_secret="your-client-secret", + tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", + base_url="https://your-production-domain.com", + required_scopes=["your-scope"], + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production Azure App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + +## Environment Variables + + + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the Azure provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.azure.AzureProvider` to use Azure authentication. + + + +### Azure-Specific Configuration + +These environment variables provide default values for the Azure provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Azure App registration Client ID (e.g., `835f09b6-0f0f-40cc-85cb-f32c5829a149`) + + + +Your Azure App registration Client Secret + + + +Your Azure tenant ID (specific ID, "organizations", or "consumers") + + +This is **REQUIRED**. Find your tenant ID in Azure Portal under Microsoft Entra ID → Overview. + + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +Redirect path configured in your Azure App registration + + + +Comma-, space-, or JSON-separated list of required scopes for your API (at least one scope required). These are validated on tokens and used as defaults if the client does not request specific scopes. Use unprefixed scope names from your Azure App registration (e.g., `read,write`). + +You can include standard OIDC scopes (`openid`, `profile`, `email`, `offline_access`) in `required_scopes`. FastMCP automatically handles them correctly: they're sent to Azure unprefixed and excluded from token validation (since Azure doesn't include OIDC scopes in access token `scp` claims). + + +Azure's OAuth API requires the `scope` parameter - you must provide at least one scope. + + + + +Comma-, space-, or JSON-separated list of additional scopes to include in the authorization request without prefixing. Use this to request upstream scopes such as Microsoft Graph permissions. These are not used for token validation. + + + +Application ID URI used to prefix scopes during authorization. + + + +Azure authority base URL. Override this to use Azure Government: + +- `login.microsoftonline.com` - Azure Public Cloud (default) +- `login.microsoftonline.us` - Azure Government + +This setting affects all Azure OAuth endpoints (authorization, token, issuer, JWKS). + + + +Example `.env` file: +```bash +# Use the Azure provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.azure.AzureProvider + +# Azure OAuth credentials +FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID=835f09b6-0f0f-40cc-85cb-f32c5829a149 +FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET=your-client-secret-here +FASTMCP_SERVER_AUTH_AZURE_TENANT_ID=08541b6e-646d-43de-a0eb-834e6713d6d5 +FASTMCP_SERVER_AUTH_AZURE_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES=read,write +# Optional custom API configuration +# FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI=api://your-api-id +# Request additional upstream scopes (optional) +# FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES=User.Read,Mail.Read +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="Azure Secured App") + +@mcp.tool +async def protected_tool(query: str) -> str: + """A tool that requires Azure authentication to access.""" + # Your tool implementation here + return f"Processing authenticated request: {query}" +``` + diff --git a/docs/v2/integrations/chatgpt.mdx b/docs/v2/integrations/chatgpt.mdx new file mode 100644 index 000000000..18028c69d --- /dev/null +++ b/docs/v2/integrations/chatgpt.mdx @@ -0,0 +1,157 @@ +--- +title: ChatGPT 🤝 FastMCP +sidebarTitle: ChatGPT +description: Connect FastMCP servers to ChatGPT in Chat and Deep Research modes +icon: message-smile +tag: NEW +--- + +ChatGPT supports MCP servers through remote HTTP connections in two modes: **Chat mode** for interactive conversations and **Deep Research mode** for comprehensive information retrieval. + + +**Developer Mode Required for Chat Mode**: To use MCP servers in regular ChatGPT conversations, you must first enable Developer Mode in your ChatGPT settings. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users. + + + +OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Learn more from their [MCP documentation](https://platform.openai.com/docs/mcp) and [Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode). + + +## Build a Server + +First, let's create a simple FastMCP server: + +```python server.py +from fastmcp import FastMCP +import random + +mcp = FastMCP("Demo Server") + +@mcp.tool +def roll_dice(sides: int = 6) -> int: + """Roll a dice with the specified number of sides.""" + return random.randint(1, sides) + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + +### Deploy Your Server + +Your server must be accessible from the internet. For development, use `ngrok`: + + +```bash Terminal 1 +python server.py +``` + +```bash Terminal 2 +ngrok http 8000 +``` + + +Note your public URL (e.g., `https://abc123.ngrok.io`) for the next steps. + +## Chat Mode + +Chat mode lets you use MCP tools directly in ChatGPT conversations. See [OpenAI's Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode) for the latest requirements. + +### Add to ChatGPT + +#### 1. Enable Developer Mode + +1. Open ChatGPT and go to **Settings** → **Connectors** +2. Under **Advanced**, toggle **Developer Mode** to enabled + +#### 2. Create Connector + +1. In **Settings** → **Connectors**, click **Create** +2. Enter: + - **Name**: Your server name + - **Server URL**: `https://your-server.ngrok.io/mcp/` +3. Check **I trust this provider** +4. Add authentication if needed +5. Click **Create** + + +**Without Developer Mode**: If you don't have search/fetch tools, ChatGPT will reject the server. With Developer Mode enabled, you don't need search/fetch tools for Chat mode. + + +#### 3. Use in Chat + +1. Start a new chat +2. Click the **+** button → **More** → **Developer Mode** +3. **Enable your MCP server connector** (required - the connector must be explicitly added to each chat) +4. Now you can use your tools: + +Example usage: +- "Roll a 20-sided dice" +- "Roll dice" (uses default 6 sides) + + +The connector must be explicitly enabled in each chat session through Developer Mode. Once added, it remains active for the entire conversation. + + +### Skip Confirmations + +Use `annotations={"readOnlyHint": True}` to skip confirmation prompts for read-only tools: + +```python +@mcp.tool(annotations={"readOnlyHint": True}) +def get_status() -> str: + """Check system status.""" + return "All systems operational" + +@mcp.tool() # No annotation - ChatGPT may ask for confirmation +def delete_item(id: str) -> str: + """Delete an item.""" + return f"Deleted {id}" +``` + +## Deep Research Mode + +Deep Research mode provides systematic information retrieval with citations. See [OpenAI's MCP documentation](https://platform.openai.com/docs/mcp) for the latest Deep Research specifications. + + +**Search and Fetch Required**: Without Developer Mode, ChatGPT will reject any server that doesn't have both `search` and `fetch` tools. Even in Developer Mode, Deep Research only uses these two tools. + + +### Tool Implementation + +Deep Research tools must follow this pattern: + +```python +@mcp.tool() +def search(query: str) -> dict: + """ + Search for records matching the query. + Must return {"ids": [list of string IDs]} + """ + # Your search logic + matching_ids = ["id1", "id2", "id3"] + return {"ids": matching_ids} + +@mcp.tool() +def fetch(id: str) -> dict: + """ + Fetch a complete record by ID. + Return the full record data for ChatGPT to analyze. + """ + # Your fetch logic + return { + "id": id, + "title": "Record Title", + "content": "Full record content...", + "metadata": {"author": "Jane Doe", "date": "2024"} + } +``` + +### Using Deep Research + +1. Ensure your server is added to ChatGPT's connectors (same as Chat mode) +2. Start a new chat +3. Click **+** → **Deep Research** +4. Select your MCP server as a source +5. Ask research questions + +ChatGPT will use your `search` and `fetch` tools to find and cite relevant information. + diff --git a/docs/v2/integrations/claude-code.mdx b/docs/v2/integrations/claude-code.mdx new file mode 100644 index 000000000..2b04c10a8 --- /dev/null +++ b/docs/v2/integrations/claude-code.mdx @@ -0,0 +1,177 @@ +--- +title: Claude Code 🤝 FastMCP +sidebarTitle: Claude Code +description: Install and use FastMCP servers in Claude Code +icon: message-smile +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" +import { LocalFocusTip } from "/snippets/local-focus.mdx" + + + +Claude Code supports MCP servers through multiple transport methods including STDIO, SSE, and HTTP, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers. + +## Requirements + +This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly using Claude Code's built-in MCP management commands. + +## Create a Server + +The examples in this guide will use the following simple dice-rolling server, saved as `server.py`. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run() +``` + +## Install the Server + +### FastMCP CLI + + +The easiest way to install a FastMCP server in Claude Code is using the `fastmcp install claude-code` command. This automatically handles the configuration, dependency management, and calls Claude Code's built-in MCP management system. + +```bash +fastmcp install claude-code server.py +``` + +The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file: + +```bash +# These are equivalent if your server object is named 'mcp' +fastmcp install claude-code server.py +fastmcp install claude-code server.py:mcp + +# Use explicit object name if your server has a different name +fastmcp install claude-code server.py:my_custom_server +``` + +The command will automatically configure the server with Claude Code's `claude mcp add` command. + +#### Dependencies + +FastMCP provides flexible dependency management options for your Claude Code servers: + +**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times: + +```bash +fastmcp install claude-code server.py --with pandas --with requests +``` + +**Requirements file**: If you maintain a `requirements.txt` file with all your dependencies, use `--with-requirements` to install them: + +```bash +fastmcp install claude-code server.py --with-requirements requirements.txt +``` + +**Editable packages**: For local packages under development, use `--with-editable` to install them in editable mode: + +```bash +fastmcp install claude-code server.py --with-editable ./my-local-package +``` + +Alternatively, you can use a `fastmcp.json` configuration file (recommended): + +```json fastmcp.json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "dependencies": ["pandas", "requests"] + } +} +``` + + +#### Python Version and Project Configuration + +Control the Python environment for your server with these options: + +**Python version**: Use `--python` to specify which Python version your server requires. This ensures compatibility when your server needs specific Python features: + +```bash +fastmcp install claude-code server.py --python 3.11 +``` + +**Project directory**: Use `--project` to run your server within a specific project context. This tells `uv` to use the project's configuration files and virtual environment: + +```bash +fastmcp install claude-code server.py --project /path/to/my-project +``` + +#### Environment Variables + +If your server needs environment variables (like API keys), you must include them: + +```bash +fastmcp install claude-code server.py --server-name "Weather Server" \ + --env API_KEY=your-api-key \ + --env DEBUG=true +``` + +Or load them from a `.env` file: + +```bash +fastmcp install claude-code server.py --server-name "Weather Server" --env-file .env +``` + + +**Claude Code must be installed**. The integration looks for the Claude Code CLI at the default installation location (`~/.claude/local/claude`) and uses the `claude mcp add` command to register servers. + + +### Manual Configuration + +For more control over the configuration, you can manually use Claude Code's built-in MCP management commands. This gives you direct control over how your server is launched: + +```bash +# Add a server with custom configuration +claude mcp add dice-roller -- uv run --with fastmcp fastmcp run server.py + +# Add with environment variables +claude mcp add weather-server -e API_KEY=secret -e DEBUG=true -- uv run --with fastmcp fastmcp run server.py + +# Add with specific scope (local, user, or project) +claude mcp add my-server --scope user -- uv run --with fastmcp fastmcp run server.py +``` + +You can also manually specify Python versions and project directories in your Claude Code commands: + +```bash +# With specific Python version +claude mcp add ml-server -- uv run --python 3.11 --with fastmcp fastmcp run server.py + +# Within a project directory +claude mcp add project-server -- uv run --project /path/to/project --with fastmcp fastmcp run server.py +``` + +## Using the Server + +Once your server is installed, you can start using your FastMCP server with Claude Code. + +Try asking Claude something like: + +> "Roll some dice for me" + +Claude will automatically detect your `roll_dice` tool and use it to fulfill your request, returning something like: + +> I'll roll some dice for you! Here are your results: [4, 2, 6] +> +> You rolled three dice and got a 4, a 2, and a 6! + +Claude Code can now access all the tools, resources, and prompts you've defined in your FastMCP server. + +If your server provides resources, you can reference them with `@` mentions using the format `@server:protocol://resource/path`. If your server provides prompts, you can use them as slash commands with `/mcp__servername__promptname`. \ No newline at end of file diff --git a/docs/v2/integrations/claude-desktop.mdx b/docs/v2/integrations/claude-desktop.mdx new file mode 100644 index 000000000..3158a005a --- /dev/null +++ b/docs/v2/integrations/claude-desktop.mdx @@ -0,0 +1,298 @@ +--- +title: Claude Desktop 🤝 FastMCP +sidebarTitle: Claude Desktop +description: Connect FastMCP servers to Claude Desktop +icon: message-smile +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" +import { LocalFocusTip } from "/snippets/local-focus.mdx" + + + +Claude Desktop supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers. + + +Remote MCP server support is currently in beta and available for users on Claude Pro, Max, Team, and Enterprise plans (as of June 2025). Most users will still need to use local STDIO connections. + + + +This guide focuses specifically on using FastMCP servers with Claude Desktop. For general Claude Desktop MCP setup and official examples, see the [official Claude Desktop quickstart guide](https://modelcontextprotocol.io/quickstart/user). + + + +## Requirements + +Claude Desktop traditionally requires MCP servers to run locally using STDIO transport, where your server communicates with Claude through standard input/output rather than HTTP. However, users on certain plans now have access to remote server support as well. + + +If you don't have access to remote server support or need to connect to remote servers, you can create a **proxy server** that runs locally via STDIO and forwards requests to remote HTTP servers. See the [Proxy Servers](#proxy-servers) section below. + + +## Create a Server + +The examples in this guide will use the following simple dice-rolling server, saved as `server.py`. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run() +``` + +## Install the Server + +### FastMCP CLI + + +The easiest way to install a FastMCP server in Claude Desktop is using the `fastmcp install claude-desktop` command. This automatically handles the configuration and dependency management. + + +Prior to version 2.10.3, Claude Desktop could be managed by running `fastmcp install ` without specifying the client. + + +```bash +fastmcp install claude-desktop server.py +``` + +The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file: + +```bash +# These are equivalent if your server object is named 'mcp' +fastmcp install claude-desktop server.py +fastmcp install claude-desktop server.py:mcp + +# Use explicit object name if your server has a different name +fastmcp install claude-desktop server.py:my_custom_server +``` + +After installation, restart Claude Desktop completely. You should see a hammer icon (🔨) in the bottom left of the input box, indicating that MCP tools are available. + +#### Dependencies + +FastMCP provides several ways to manage your server's dependencies when installing in Claude Desktop: + +**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times: + +```bash +fastmcp install claude-desktop server.py --with pandas --with requests +``` + +**Requirements file**: If you have a `requirements.txt` file listing all your dependencies, use `--with-requirements` to install them all at once: + +```bash +fastmcp install claude-desktop server.py --with-requirements requirements.txt +``` + +**Editable packages**: For local packages in development, use `--with-editable` to install them in editable mode: + +```bash +fastmcp install claude-desktop server.py --with-editable ./my-local-package +``` + +Alternatively, you can use a `fastmcp.json` configuration file (recommended): + +```json fastmcp.json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "dependencies": ["pandas", "requests"] + } +} +``` + + +#### Python Version and Project Directory + +FastMCP allows you to control the Python environment for your server: + +**Python version**: Use `--python` to specify which Python version your server should run with. This is particularly useful when your server requires a specific Python version: + +```bash +fastmcp install claude-desktop server.py --python 3.11 +``` + +**Project directory**: Use `--project` to run your server within a specific project directory. This ensures that `uv` will discover all `pyproject.toml`, `uv.toml`, and `.python-version` files from that project: + +```bash +fastmcp install claude-desktop server.py --project /path/to/my-project +``` + +When you specify a project directory, all relative paths in your server will be resolved from that directory, and the project's virtual environment will be used. + +#### Environment Variables + + +Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs. + + +If your server needs environment variables (like API keys), you must include them: + +```bash +fastmcp install claude-desktop server.py --server-name "Weather Server" \ + --env API_KEY=your-api-key \ + --env DEBUG=true +``` + +Or load them from a `.env` file: + +```bash +fastmcp install claude-desktop server.py --server-name "Weather Server" --env-file .env +``` + +- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies. +- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop. + + + +### Manual Configuration + +For more control over the configuration, you can manually edit Claude Desktop's configuration file. You can open the configuration file from Claude's developer settings, or find it in the following locations: +- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` + +The configuration file is a JSON object with a `mcpServers` key, which contains the configuration for each MCP server. + +```json +{ + "mcpServers": { + "dice-roller": { + "command": "python", + "args": ["path/to/your/server.py"] + } + } +} +``` + +After updating the configuration file, restart Claude Desktop completely. Look for the hammer icon (🔨) to confirm your server is loaded. + +#### Dependencies + +If your server has dependencies, you can use `uv` or another package manager to set up the environment. + + +When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration uses `uv run` to create an isolated environment with your specified packages: + +```json +{ + "mcpServers": { + "dice-roller": { + "command": "uv", + "args": [ + "run", + "--with", "fastmcp", + "--with", "pandas", + "--with", "requests", + "fastmcp", + "run", + "path/to/your/server.py" + ] + } + } +} +``` + +You can also manually specify Python versions and project directories in your configuration. Add `--python` to use a specific Python version, or `--project` to run within a project directory: + +```json +{ + "mcpServers": { + "dice-roller": { + "command": "uv", + "args": [ + "run", + "--python", "3.11", + "--project", "/path/to/project", + "--with", "fastmcp", + "fastmcp", + "run", + "path/to/your/server.py" + ] + } + } +} +``` + +The order of arguments matters: Python version and project settings come before package specifications, which come before the actual command to run. + + +- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies. +- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop. + + +#### Environment Variables + +You can also specify environment variables in the configuration: + +```json +{ + "mcpServers": { + "weather-server": { + "command": "python", + "args": ["path/to/weather_server.py"], + "env": { + "API_KEY": "your-api-key", + "DEBUG": "true" + } + } + } +} +``` + +Claude Desktop runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs. + + + +## Remote Servers + + +Users on Claude Pro, Max, Team, and Enterprise plans have first-class remote server support via integrations. For other users, or as an alternative approach, FastMCP can create a proxy server that forwards requests to a remote HTTP server. You can install the proxy server in Claude Desktop. + +Create a proxy server that connects to a remote HTTP server: + +```python proxy_server.py +from fastmcp import FastMCP + +# Create a proxy to a remote server +proxy = FastMCP.as_proxy( + "https://example.com/mcp/sse", + name="Remote Server Proxy" +) + +if __name__ == "__main__": + proxy.run() # Runs via STDIO for Claude Desktop +``` + +### Authentication + +For authenticated remote servers, create an authenticated client following the guidance in the [client auth documentation](/clients/auth/bearer) and pass it to the proxy: + +```python auth_proxy_server.py {7} +from fastmcp import FastMCP, Client +from fastmcp.client.auth import BearerAuth + +# Create authenticated client +client = Client( + "https://api.example.com/mcp/sse", + auth=BearerAuth(token="your-access-token") +) + +# Create proxy using the authenticated client +proxy = FastMCP.as_proxy(client, name="Authenticated Proxy") + +if __name__ == "__main__": + proxy.run() +``` + diff --git a/docs/v2/integrations/cursor.mdx b/docs/v2/integrations/cursor.mdx new file mode 100644 index 000000000..5b98cbd1a --- /dev/null +++ b/docs/v2/integrations/cursor.mdx @@ -0,0 +1,284 @@ +--- +title: Cursor 🤝 FastMCP +sidebarTitle: Cursor +description: Install and use FastMCP servers in Cursor +icon: message-smile +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" +import { LocalFocusTip } from "/snippets/local-focus.mdx" + + + +Cursor supports MCP servers through multiple transport methods including STDIO, SSE, and Streamable HTTP, allowing you to extend Cursor's AI assistant with custom tools, resources, and prompts from your FastMCP servers. + +## Requirements + +This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly in Cursor's settings. + +## Create a Server + +The examples in this guide will use the following simple dice-rolling server, saved as `server.py`. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run() +``` + +## Install the Server + +### FastMCP CLI + + +The easiest way to install a FastMCP server in Cursor is using the `fastmcp install cursor` command. This automatically handles the configuration, dependency management, and opens Cursor with a deeplink to install the server. + +```bash +fastmcp install cursor server.py +``` + +#### Workspace Installation + + +By default, FastMCP installs servers globally for Cursor. You can also install servers to project-specific workspaces using the `--workspace` flag: + +```bash +# Install to current directory's .cursor/ folder +fastmcp install cursor server.py --workspace . + +# Install to specific workspace +fastmcp install cursor server.py --workspace /path/to/project +``` + +This creates a `.cursor/mcp.json` configuration file in the specified workspace directory, allowing different projects to have their own MCP server configurations. + +The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file: + +```bash +# These are equivalent if your server object is named 'mcp' +fastmcp install cursor server.py +fastmcp install cursor server.py:mcp + +# Use explicit object name if your server has a different name +fastmcp install cursor server.py:my_custom_server +``` + +After running the command, Cursor will open automatically and prompt you to install the server. The command will be `uv`, which is expected as this is a Python STDIO server. Click "Install" to confirm: + +![Cursor install prompt](./cursor-install-mcp.png) + +#### Dependencies + +FastMCP offers multiple ways to manage dependencies for your Cursor servers: + +**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times: + +```bash +fastmcp install cursor server.py --with pandas --with requests +``` + +**Requirements file**: For projects with a `requirements.txt` file, use `--with-requirements` to install all dependencies at once: + +```bash +fastmcp install cursor server.py --with-requirements requirements.txt +``` + +**Editable packages**: When developing local packages, use `--with-editable` to install them in editable mode: + +```bash +fastmcp install cursor server.py --with-editable ./my-local-package +``` + +Alternatively, you can use a `fastmcp.json` configuration file (recommended): + +```json fastmcp.json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "dependencies": ["pandas", "requests"] + } +} +``` + + +#### Python Version and Project Configuration + +Control your server's Python environment with these options: + +**Python version**: Use `--python` to specify which Python version your server should use. This is essential when your server requires specific Python features: + +```bash +fastmcp install cursor server.py --python 3.11 +``` + +**Project directory**: Use `--project` to run your server within a specific project context. This ensures `uv` discovers all project configuration files and uses the correct virtual environment: + +```bash +fastmcp install cursor server.py --project /path/to/my-project +``` + +#### Environment Variables + + +Cursor runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs. + + +If your server needs environment variables (like API keys), you must include them: + +```bash +fastmcp install cursor server.py --server-name "Weather Server" \ + --env API_KEY=your-api-key \ + --env DEBUG=true +``` + +Or load them from a `.env` file: + +```bash +fastmcp install cursor server.py --server-name "Weather Server" --env-file .env +``` + + +**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies. + + +### Generate MCP JSON + + +**Use the first-class integration above for the best experience.** The MCP JSON generation is useful for advanced use cases, manual configuration, or integration with other tools. + + +You can generate MCP JSON configuration for manual use: + +```bash +# Generate configuration and output to stdout +fastmcp install mcp-json server.py --server-name "Dice Roller" --with pandas + +# Copy configuration to clipboard for easy pasting +fastmcp install mcp-json server.py --server-name "Dice Roller" --copy +``` + +This generates the standard `mcpServers` configuration format that can be used with any MCP-compatible client. + +### Manual Configuration + +For more control over the configuration, you can manually edit Cursor's configuration file. The configuration file is located at: +- **All platforms**: `~/.cursor/mcp.json` + +The configuration file is a JSON object with a `mcpServers` key, which contains the configuration for each MCP server. + +```json +{ + "mcpServers": { + "dice-roller": { + "command": "python", + "args": ["path/to/your/server.py"] + } + } +} +``` + +After updating the configuration file, your server should be available in Cursor. + +#### Dependencies + +If your server has dependencies, you can use `uv` or another package manager to set up the environment. + +When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration should use `uv run` to create an isolated environment with your specified packages: + +```json +{ + "mcpServers": { + "dice-roller": { + "command": "uv", + "args": [ + "run", + "--with", "fastmcp", + "--with", "pandas", + "--with", "requests", + "fastmcp", + "run", + "path/to/your/server.py" + ] + } + } +} +``` + +You can also manually specify Python versions and project directories in your configuration: + +```json +{ + "mcpServers": { + "dice-roller": { + "command": "uv", + "args": [ + "run", + "--python", "3.11", + "--project", "/path/to/project", + "--with", "fastmcp", + "fastmcp", + "run", + "path/to/your/server.py" + ] + } + } +} +``` + +Note that the order of arguments is important: Python version and project settings should come before package specifications. + + +**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies. + + +#### Environment Variables + +You can also specify environment variables in the configuration: + +```json +{ + "mcpServers": { + "weather-server": { + "command": "python", + "args": ["path/to/weather_server.py"], + "env": { + "API_KEY": "your-api-key", + "DEBUG": "true" + } + } + } +} +``` + + +Cursor runs servers in a completely isolated environment with no access to your shell environment or locally installed applications. You must explicitly pass any environment variables your server needs. + + +## Using the Server + +Once your server is installed, you can start using your FastMCP server with Cursor's AI assistant. + +Try asking Cursor something like: + +> "Roll some dice for me" + +Cursor will automatically detect your `roll_dice` tool and use it to fulfill your request, returning something like: + +> 🎲 Here are your dice rolls: 4, 6, 4 +> +> You rolled 3 dice with a total of 14! The 6 was a nice high roll there! + +The AI assistant can now access all the tools, resources, and prompts you've defined in your FastMCP server. diff --git a/docs/v2/integrations/descope.mdx b/docs/v2/integrations/descope.mdx new file mode 100644 index 000000000..81eb0642b --- /dev/null +++ b/docs/v2/integrations/descope.mdx @@ -0,0 +1,146 @@ +--- +title: Descope 🤝 FastMCP +sidebarTitle: Descope +description: Secure your FastMCP server with Descope +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +This guide shows you how to secure your FastMCP server using [**Descope**](https://www.descope.com), a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where Descope handles user login and your FastMCP server validates the tokens. + +## Configuration + +### Prerequisites + +Before you begin, you will need: + +1. To [sign up](https://www.descope.com/sign-up) for a Free Forever Descope account +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:3000`) + +### Step 1: Configure Descope + + + + 1. Go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console, and create a new MCP Server. + 2. Give the MCP server a name and description. + 3. Ensure that **Dynamic Client Registration (DCR)** is enabled. Then click **Create**. + 4. Once you've created the MCP Server, note your Well-Known URL. + + + + DCR is required for FastMCP clients to automatically register with your authentication server. + + + + + Save your Well-Known URL from [MCP Server Settings](https://app.descope.com/mcp-servers): + ``` + Well-Known URL: https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration + ``` + + + +### Step 2: Environment Setup + +Create a `.env` file with your Descope configuration: + +```bash +DESCOPE_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration # Your Descope Well-Known URL +SERVER_URL=http://localhost:3000 # Your server's base URL +``` + +### Step 3: FastMCP Configuration + +Create your FastMCP server file and use the DescopeProvider to handle all the OAuth integration automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.descope import DescopeProvider + +# The DescopeProvider automatically discovers Descope endpoints +# and configures JWT token validation +auth_provider = DescopeProvider( + config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL + base_url=SERVER_URL, # Your server's public URL +) + +# Create FastMCP server with auth +mcp = FastMCP(name="My Descope Protected Server", auth=auth_provider) + +``` + +## Testing + +To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the environment variables with your actual values!), you can run the following command: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Now, you can use a FastMCP client to test that you can reach your server after authenticating: + +```python +from fastmcp import Client +import asyncio + +async def main(): + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + assert await client.ping() + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Environment Variables + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the Descope provider to be used automatically without explicitly instantiating it in code. + + + + Set to `fastmcp.server.auth.providers.descope.DescopeProvider` to use + Descope authentication. + + + +### Descope-Specific Configuration + +These environment variables provide default values for the Descope provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Well-Known URL from the [Descope Console](https://app.descope.com/mcp-servers) + + + + Public URL of your FastMCP server (e.g., `https://your-server.com` or + `http://localhost:8000` for development) + + + +Example `.env` file: + +```bash +# Use the Descope provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.descope.DescopeProvider + +# Descope configuration +FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration +FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL=https://your-server.com +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="My Descope Protected Server") +``` diff --git a/docs/v2/integrations/discord.mdx b/docs/v2/integrations/discord.mdx new file mode 100644 index 000000000..4e3670358 --- /dev/null +++ b/docs/v2/integrations/discord.mdx @@ -0,0 +1,255 @@ +--- +title: Discord OAuth 🤝 FastMCP +sidebarTitle: Discord +description: Secure your FastMCP server with Discord OAuth +icon: discord +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **Discord OAuth**. Since Discord doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Discord's traditional OAuth with MCP's authentication requirements. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. A **[Discord Account](https://discord.com/)** with access to create applications +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Create a Discord Application + +Create an application in the Discord Developer Portal to get the credentials needed for authentication: + + + + Go to the [Discord Developer Portal](https://discord.com/developers/applications). + + Click **"New Application"** and give it a name users will recognize (e.g., "My FastMCP Server"). + + + + In the left sidebar, click **"OAuth2"**. + + In the **Redirects** section, click **"Add Redirect"** and enter your callback URL: + - For development: `http://localhost:8000/auth/callback` + - For production: `https://your-domain.com/auth/callback` + + + The redirect URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. Discord allows `http://localhost` URLs for development. For production, use HTTPS. + + + + + On the same OAuth2 page, you'll find: + + - **Client ID**: A numeric string like `12345` + - **Client Secret**: Click "Reset Secret" to generate one + + + Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `DiscordProvider`, which handles Discord's OAuth flow automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.discord import DiscordProvider + +auth_provider = DiscordProvider( + client_id="12345", # Your Discord Application Client ID + client_secret="your-client-secret", # Your Discord OAuth Client Secret + base_url="http://localhost:8000", # Must match your OAuth configuration +) + +mcp = FastMCP(name="Discord Secured App", auth=auth_provider) + +@mcp.tool +async def get_user_info() -> dict: + """Returns information about the authenticated Discord user.""" + from fastmcp.server.dependencies import get_access_token + + token = get_access_token() + return { + "discord_id": token.claims.get("sub"), + "username": token.claims.get("username"), + "avatar": token.claims.get("avatar"), + } +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by Discord OAuth authentication. + +### Testing with a Client + +Create a test client that authenticates with your Discord-protected server: + +```python test_client.py +from fastmcp import Client +import asyncio + +async def main(): + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + print("✓ Authenticated with Discord!") + + result = await client.call_tool("get_user_info") + print(f"Discord user: {result['username']}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to Discord's authorization page +2. Sign in with your Discord account and authorize the app +3. After authorization, you'll be redirected back +4. The client receives the token and can make authenticated requests + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Discord Scopes + +Discord OAuth supports several scopes for accessing different types of user data: + +| Scope | Description | +|-------|-------------| +| `identify` | Access username, avatar, and discriminator (default) | +| `email` | Access the user's email address | +| `guilds` | Access the user's list of servers | +| `guilds.join` | Ability to add the user to a server | + +To request additional scopes: + +```python +auth_provider = DiscordProvider( + client_id="...", + client_secret="...", + base_url="http://localhost:8000", + required_scopes=["identify", "email"], +) +``` + +## Production Configuration + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.discord import DiscordProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +auth_provider = DiscordProvider( + client_id="12345", + client_secret=os.environ["DISCORD_CLIENT_SECRET"], + base_url="https://your-production-domain.com", + + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production Discord App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + +## Environment Variables + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the Discord provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.discord.DiscordProvider` to use Discord authentication. + + + +### Discord-Specific Configuration + +These environment variables provide default values for the Discord provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Discord Application Client ID (e.g., `12345`) + + + +Your Discord OAuth Client Secret + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +Redirect path configured in your Discord OAuth settings + + + +Comma-, space-, or JSON-separated list of required Discord scopes (e.g., `identify,email` or `["identify","email"]`) + + + +HTTP request timeout for Discord API calls + + + +Example `.env` file: +```bash +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.discord.DiscordProvider + +FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID=12345 +FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET=your-client-secret +FASTMCP_SERVER_AUTH_DISCORD_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_DISCORD_REQUIRED_SCOPES=identify,email +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +mcp = FastMCP(name="Discord Secured App") + +@mcp.tool +async def protected_tool(query: str) -> str: + """A tool that requires Discord authentication to access.""" + return f"Processing authenticated request: {query}" +``` diff --git a/docs/v2/integrations/eunomia-authorization.mdx b/docs/v2/integrations/eunomia-authorization.mdx new file mode 100644 index 000000000..bccfbccdb --- /dev/null +++ b/docs/v2/integrations/eunomia-authorization.mdx @@ -0,0 +1,129 @@ +--- +title: Eunomia Authorization 🤝 FastMCP +sidebarTitle: Eunomia Auth +description: Add policy-based authorization to your FastMCP servers with Eunomia +icon: shield-check +--- + +Add **policy-based authorization** to your FastMCP servers with one-line code addition with the **[Eunomia][eunomia-github] authorization middleware**. + +Control which tools, resources and prompts MCP clients can view and execute on your server. Define dynamic JSON-based policies and obtain a comprehensive audit log of all access attempts and violations. + +## How it Works + +Exploiting FastMCP's [Middleware][fastmcp-middleare], the Eunomia middleware intercepts all MCP requests to your server and automatically maps MCP methods to authorization checks. + +### Listing Operations + +The middleware behaves as a filter for listing operations (`tools/list`, `resources/list`, `prompts/list`), hiding to the client components that are not authorized by the defined policies. + +```mermaid +sequenceDiagram + participant MCPClient as MCP Client + participant EunomiaMiddleware as Eunomia Middleware + participant MCPServer as FastMCP Server + participant EunomiaServer as Eunomia Server + + MCPClient->>EunomiaMiddleware: MCP Listing Request (e.g., tools/list) + EunomiaMiddleware->>MCPServer: MCP Listing Request + MCPServer-->>EunomiaMiddleware: MCP Listing Response + EunomiaMiddleware->>EunomiaServer: Authorization Checks + EunomiaServer->>EunomiaMiddleware: Authorization Decisions + EunomiaMiddleware-->>MCPClient: Filtered MCP Listing Response +``` + +### Execution Operations + +The middleware behaves as a firewall for execution operations (`tools/call`, `resources/read`, `prompts/get`), blocking operations that are not authorized by the defined policies. + +```mermaid +sequenceDiagram + participant MCPClient as MCP Client + participant EunomiaMiddleware as Eunomia Middleware + participant MCPServer as FastMCP Server + participant EunomiaServer as Eunomia Server + + MCPClient->>EunomiaMiddleware: MCP Execution Request (e.g., tools/call) + EunomiaMiddleware->>EunomiaServer: Authorization Check + EunomiaServer->>EunomiaMiddleware: Authorization Decision + EunomiaMiddleware-->>MCPClient: MCP Unauthorized Error (if denied) + EunomiaMiddleware->>MCPServer: MCP Execution Request (if allowed) + MCPServer-->>EunomiaMiddleware: MCP Execution Response (if allowed) + EunomiaMiddleware-->>MCPClient: MCP Execution Response (if allowed) +``` + +## Add Authorization to Your Server + + +Eunomia is an AI-specific authorization server that handles policy decisions. The server runs embedded within your MCP server by default for a zero-effort configuration, but can alternatively be run remotely for centralized policy decisions. + + + +### Create a Server with Authorization + +First, install the `eunomia-mcp` package: + +```bash +pip install eunomia-mcp +``` + +Then create a FastMCP server and add the Eunomia middleware in one line: + +```python server.py +from fastmcp import FastMCP +from eunomia_mcp import create_eunomia_middleware + +# Create your FastMCP server +mcp = FastMCP("Secure MCP Server 🔒") + +@mcp.tool() +def add(a: int, b: int) -> int: + """Add two numbers""" + return a + b + +# Add middleware to your server +middleware = create_eunomia_middleware(policy_file="mcp_policies.json") +mcp.add_middleware(middleware) + +if __name__ == "__main__": + mcp.run() +``` + +### Configure Access Policies + +Use the `eunomia-mcp` CLI in your terminal to manage your authorization policies: + +```bash +# Create a default policy file +eunomia-mcp init + +# Or create a policy file customized for your FastMCP server +eunomia-mcp init --custom-mcp "app.server:mcp" +``` + +This creates `mcp_policies.json` file that you can further edit to your access control needs. + +```bash +# Once edited, validate your policy file +eunomia-mcp validate mcp_policies.json +``` + +### Run the Server + +Start your FastMCP server normally: + +```bash +python server.py +``` + +The middleware will now intercept all MCP requests and check them against your policies. Requests include agent identification through headers like `X-Agent-ID`, `X-User-ID`, `User-Agent`, or `Authorization` and an automatic mapping of MCP methods to authorization resources and actions. + + + For detailed policy configuration, custom authentication, and remote + deployments, visit the [Eunomia MCP Middleware + repository][eunomia-mcp-github]. + + +[eunomia-github]: https://github.com/whataboutyou-ai/eunomia +[eunomia-mcp-github]: https://github.com/whataboutyou-ai/eunomia/tree/main/pkgs/extensions/mcp +[fastmcp-middleare]: /servers/middleware diff --git a/docs/v2/integrations/fastapi.mdx b/docs/v2/integrations/fastapi.mdx new file mode 100644 index 000000000..683bc244e --- /dev/null +++ b/docs/v2/integrations/fastapi.mdx @@ -0,0 +1,451 @@ +--- +title: FastAPI 🤝 FastMCP +sidebarTitle: FastAPI +description: Integrate FastMCP with FastAPI applications +icon: bolt +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + +FastMCP provides two powerful ways to integrate with FastAPI applications: + +1. **[Generate an MCP server FROM your FastAPI app](#generating-an-mcp-server)** - Convert existing API endpoints into MCP tools +2. **[Mount an MCP server INTO your FastAPI app](#mounting-an-mcp-server)** - Add MCP functionality to your web application + + + +Generating MCP servers from OpenAPI is a great way to get started with FastMCP, but in practice LLMs achieve **significantly better performance** with well-designed and curated MCP servers than with auto-converted OpenAPI servers. This is especially true for complex APIs with many endpoints and parameters. + +We recommend using the FastAPI integration for bootstrapping and prototyping, not for mirroring your API to LLM clients. See the post [Stop Converting Your REST APIs to MCP](https://www.jlowin.dev/blog/stop-converting-rest-apis-to-mcp) for more details. + + + + +FastMCP does *not* include FastAPI as a dependency; you must install it separately to use this integration. + + +## Example FastAPI Application + +Throughout this guide, we'll use this e-commerce API as our example (click the `Copy` button to copy it for use with other code blocks): + +```python [expandable] +# Copy this FastAPI server into other code blocks in this guide + +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +# Models +class Product(BaseModel): + name: str + price: float + category: str + description: str | None = None + +class ProductResponse(BaseModel): + id: int + name: str + price: float + category: str + description: str | None = None + +# Create FastAPI app +app = FastAPI(title="E-commerce API", version="1.0.0") + +# In-memory database +products_db = { + 1: ProductResponse( + id=1, name="Laptop", price=999.99, category="Electronics" + ), + 2: ProductResponse( + id=2, name="Mouse", price=29.99, category="Electronics" + ), + 3: ProductResponse( + id=3, name="Desk Chair", price=299.99, category="Furniture" + ), +} +next_id = 4 + +@app.get("/products", response_model=list[ProductResponse]) +def list_products( + category: str | None = None, + max_price: float | None = None, +) -> list[ProductResponse]: + """List all products with optional filtering.""" + products = list(products_db.values()) + if category: + products = [p for p in products if p.category == category] + if max_price: + products = [p for p in products if p.price <= max_price] + return products + +@app.get("/products/{product_id}", response_model=ProductResponse) +def get_product(product_id: int): + """Get a specific product by ID.""" + if product_id not in products_db: + raise HTTPException(status_code=404, detail="Product not found") + return products_db[product_id] + +@app.post("/products", response_model=ProductResponse) +def create_product(product: Product): + """Create a new product.""" + global next_id + product_response = ProductResponse(id=next_id, **product.model_dump()) + products_db[next_id] = product_response + next_id += 1 + return product_response + +@app.put("/products/{product_id}", response_model=ProductResponse) +def update_product(product_id: int, product: Product): + """Update an existing product.""" + if product_id not in products_db: + raise HTTPException(status_code=404, detail="Product not found") + products_db[product_id] = ProductResponse( + id=product_id, + **product.model_dump(), + ) + return products_db[product_id] + +@app.delete("/products/{product_id}") +def delete_product(product_id: int): + """Delete a product.""" + if product_id not in products_db: + raise HTTPException(status_code=404, detail="Product not found") + del products_db[product_id] + return {"message": "Product deleted"} +``` + + +All subsequent code examples in this guide assume you have the above FastAPI application code already defined. Each example builds upon this base application, `app`. + + +## Generating an MCP Server + + + +One of the most common ways to bootstrap an MCP server is to generate it from an existing FastAPI application. FastMCP will expose your FastAPI endpoints as MCP components (tools, by default) in order to expose your API to LLM clients. + + + +### Basic Conversion + +Convert the FastAPI app to an MCP server with a single line: + +```python {5} +# Assumes the FastAPI app from above is already defined +from fastmcp import FastMCP + +# Convert to MCP server +mcp = FastMCP.from_fastapi(app=app) + +if __name__ == "__main__": + mcp.run() +``` + +### Adding Components + +Your converted MCP server is a full FastMCP instance, meaning you can add new tools, resources, and other components to it just like you would with any other FastMCP instance. + +```python {8-11} +# Assumes the FastAPI app from above is already defined +from fastmcp import FastMCP + +# Convert to MCP server +mcp = FastMCP.from_fastapi(app=app) + +# Add a new tool +@mcp.tool +def get_product(product_id: int) -> ProductResponse: + """Get a product by ID.""" + return products_db[product_id] + +# Run the MCP server +if __name__ == "__main__": + mcp.run() +``` + + + + + +### Interacting with the MCP Server + +Once you've converted your FastAPI app to an MCP server, you can interact with it using the FastMCP client to test functionality before deploying it to an LLM-based application. + +```python {3, } +# Assumes the FastAPI app from above is already defined +from fastmcp import FastMCP +from fastmcp.client import Client +import asyncio + +# Convert to MCP server +mcp = FastMCP.from_fastapi(app=app) + +async def demo(): + async with Client(mcp) as client: + # List available tools + tools = await client.list_tools() + print(f"Available tools: {[t.name for t in tools]}") + + # Create a product + result = await client.call_tool( + "create_product_products_post", + { + "name": "Wireless Keyboard", + "price": 79.99, + "category": "Electronics", + "description": "Bluetooth mechanical keyboard" + } + ) + print(f"Created product: {result.data}") + + # List electronics under $100 + result = await client.call_tool( + "list_products_products_get", + {"category": "Electronics", "max_price": 100} + ) + print(f"Affordable electronics: {result.data}") + +if __name__ == "__main__": + asyncio.run(demo()) +``` + +### Custom Route Mapping + +Because FastMCP's FastAPI integration is based on its [OpenAPI integration](/integrations/openapi), you can customize how endpoints are converted to MCP components in exactly the same way. For example, here we use a `RouteMap` to map all GET requests to MCP resources, and all POST/PUT/DELETE requests to MCP tools: + +```python +# Assumes the FastAPI app from above is already defined +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType + +# Custom mapping rules +mcp = FastMCP.from_fastapi( + app=app, + route_maps=[ + # GET with path params → ResourceTemplates + RouteMap( + methods=["GET"], + pattern=r".*\{.*\}.*", + mcp_type=MCPType.RESOURCE_TEMPLATE + ), + # Other GETs → Resources + RouteMap( + methods=["GET"], + pattern=r".*", + mcp_type=MCPType.RESOURCE + ), + # POST/PUT/DELETE → Tools (default) + ], +) + +# Now: +# - GET /products → Resource +# - GET /products/{id} → ResourceTemplate +# - POST/PUT/DELETE → Tools +``` + + +To learn more about customizing the conversion process, see the [OpenAPI Integration guide](/integrations/openapi). + + +### Authentication and Headers + +You can configure headers and other client options via the `httpx_client_kwargs` parameter. For example, to add authentication to your FastAPI app, you can pass a `headers` dictionary to the `httpx_client_kwargs` parameter: + +```python {27-31} +# Assumes the FastAPI app from above is already defined +from fastmcp import FastMCP + +# Add authentication to your FastAPI app +from fastapi import Depends, Header +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials + +security = HTTPBearer() + +def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): + if credentials.credentials != "secret-token": + raise HTTPException(status_code=401, detail="Invalid authentication") + return credentials.credentials + +# Add a protected endpoint +@app.get("/admin/stats", dependencies=[Depends(verify_token)]) +def get_admin_stats(): + return { + "total_products": len(products_db), + "categories": list(set(p.category for p in products_db.values())) + } + +# Create MCP server with authentication headers +mcp = FastMCP.from_fastapi( + app=app, + httpx_client_kwargs={ + "headers": { + "Authorization": "Bearer secret-token", + } + } +) +``` + +## Mounting an MCP Server + + + +In addition to generating servers, FastMCP can facilitate adding MCP servers to your existing FastAPI application. You can do this by mounting the MCP ASGI application. + +### Basic Mounting + +To mount an MCP server, you can use the `http_app` method on your FastMCP instance. This will return an ASGI application that can be mounted to your FastAPI application. + +```python {23-30} +from fastmcp import FastMCP +from fastapi import FastAPI + +# Create MCP server +mcp = FastMCP("Analytics Tools") + +@mcp.tool +def analyze_pricing(category: str) -> dict: + """Analyze pricing for a category.""" + products = [p for p in products_db.values() if p.category == category] + if not products: + return {"error": f"No products in {category}"} + + prices = [p.price for p in products] + return { + "category": category, + "avg_price": round(sum(prices) / len(prices), 2), + "min": min(prices), + "max": max(prices), + } + +# Create ASGI app from MCP server +mcp_app = mcp.http_app(path='/mcp') + +# Key: Pass lifespan to FastAPI +app = FastAPI(title="E-commerce API", lifespan=mcp_app.lifespan) + +# Mount the MCP server +app.mount("/analytics", mcp_app) + +# Now: API at /products/*, MCP at /analytics/mcp/ +``` + +## Offering an LLM-Friendly API + +A common pattern is to generate an MCP server from your FastAPI app and serve both interfaces from the same application. This provides an LLM-optimized interface alongside your regular API: + +```python +# Assumes the FastAPI app from above is already defined +from fastmcp import FastMCP +from fastapi import FastAPI + +# 1. Generate MCP server from your API +mcp = FastMCP.from_fastapi(app=app, name="E-commerce MCP") + +# 2. Create the MCP's ASGI app +mcp_app = mcp.http_app(path='/mcp') + +# 3. Create a new FastAPI app that combines both sets of routes +combined_app = FastAPI( + title="E-commerce API with MCP", + routes=[ + *mcp_app.routes, # MCP routes + *app.routes, # Original API routes + ], + lifespan=mcp_app.lifespan, +) + +# Now you have: +# - Regular API: http://localhost:8000/products +# - LLM-friendly MCP: http://localhost:8000/mcp +# Both served from the same FastAPI application! +``` + +This approach lets you maintain a single codebase while offering both traditional REST endpoints and MCP-compatible endpoints for LLM clients. + +## Key Considerations + +### Operation IDs + +FastAPI operation IDs become MCP component names. Always specify meaningful operation IDs: + +```python +# Good - explicit operation_id +@app.get("/users/{user_id}", operation_id="get_user_by_id") +def get_user(user_id: int): + return {"id": user_id} + +# Less ideal - auto-generated name +@app.get("/users/{user_id}") +def get_user(user_id: int): + return {"id": user_id} +``` + +### Lifespan Management + +When mounting MCP servers, always pass the lifespan context: + +```python +# Correct - lifespan passed +mcp_app = mcp.http_app(path='/mcp') +app = FastAPI(lifespan=mcp_app.lifespan) +app.mount("/mcp", mcp_app) + +# Incorrect - missing lifespan +app = FastAPI() +app.mount("/mcp", mcp.http_app()) # Session manager won't initialize +``` + +If you're mounting an authenticated MCP server under a path prefix, see [Mounting Authenticated Servers](/deployment/http#mounting-authenticated-servers) for important OAuth routing considerations. + +### CORS Middleware + +If your FastAPI app uses `CORSMiddleware` and you're mounting an OAuth-protected FastMCP server, avoid adding application-wide CORS middleware. FastMCP and the MCP SDK already handle CORS for OAuth routes, and layering CORS middleware can cause conflicts (such as 404 errors on `.well-known` routes or OPTIONS requests). + +If you need CORS on your own FastAPI routes, use the sub-app pattern: mount your API and FastMCP as separate apps, each with their own middleware, rather than adding top-level `CORSMiddleware` to the combined application. + +### Combining Lifespans + +If your FastAPI app already has a lifespan (for database connections, startup tasks, etc.), you can't simply replace it with the MCP lifespan. Instead, you need to create a new lifespan function that manages both contexts. This ensures that both your app's initialization logic and the MCP server's session manager run properly: + +```python +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastmcp import FastMCP + +# Your existing lifespan +@asynccontextmanager +async def app_lifespan(app: FastAPI): + # Startup + print("Starting up the app...") + # Initialize database, cache, etc. + yield + # Shutdown + print("Shutting down the app...") + +# Create MCP server +mcp = FastMCP("Tools") +mcp_app = mcp.http_app(path='/mcp') + +# Combine both lifespans +@asynccontextmanager +async def combined_lifespan(app: FastAPI): + # Run both lifespans + async with app_lifespan(app): + async with mcp_app.lifespan(app): + yield + +# Use the combined lifespan +app = FastAPI(lifespan=combined_lifespan) +app.mount("/mcp", mcp_app) +``` + +This pattern ensures both your app's initialization logic and the MCP server's session manager are properly managed. The key is using nested `async with` statements - the inner context (MCP) will be initialized after the outer context (your app), and cleaned up before it. This maintains the correct initialization and cleanup order for all your resources. + +### Performance Tips + +1. **Use in-memory transport for testing** - Pass MCP servers directly to clients +2. **Design purpose-built MCP tools** - Better than auto-converting complex APIs +3. **Keep tool parameters simple** - LLMs perform better with focused interfaces + +For more details on configuration options, see the [OpenAPI Integration guide](/integrations/openapi). \ No newline at end of file diff --git a/docs/v2/integrations/gemini-cli.mdx b/docs/v2/integrations/gemini-cli.mdx new file mode 100644 index 000000000..af63a5d32 --- /dev/null +++ b/docs/v2/integrations/gemini-cli.mdx @@ -0,0 +1,174 @@ +--- +title: Gemini CLI 🤝 FastMCP +sidebarTitle: Gemini CLI +description: Install and use FastMCP servers in Gemini CLI +icon: message-smile +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" +import { LocalFocusTip } from "/snippets/local-focus.mdx" + + + +Gemini CLI supports MCP servers through multiple transport methods including STDIO, SSE, and HTTP, allowing you to extend Gemini's capabilities with custom tools, resources, and prompts from your FastMCP servers. + +## Requirements + +This integration uses STDIO transport to run your FastMCP server locally. For remote deployments, you can run your FastMCP server with HTTP or SSE transport and configure it directly using Gemini CLI's built-in MCP management commands. + +## Create a Server + +The examples in this guide will use the following simple dice-rolling server, saved as `server.py`. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run() +``` + +## Install the Server + +### FastMCP CLI + + +The easiest way to install a FastMCP server in Gemini CLI is using the `fastmcp install gemini-cli` command. This automatically handles the configuration, dependency management, and calls Gemini CLI's built-in MCP management system. + +```bash +fastmcp install gemini-cli server.py +``` + +The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file: + +```bash +# These are equivalent if your server object is named 'mcp' +fastmcp install gemini-cli server.py +fastmcp install gemini-cli server.py:mcp + +# Use explicit object name if your server has a different name +fastmcp install gemini-cli server.py:my_custom_server +``` + +The command will automatically configure the server with Gemini CLI's `gemini mcp add` command. + +#### Dependencies + +FastMCP provides flexible dependency management options for your Gemini CLI servers: + +**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times: + +```bash +fastmcp install gemini-cli server.py --with pandas --with requests +``` + +**Requirements file**: If you maintain a `requirements.txt` file with all your dependencies, use `--with-requirements` to install them: + +```bash +fastmcp install gemini-cli server.py --with-requirements requirements.txt +``` + +**Editable packages**: For local packages under development, use `--with-editable` to install them in editable mode: + +```bash +fastmcp install gemini-cli server.py --with-editable ./my-local-package +``` + +Alternatively, you can use a `fastmcp.json` configuration file (recommended): + +```json fastmcp.json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "dependencies": ["pandas", "requests"] + } +} +``` + + +#### Python Version and Project Configuration + +Control the Python environment for your server with these options: + +**Python version**: Use `--python` to specify which Python version your server requires. This ensures compatibility when your server needs specific Python features: + +```bash +fastmcp install gemini-cli server.py --python 3.11 +``` + +**Project directory**: Use `--project` to run your server within a specific project context. This tells `uv` to use the project's configuration files and virtual environment: + +```bash +fastmcp install gemini-cli server.py --project /path/to/my-project +``` + +#### Environment Variables + +If your server needs environment variables (like API keys), you must include them: + +```bash +fastmcp install gemini-cli server.py --server-name "Weather Server" \ + --env API_KEY=your-api-key \ + --env DEBUG=true +``` + +Or load them from a `.env` file: + +```bash +fastmcp install gemini-cli server.py --server-name "Weather Server" --env-file .env +``` + + +**Gemini CLI must be installed**. The integration looks for the Gemini CLI and uses the `gemini mcp add` command to register servers. + + +### Manual Configuration + +For more control over the configuration, you can manually use Gemini CLI's built-in MCP management commands. This gives you direct control over how your server is launched: + +```bash +# Add a server with custom configuration +gemini mcp add dice-roller uv -- run --with fastmcp fastmcp run server.py + +# Add with environment variables +gemini mcp add weather-server -e API_KEY=secret -e DEBUG=true uv -- run --with fastmcp fastmcp run server.py + +# Add with specific scope (user, or project) +gemini mcp add my-server --scope user uv -- run --with fastmcp fastmcp run server.py +``` + +You can also manually specify Python versions and project directories in your Gemini CLI commands: + +```bash +# With specific Python version +gemini mcp add ml-server uv -- run --python 3.11 --with fastmcp fastmcp run server.py + +# Within a project directory +gemini mcp add project-server uv -- run --project /path/to/project --with fastmcp fastmcp run server.py +``` + +## Using the Server + +Once your server is installed, you can start using your FastMCP server with Gemini CLI. + +Try asking Gemini something like: + +> "Roll some dice for me" + +Gemini will automatically detect your `roll_dice` tool and use it to fulfill your request. + +Gemini CLI can now access all the tools and prompts you've defined in your FastMCP server. + +If your server provides prompts, you can use them as slash commands with `/prompt_name`. diff --git a/docs/v2/integrations/gemini.mdx b/docs/v2/integrations/gemini.mdx new file mode 100644 index 000000000..159e662a5 --- /dev/null +++ b/docs/v2/integrations/gemini.mdx @@ -0,0 +1,108 @@ +--- +title: Gemini SDK 🤝 FastMCP +sidebarTitle: Gemini SDK +description: Connect FastMCP servers to the Google Gemini SDK +icon: message-code +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + +Google's Gemini API includes built-in support for MCP servers in their Python and JavaScript SDKs, allowing you to connect directly to MCP servers and use their tools seamlessly with Gemini models. + +## Gemini Python SDK + +Google's [Gemini Python SDK](https://ai.google.dev/gemini-api/docs) can use FastMCP clients directly. + + +Google's MCP integration is currently experimental and available in the Python and JavaScript SDKs. The API automatically calls MCP tools when needed and can connect to both local and remote MCP servers. + + + +Currently, Gemini's MCP support only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to the AI. Other MCP features like resources and prompts are not currently supported. + + +### Create a Server + +First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run() +``` + +### Call the Server + + +To use the Gemini API with MCP, you'll need to install the Google Generative AI SDK: + +```bash +pip install google-genai +``` + +You'll also need to authenticate with Google. You can do this by setting the `GEMINI_API_KEY` environment variable. Consult the Gemini SDK documentation for more information. + +```bash +export GEMINI_API_KEY="your-api-key" +``` + +Gemini's SDK interacts directly with the MCP client session. To call the server, you'll need to instantiate a FastMCP client, enter its connection context, and pass the client session to the Gemini SDK. + +```python {5, 9, 15} +from fastmcp import Client +from google import genai +import asyncio + +mcp_client = Client("server.py") +gemini_client = genai.Client() + +async def main(): + async with mcp_client: + response = await gemini_client.aio.models.generate_content( + model="gemini-2.0-flash", + contents="Roll 3 dice!", + config=genai.types.GenerateContentConfig( + temperature=0, + tools=[mcp_client.session], # Pass the FastMCP client session + ), + ) + print(response.text) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +If you run this code, you'll see output like: + +```text +Okay, I rolled 3 dice and got a 5, 4, and 1. +``` + +### Remote & Authenticated Servers + +In the above example, we connected to our local server using `stdio` transport. Because we're using a FastMCP client, you can also connect to any local or remote MCP server, using any [transport](/clients/transports) or [auth](/clients/auth) method supported by FastMCP, simply by changing the client configuration. + +For example, to connect to a remote, authenticated server, you can use the following client: + +```python +from fastmcp import Client +from fastmcp.client.auth import BearerAuth + +mcp_client = Client( + "https://my-server.com/mcp/", + auth=BearerAuth(""), +) +``` + +The rest of the code remains the same. + + diff --git a/docs/v2/integrations/github.mdx b/docs/v2/integrations/github.mdx new file mode 100644 index 000000000..f1b646b67 --- /dev/null +++ b/docs/v2/integrations/github.mdx @@ -0,0 +1,253 @@ +--- +title: GitHub OAuth 🤝 FastMCP +sidebarTitle: GitHub +description: Secure your FastMCP server with GitHub OAuth +icon: github +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **GitHub OAuth**. Since GitHub doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge GitHub's traditional OAuth with MCP's authentication requirements. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. A **[GitHub Account](https://github.com/)** with access to create OAuth Apps +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Create a GitHub OAuth App + +Create an OAuth App in your GitHub settings to get the credentials needed for authentication: + + + + Go to **Settings → Developer settings → OAuth Apps** in your GitHub account, or visit [github.com/settings/developers](https://github.com/settings/developers). + + Click **"New OAuth App"** to create a new application. + + + + Fill in the application details: + + - **Application name**: Choose a name users will recognize (e.g., "My FastMCP Server") + - **Homepage URL**: Your application's homepage or documentation URL + - **Authorization callback URL**: Your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`) + + + The callback URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. For local development, GitHub allows `http://localhost` URLs. For production, you must use HTTPS. + + + + If you want to use a custom callback path (e.g., `/auth/github/callback`), make sure to set the same path in both your GitHub OAuth App settings and the `redirect_path` parameter when configuring the GitHubProvider. + + + + + After creating the app, you'll see: + + - **Client ID**: A public identifier like `Ov23liAbcDefGhiJkLmN` + - **Client Secret**: Click "Generate a new client secret" and save the value securely + + + Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `GitHubProvider`, which handles GitHub's OAuth quirks automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.github import GitHubProvider + +# The GitHubProvider handles GitHub's token format and validation +auth_provider = GitHubProvider( + client_id="Ov23liAbcDefGhiJkLmN", # Your GitHub OAuth App Client ID + client_secret="github_pat_...", # Your GitHub OAuth App Client Secret + base_url="http://localhost:8000", # Must match your OAuth App configuration + # redirect_path="/auth/callback" # Default value, customize if needed +) + +mcp = FastMCP(name="GitHub Secured App", auth=auth_provider) + +# Add a protected tool to test authentication +@mcp.tool +async def get_user_info() -> dict: + """Returns information about the authenticated GitHub user.""" + from fastmcp.server.dependencies import get_access_token + + token = get_access_token() + # The GitHubProvider stores user data in token claims + return { + "github_user": token.claims.get("login"), + "name": token.claims.get("name"), + "email": token.claims.get("email") + } +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by GitHub OAuth authentication. + +### Testing with a Client + +Create a test client that authenticates with your GitHub-protected server: + +```python test_client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle GitHub OAuth + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open GitHub login in your browser + print("✓ Authenticated with GitHub!") + + # Test the protected tool + result = await client.call_tool("get_user_info") + print(f"GitHub user: {result['github_user']}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to GitHub's authorization page +2. After you authorize the app, you'll be redirected back +3. The client receives the token and can make authenticated requests + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Production setup with encrypted persistent token storage +auth_provider = GitHubProvider( + client_id="Ov23liAbcDefGhiJkLmN", + client_secret="github_pat_...", + base_url="https://your-production-domain.com", + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production GitHub App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + +## Environment Variables + + + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the GitHub provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.github.GitHubProvider` to use GitHub authentication. + + + +### GitHub-Specific Configuration + +These environment variables provide default values for the GitHub provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your GitHub OAuth App Client ID (e.g., `Ov23liAbcDefGhiJkLmN`) + + + +Your GitHub OAuth App Client Secret + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +Redirect path configured in your GitHub OAuth App + + + +Comma-, space-, or JSON-separated list of required GitHub scopes (e.g., `user repo` or `["user","repo"]`) + + + +HTTP request timeout for GitHub API calls + + + +Example `.env` file: +```bash +# Use the GitHub provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider + +# GitHub OAuth credentials +FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID=Ov23liAbcDefGhiJkLmN +FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET=github_pat_... +FASTMCP_SERVER_AUTH_GITHUB_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES=user,repo +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="GitHub Secured App") + +@mcp.tool +async def list_repos() -> list[str]: + """List the authenticated user's repositories.""" + # Your tool implementation here + pass +``` diff --git a/docs/v2/integrations/google.mdx b/docs/v2/integrations/google.mdx new file mode 100644 index 000000000..b673962df --- /dev/null +++ b/docs/v2/integrations/google.mdx @@ -0,0 +1,267 @@ +--- +title: Google OAuth 🤝 FastMCP +sidebarTitle: Google +description: Secure your FastMCP server with Google OAuth +icon: google +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **Google OAuth**. Since Google doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Google's traditional OAuth with MCP's authentication requirements. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. A **[Google Cloud Account](https://console.cloud.google.com/)** with access to create OAuth 2.0 Client IDs +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Create a Google OAuth 2.0 Client ID + +Create an OAuth 2.0 Client ID in your Google Cloud Console to get the credentials needed for authentication: + + + + Go to the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) and select your project (or create a new one). + + First, configure the OAuth consent screen by navigating to **APIs & Services → OAuth consent screen**. Choose "External" for testing or "Internal" for G Suite organizations. + + + + Navigate to **APIs & Services → Credentials** and click **"+ CREATE CREDENTIALS"** → **"OAuth client ID"**. + + Configure your OAuth client: + + - **Application type**: Web application + - **Name**: Choose a descriptive name (e.g., "FastMCP Server") + - **Authorized JavaScript origins**: Add your server's base URL (e.g., `http://localhost:8000`) + - **Authorized redirect URIs**: Add your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`) + + + The redirect URI must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. For local development, Google allows `http://localhost` URLs with various ports. For production, you must use HTTPS. + + + + If you want to use a custom callback path (e.g., `/auth/google/callback`), make sure to set the same path in both your Google OAuth Client settings and the `redirect_path` parameter when configuring the GoogleProvider. + + + + + After creating the client, you'll receive: + + - **Client ID**: A string ending in `.apps.googleusercontent.com` + - **Client Secret**: A string starting with `GOCSPX-` + + Download the JSON credentials or copy these values securely. + + + Store these credentials securely. Never commit them to version control. Use environment variables or a secrets manager in production. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `GoogleProvider`, which handles Google's OAuth flow automatically: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.google import GoogleProvider + +# The GoogleProvider handles Google's token format and validation +auth_provider = GoogleProvider( + client_id="123456789.apps.googleusercontent.com", # Your Google OAuth Client ID + client_secret="GOCSPX-abc123...", # Your Google OAuth Client Secret + base_url="http://localhost:8000", # Must match your OAuth configuration + required_scopes=[ # Request user information + "openid", + "https://www.googleapis.com/auth/userinfo.email", + ], + # redirect_path="/auth/callback" # Default value, customize if needed +) + +mcp = FastMCP(name="Google Secured App", auth=auth_provider) + +# Add a protected tool to test authentication +@mcp.tool +async def get_user_info() -> dict: + """Returns information about the authenticated Google user.""" + from fastmcp.server.dependencies import get_access_token + + token = get_access_token() + # The GoogleProvider stores user data in token claims + return { + "google_id": token.claims.get("sub"), + "email": token.claims.get("email"), + "name": token.claims.get("name"), + "picture": token.claims.get("picture"), + "locale": token.claims.get("locale") + } +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by Google OAuth authentication. + +### Testing with a Client + +Create a test client that authenticates with your Google-protected server: + +```python test_client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle Google OAuth + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open Google login in your browser + print("✓ Authenticated with Google!") + + # Test the protected tool + result = await client.call_tool("get_user_info") + print(f"Google user: {result['email']}") + print(f"Name: {result['name']}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to Google's authorization page +2. Sign in with your Google account and grant the requested permissions +3. After authorization, you'll be redirected back +4. The client receives the token and can make authenticated requests + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.google import GoogleProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Production setup with encrypted persistent token storage +auth_provider = GoogleProvider( + client_id="123456789.apps.googleusercontent.com", + client_secret="GOCSPX-abc123...", + base_url="https://your-production-domain.com", + required_scopes=["openid", "https://www.googleapis.com/auth/userinfo.email"], + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production Google App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + +## Environment Variables + + + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the Google provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.google.GoogleProvider` to use Google authentication. + + + +### Google-Specific Configuration + +These environment variables provide default values for the Google provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Google OAuth 2.0 Client ID (e.g., `123456789.apps.googleusercontent.com`) + + + +Your Google OAuth 2.0 Client Secret (e.g., `GOCSPX-abc123...`) + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +Redirect path configured in your Google OAuth Client + + + +Comma-, space-, or JSON-separated list of required Google scopes (e.g., `"openid,https://www.googleapis.com/auth/userinfo.email"` or `["openid", "https://www.googleapis.com/auth/userinfo.email"]`) + + + +HTTP request timeout for Google API calls + + + +Example `.env` file: +```bash +# Use the Google provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleProvider + +# Google OAuth credentials +FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com +FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-abc123... +FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES=openid,https://www.googleapis.com/auth/userinfo.email +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="Google Secured App") + +@mcp.tool +async def protected_tool(query: str) -> str: + """A tool that requires Google authentication to access.""" + # Your tool implementation here + return f"Processing authenticated request: {query}" +``` \ No newline at end of file diff --git a/docs/v2/integrations/mcp-json-configuration.mdx b/docs/v2/integrations/mcp-json-configuration.mdx new file mode 100644 index 000000000..b44b15e76 --- /dev/null +++ b/docs/v2/integrations/mcp-json-configuration.mdx @@ -0,0 +1,422 @@ +--- +title: MCP JSON Configuration 🤝 FastMCP +sidebarTitle: MCP.json +description: Generate standard MCP configuration files for any compatible client +icon: brackets-curly +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +FastMCP can generate standard MCP JSON configuration files that work with any MCP-compatible client including Claude Desktop, VS Code, Cursor, and other applications that support the Model Context Protocol. + +## MCP JSON Configuration Standard + +The MCP JSON configuration format is an **emergent standard** that has developed across the MCP ecosystem. This format defines how MCP clients should configure and launch MCP servers, providing a consistent way to specify server commands, arguments, and environment variables. + +### Configuration Structure + +The standard uses a `mcpServers` object where each key represents a server name and the value contains the server's configuration: + +```json +{ + "mcpServers": { + "server-name": { + "command": "executable", + "args": ["arg1", "arg2"], + "env": { + "VAR": "value" + } + } + } +} +``` + +### Server Configuration Fields + +#### `command` (required) +The executable command to run the MCP server. This should be an absolute path or a command available in the system PATH. + +```json +{ + "command": "python" +} +``` + +#### `args` (optional) +An array of command-line arguments passed to the server executable. Arguments are passed in order. + +```json +{ + "args": ["server.py", "--verbose", "--port", "8080"] +} +``` + +#### `env` (optional) +An object containing environment variables to set when launching the server. All values must be strings. + +```json +{ + "env": { + "API_KEY": "secret-key", + "DEBUG": "true", + "PORT": "8080" + } +} +``` + +### Client Adoption + +This format is widely adopted across the MCP ecosystem: + +- **Claude Desktop**: Uses `~/.claude/claude_desktop_config.json` +- **Cursor**: Uses `~/.cursor/mcp.json` +- **VS Code**: Uses workspace `.vscode/mcp.json` +- **Other clients**: Many MCP-compatible applications follow this standard + +## Overview + + +**For the best experience, use FastMCP's first-class integrations:** [`fastmcp install claude-code`](/integrations/claude-code), [`fastmcp install claude-desktop`](/integrations/claude-desktop), or [`fastmcp install cursor`](/integrations/cursor). Use MCP JSON generation for advanced use cases and unsupported clients. + + +The `fastmcp install mcp-json` command generates configuration in the standard `mcpServers` format used across the MCP ecosystem. This is useful when: + +- **Working with unsupported clients** - Any MCP client not directly integrated with FastMCP +- **CI/CD environments** - Automated configuration generation for deployments +- **Configuration sharing** - Easy distribution of server setups to team members +- **Custom tooling** - Integration with your own MCP management tools +- **Manual setup** - When you prefer to manually configure your MCP client + +## Basic Usage + +Generate configuration and output to stdout (useful for piping): + +```bash +fastmcp install mcp-json server.py +``` + +This outputs the server configuration JSON with the server name as the root key: + +```json +{ + "My Server": { + "command": "uv", + "args": [ + "run", + "--with", + "fastmcp", + "fastmcp", + "run", + "/absolute/path/to/server.py" + ] + } +} +``` + +To use this in a client configuration file, add it to the `mcpServers` object in your client's configuration: + +```json +{ + "mcpServers": { + "My Server": { + "command": "uv", + "args": [ + "run", + "--with", + "fastmcp", + "fastmcp", + "run", + "/absolute/path/to/server.py" + ] + } + } +} +``` + + +When using `--python`, `--project`, or `--with-requirements`, the generated configuration will include these options in the `uv run` command, ensuring your server runs with the correct Python version and dependencies. + + + +Different MCP clients may have specific configuration requirements or formatting needs. Always consult your client's documentation to ensure proper integration. + + +## Configuration Options + +### Server Naming + +```bash +# Use server's built-in name (from FastMCP constructor) +fastmcp install mcp-json server.py + +# Override with custom name +fastmcp install mcp-json server.py --name "Custom Server Name" +``` + +### Dependencies + +Add Python packages your server needs: + +```bash +# Single package +fastmcp install mcp-json server.py --with pandas + +# Multiple packages +fastmcp install mcp-json server.py --with pandas --with requests --with httpx + +# Editable local package +fastmcp install mcp-json server.py --with-editable ./my-package + +# From requirements file +fastmcp install mcp-json server.py --with-requirements requirements.txt +``` + +You can also use a `fastmcp.json` configuration file (recommended): + +```json fastmcp.json +{ + "$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json", + "source": { + "path": "server.py", + "entrypoint": "mcp" + }, + "environment": { + "dependencies": ["pandas", "matplotlib", "seaborn"] + } +} +``` + +Then simply install with: +```bash +fastmcp install mcp-json fastmcp.json +``` + + +### Environment Variables + +```bash +# Individual environment variables +fastmcp install mcp-json server.py \ + --env API_KEY=your-secret-key \ + --env DEBUG=true + +# Load from .env file +fastmcp install mcp-json server.py --env-file .env +``` + +### Python Version and Project Directory + +Specify Python version or run within a specific project: + +```bash +# Use specific Python version +fastmcp install mcp-json server.py --python 3.11 + +# Run within a project directory +fastmcp install mcp-json server.py --project /path/to/project +``` + +### Server Object Selection + +Use the same `file.py:object` notation as other FastMCP commands: + +```bash +# Auto-detects server object (looks for 'mcp', 'server', or 'app') +fastmcp install mcp-json server.py + +# Explicit server object +fastmcp install mcp-json server.py:my_custom_server +``` + +## Clipboard Integration + +Copy configuration directly to your clipboard for easy pasting: + +```bash +fastmcp install mcp-json server.py --copy +``` + + +The `--copy` flag requires the `pyperclip` Python package. If not installed, you'll see an error message with installation instructions. + + +## Usage Examples + +### Basic Server + +```bash +fastmcp install mcp-json dice_server.py +``` + +Output: +```json +{ + "Dice Server": { + "command": "uv", + "args": [ + "run", + "--with", + "fastmcp", + "fastmcp", + "run", + "/home/user/dice_server.py" + ] + } +} +``` + +### Production Server with Dependencies + +```bash +fastmcp install mcp-json api_server.py \ + --name "Production API Server" \ + --with requests \ + --with python-dotenv \ + --env API_BASE_URL=https://api.example.com \ + --env TIMEOUT=30 +``` + +### Advanced Configuration + +```bash +fastmcp install mcp-json ml_server.py \ + --name "ML Analysis Server" \ + --python 3.11 \ + --with-requirements requirements.txt \ + --project /home/user/ml-project \ + --env GPU_DEVICE=0 +``` + +Output: +```json +{ + "Production API Server": { + "command": "uv", + "args": [ + "run", + "--with", + "fastmcp", + "--with", + "python-dotenv", + "--with", + "requests", + "fastmcp", + "run", + "/home/user/api_server.py" + ], + "env": { + "API_BASE_URL": "https://api.example.com", + "TIMEOUT": "30" + } + } +} +``` + +The advanced configuration example generates: +```json +{ + "ML Analysis Server": { + "command": "uv", + "args": [ + "run", + "--python", + "3.11", + "--project", + "/home/user/ml-project", + "--with", + "fastmcp", + "--with-requirements", + "requirements.txt", + "fastmcp", + "run", + "/home/user/ml_server.py" + ], + "env": { + "GPU_DEVICE": "0" + } + } +} +``` + +### Pipeline Usage + +Save configuration to file: + +```bash +fastmcp install mcp-json server.py > mcp-config.json +``` + +Use in shell scripts: + +```bash +#!/bin/bash +CONFIG=$(fastmcp install mcp-json server.py --name "CI Server") +echo "$CONFIG" | jq '."CI Server".command' +# Output: "uv" +``` + +## Integration with MCP Clients + +The generated configuration works with any MCP-compatible application: + +### Claude Desktop + +**Prefer [`fastmcp install claude-desktop`](/integrations/claude-desktop)** for automatic installation. Use MCP JSON for advanced configuration needs. + +Copy the `mcpServers` object into `~/.claude/claude_desktop_config.json` + +### Cursor + +**Prefer [`fastmcp install cursor`](/integrations/cursor)** for automatic installation. Use MCP JSON for advanced configuration needs. + +Add to `~/.cursor/mcp.json` + +### VS Code +Add to your workspace's `.vscode/mcp.json` file + +### Custom Applications +Use the JSON configuration with any application that supports the MCP protocol + +## Configuration Format + +The generated configuration outputs a server object with the server name as the root key: + +```json +{ + "": { + "command": "", + "args": ["", "", "..."], + "env": { + "": "" + } + } +} +``` + +To use this in an MCP client, add it to the client's `mcpServers` configuration object. + +**Fields:** +- `command`: The executable to run (always `uv` for FastMCP servers) +- `args`: Command-line arguments including dependencies and server path +- `env`: Environment variables (only included if specified) + + +**All file paths in the generated configuration are absolute paths**. This ensures the configuration works regardless of the working directory when the MCP client starts the server. + + +## Requirements + +- **uv**: Must be installed and available in your system PATH +- **pyperclip** (optional): Required only for `--copy` functionality + +Install uv if not already available: + +```bash +# macOS +brew install uv + +# Linux/Windows +curl -LsSf https://astral.sh/uv/install.sh | sh +``` diff --git a/docs/v2/integrations/oci.mdx b/docs/v2/integrations/oci.mdx new file mode 100644 index 000000000..ab46486c4 --- /dev/null +++ b/docs/v2/integrations/oci.mdx @@ -0,0 +1,333 @@ +--- +title: OCI IAM OAuth 🤝 FastMCP +sidebarTitle: Oracle +description: Secure your FastMCP server with OCI IAM OAuth +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **OCI IAM OAuth**. Since OCI IAM doesn't support Dynamic Client Registration, this integration uses the [**OIDC Proxy**](/servers/auth/oidc-proxy) pattern to bridge OCI's traditional OAuth with MCP's authentication requirements. + +## Configuration + +### Prerequisites + +1. An OCI cloud Account with access to create an Integrated Application in an Identity Domain. +2. Your FastMCP server's URL (For dev environments, it is http://localhost:8000. For PROD environments, it could be https://mcp.${DOMAIN}.com) + +### Step 1: Make sure client access is enabled for JWK's URL + + + + + Login to OCI console (https://cloud.oracle.com for OCI commercial cloud). + From "Identity & Security" menu, open Domains page. + On the Domains list page, select the domain that you are using for MCP Authentication. + Open Settings tab. + Click on "Edit Domain Settings" button. + + + OCI console showing the Edit Domain Settings button in the IAM Domain settings page + + + + + + Enable "Configure client access" checkbox as shown in the screenshot. + + + OCI IAM Domain Settings + + + + +### Step 2: Create OAuth client for MCP server authentication + +Follow the Steps as mentioned below to create an OAuth client. + + + + + Login to OCI console (https://cloud.oracle.com for OCI commercial cloud). + From "Identity & Security" menu, open Domains page. + On the Domains list page, select the domain in which you want to create MCP server OAuth client. If you need help finding the list page for the domain, see [Listing Identity Domains.](https://docs.oracle.com/en-us/iaas/Content/Identity/domains/to-view-identity-domains.htm#view-identity-domains). + On the details page, select Integrated applications. A list of applications in the domain is displayed. + + + + + Select Add application. + In the Add application window, select Confidential Application. + Select Launch workflow. + In the Add application details page, Enter name and description as shown below. + + + Adding a Confidential Integrated Application in OCI IAM Domain + + + + + + Once the Integrated Application is created, Click on "OAuth configuration" tab. + Click on "Edit OAuth configuration" button. + Configure the application as OAuth client by selecting "Configure this application as a client now" radio button. + Select "Authorization code" grant type. If you are planning to use the same OAuth client application for token exchange, select "Client credentials" grant type as well. In the sample, we will use the same client. + For Authorization grant type, select redirect URL. In most cases, this will be the MCP server URL followed by "/oauth/callback". + + + OAuth Configuration for an Integrated Application in OCI IAM Domain + + + + + + Click on "Submit" button to update OAuth configuration for the client application. + **Note: You don't need to do any special configuration to support PKCE for the OAuth client.** + Make sure to Activate the client application. + Note down client ID and client secret for the application. Update .env file and replace FASTMCP_SERVER_AUTH_OCI_CLIENT_ID and FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET values. + FASTMCP_SERVER_AUTH_OCI_IAM_GUID in the env file is the Identity domain URL that you chose for the MCP server. + + + +This is all you need to implement MCP server authentication against OCI IAM. However, you may want to use an authenticated user token to invoke OCI control plane APIs and propagate identity to the OCI control plane instead of using a service user account. In that case, you need to implement token exchange. + +### Step 3: Token Exchange Setup (Only if MCP server needs to talk to OCI Control Plane) + +Token exchange helps you exchange a logged-in user's OCI IAM token for an OCI control plane session token, also known as UPST (User Principal Session Token). To learn more about token exchange, refer to my [Workload Identity Federation Blog](https://www.ateam-oracle.com/post/workload-identity-federation) + +For token exchange, we need to configure Identity propagation trust. The blog above discusses setting up the trust using REST APIs. However, you can also use OCI CLI. Before using the CLI command below, ensure that you have created a token exchange OAuth client. In most cases, you can use the same OAuth client that you created above. You will use the client ID of the token exchange OAuth client in the CLI command below and replace it with {FASTMCP_SERVER_AUTH_OCI_CLIENT_ID}. + +You will also need to update the client secret for the token exchange OAuth client in the .env file. It is the FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET parameter. Update FASTMCP_SERVER_AUTH_OCI_IAM_GUID and FASTMCP_SERVER_AUTH_OCI_CLIENT_ID as well for the token exchange OAuth client in the .env file. + +```bash +oci identity-domains identity-propagation-trust create \ +--schemas '["urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"]' \ +--public-key-endpoint "https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/admin/v1/SigningCert/jwk" \ +--name "For Token Exchange" --type "JWT" \ +--issuer "https://identity.oraclecloud.com/" --active true \ +--endpoint "https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oracleclcoud.com" \ +--subject-claim-name "sub" --allow-impersonation false \ +--subject-mapping-attribute "username" \ +--subject-type "User" --client-claim-name "iss" \ +--client-claim-values '["https://identity.oraclecloud.com/"]' \ +--oauth-clients '["{FASTMCP_SERVER_AUTH_OCI_CLIENT_ID}"]' +``` + +To exchange access token for OCI token and create a signer object, you need to add below code in MCP server. You can then use the signer object to create any OCI control plane client. + +```python + +from fastmcp.server.dependencies import get_access_token +from fastmcp.utilities.logging import get_logger +from oci.auth.signers import TokenExchangeSigner +import os + +logger = get_logger(__name__) + +# Load configuration from environment +FASTMCP_SERVER_AUTH_OCI_IAM_GUID = os.environ["FASTMCP_SERVER_AUTH_OCI_IAM_GUID"] +FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"] +FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"] + +_global_token_cache = {} #In memory cache for OCI session token signer + +def get_oci_signer() -> TokenExchangeSigner: + + authntoken = get_access_token() + tokenID = authntoken.claims.get("jti") + token = authntoken.token + + #Check if the signer exists for the token ID in memory cache + cached_signer = _global_token_cache.get(tokenID) + logger.debug(f"Global cached signer: {cached_signer}") + if cached_signer: + logger.debug(f"Using globally cached signer for token ID: {tokenID}") + return cached_signer + + #If the signer is not yet created for the token then create new OCI signer object + logger.debug(f"Creating new signer for token ID: {tokenID}") + signer = TokenExchangeSigner( + jwt_or_func=token, + oci_domain_id=FASTMCP_SERVER_AUTH_OCI_IAM_GUID.split(".")[0], + client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, + client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET, + ) + logger.debug(f"Signer {signer} created for token ID: {tokenID}") + + #Cache the signer object in memory cache + _global_token_cache[tokenID] = signer + logger.debug(f"Signer cached for token ID: {tokenID}") + + return signer +``` + +## Running MCP server + +Once the setup is complete, to run the MCP server, run the below command. +```bash +fastmcp run server.py:mcp --transport http --port 8000 +``` + +To run MCP client, run the below command. +```bash +python3 client.py +``` + +MCP Client sample is as below. +```python client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle OCI OAuth flows + async with Client("http://localhost:8000/mcp/", auth="oauth") as client: + # First-time connection will open OCI login in your browser + print("✓ Authenticated with OCI IAM") + + tools = await client.list_tools() + print(f"🔧 Available tools ({len(tools)}):") + for tool in tools: + print(f" - {tool.name}: {tool.description}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to OCI IAM's login page +2. Sign in with your OCI account and grant the requested consent +3. After authorization, you'll be redirected back to the redirect path +4. The client receives the token and can make authenticated requests + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`: + +```python server.py + +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.oci import OCIProvider + +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Load configuration from environment +FASTMCP_SERVER_AUTH_OCI_CONFIG_URL = os.environ["FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"] +FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"] +FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"] + +# Production setup with encrypted persistent token storage +auth_provider = OCIProvider( + config_url=FASTMCP_SERVER_AUTH_OCI_CONFIG_URL, + client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, + client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET, + base_url="https://your-production-domain.com", + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production OCI App", auth=auth_provider) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at Rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Environment Variables + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the OCI provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.oci.OCIProvider` to use OCI IAM authentication. + + + +### OCI-Specific Configuration + +These environment variables provide default values for the OCI IAM provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your OCI Application Configuration URL (e.g., `idcs-asdascxasd11......identity.oraclecloud.com`) + + + +Your OCI Application Configuration URL (e.g., `https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/.well-known/openid-configuration`) + + + +Your OCI Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`) + + + +Your OCI Application Client Secret (e.g., `idcsssvPYqbjemq...`) + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Redirect path configured in your OCI IAM Integrated Application + + + + +Example `.env` file: +```bash +# Use the OCI IAM provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.oci.OCIProvider + +# OCI IAM configuration and credentials +FASTMCP_SERVER_AUTH_OCI_IAM_GUID=idcs-asaacasd1111..... +FASTMCP_SERVER_AUTH_OCI_CONFIG_URL=https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/.well-known/openid-configuration +FASTMCP_SERVER_AUTH_OCI_CLIENT_ID= +FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET= +FASTMCP_SERVER_AUTH_OCI_BASE_URL=https://your-server.com +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_access_token + +# Authentication is automatically configured from environment +mcp = FastMCP(name="OCI Secured App") + +@mcp.tool +def whoami() -> str: + """The whoami function is to test MCP server without requiring token exchange. + This tool can be used to test successful authentication against OCI IAM. + It will return logged in user's subject (username from IAM domain).""" + token = get_access_token() + user = token.claims.get("sub") + return f"You are User: {user}" +``` \ No newline at end of file diff --git a/docs/v2/integrations/openai.mdx b/docs/v2/integrations/openai.mdx new file mode 100644 index 000000000..6f88193f5 --- /dev/null +++ b/docs/v2/integrations/openai.mdx @@ -0,0 +1,227 @@ +--- +title: OpenAI API 🤝 FastMCP +sidebarTitle: OpenAI API +description: Connect FastMCP servers to the OpenAI API +icon: message-code +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + +## Responses API + +OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) supports [MCP servers](https://platform.openai.com/docs/guides/tools-remote-mcp) as remote tool sources, allowing you to extend AI capabilities with custom functions. + + +The Responses API is a distinct API from OpenAI's Completions API or Assistants API. At this time, only the Responses API supports MCP. + + + +Currently, the Responses API only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to the AI agent. Other MCP features like resources and prompts are not currently supported. + + + +### Create a Server + +First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice. + +```python server.py +import random +from fastmcp import FastMCP + +mcp = FastMCP(name="Dice Roller") + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + +### Deploy the Server + +Your server must be deployed to a public URL in order for OpenAI to access it. + +For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server. + +Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet: + + +```bash FastMCP server +python server.py +``` + +```bash ngrok +ngrok http 8000 +``` + + + +This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks. + + +### Call the Server + +To use the Responses API, you'll need to install the OpenAI Python SDK (not included with FastMCP): + +```bash +pip install openai +``` + +You'll also need to authenticate with OpenAI. You can do this by setting the `OPENAI_API_KEY` environment variable. Consult the OpenAI SDK documentation for more information. + +```bash +export OPENAI_API_KEY="your-api-key" +``` + +Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment. + +```python {4, 11-16} +from openai import OpenAI + +# Your server URL (replace with your actual URL) +url = 'https://your-server-url.com' + +client = OpenAI() + +resp = client.responses.create( + model="gpt-4.1", + tools=[ + { + "type": "mcp", + "server_label": "dice_server", + "server_url": f"{url}/mcp/", + "require_approval": "never", + }, + ], + input="Roll a few dice!", +) + +print(resp.output_text) +``` +If you run this code, you'll see something like the following output: + +```text +You rolled 3 dice and got the following results: 6, 4, and 2! +``` + +### Authentication + + + +The Responses API can include headers to authenticate the request, which means you don't have to worry about your server being publicly accessible. + +#### Server Authentication + +The simplest way to add authentication to the server is to use a bearer token scheme. + +For this example, we'll quickly generate our own tokens with FastMCP's `RSAKeyPair` utility, but this may not be appropriate for production use. For more details, see the complete server-side [Token Verification](/servers/auth/token-verification) documentation. + +We'll start by creating an RSA key pair to sign and verify tokens. + +```python +from fastmcp.server.auth.providers.jwt import RSAKeyPair + +key_pair = RSAKeyPair.generate() +access_token = key_pair.create_token(audience="dice-server") +``` + + +FastMCP's `RSAKeyPair` utility is for development and testing only. + + +Next, we'll create a `JWTVerifier` to authenticate the server. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth import JWTVerifier + +auth = JWTVerifier( + public_key=key_pair.public_key, + audience="dice-server", +) + +mcp = FastMCP(name="Dice Roller", auth=auth) +``` + +Here is a complete example that you can copy/paste. For simplicity and the purposes of this example only, it will print the token to the console. **Do NOT do this in production!** + +```python server.py [expandable] +from fastmcp import FastMCP +from fastmcp.server.auth import JWTVerifier +from fastmcp.server.auth.providers.jwt import RSAKeyPair +import random + +key_pair = RSAKeyPair.generate() +access_token = key_pair.create_token(audience="dice-server") + +auth = JWTVerifier( + public_key=key_pair.public_key, + audience="dice-server", +) + +mcp = FastMCP(name="Dice Roller", auth=auth) + +@mcp.tool +def roll_dice(n_dice: int) -> list[int]: + """Roll `n_dice` 6-sided dice and return the results.""" + return [random.randint(1, 6) for _ in range(n_dice)] + +if __name__ == "__main__": + print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n") + mcp.run(transport="http", port=8000) +``` + +#### Client Authentication + +If you try to call the authenticated server with the same OpenAI code we wrote earlier, you'll get an error like this: + +```python +pythonAPIStatusError: Error code: 424 - { + "error": { + "message": "Error retrieving tool list from MCP server: 'dice_server'. Http status code: 401 (Unauthorized)", + "type": "external_connector_error", + "param": "tools", + "code": "http_error" + } +} +``` + +As expected, the server is rejecting the request because it's not authenticated. + +To authenticate the client, you can pass the token in the `Authorization` header with the `Bearer` scheme: + + +```python {4, 7, 19-21} [expandable] +from openai import OpenAI + +# Your server URL (replace with your actual URL) +url = 'https://your-server-url.com' + +# Your access token (replace with your actual token) +access_token = 'your-access-token' + +client = OpenAI() + +resp = client.responses.create( + model="gpt-4.1", + tools=[ + { + "type": "mcp", + "server_label": "dice_server", + "server_url": f"{url}/mcp/", + "require_approval": "never", + "headers": { + "Authorization": f"Bearer {access_token}" + } + }, + ], + input="Roll a few dice!", +) + +print(resp.output_text) +``` + +You should now see the dice roll results in the output. \ No newline at end of file diff --git a/docs/v2/integrations/openapi.mdx b/docs/v2/integrations/openapi.mdx new file mode 100644 index 000000000..8662a0c05 --- /dev/null +++ b/docs/v2/integrations/openapi.mdx @@ -0,0 +1,451 @@ +--- +title: OpenAPI 🤝 FastMCP +sidebarTitle: OpenAPI +description: Generate MCP servers from any OpenAPI specification +icon: list-tree +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +FastMCP can automatically generate an MCP server from any OpenAPI specification, allowing AI models to interact with existing APIs through the MCP protocol. Instead of manually creating tools and resources, you provide an OpenAPI spec and FastMCP intelligently converts API endpoints into the appropriate MCP components. + + +Generating MCP servers from OpenAPI is a great way to get started with FastMCP, but in practice LLMs achieve **significantly better performance** with well-designed and curated MCP servers than with auto-converted OpenAPI servers. This is especially true for complex APIs with many endpoints and parameters. + +We recommend using the FastAPI integration for bootstrapping and prototyping, not for mirroring your API to LLM clients. See the post [Stop Converting Your REST APIs to MCP](https://www.jlowin.dev/blog/stop-converting-rest-apis-to-mcp) for more details. + + +## Create a Server + +To convert an OpenAPI specification to an MCP server, use the `FastMCP.from_openapi()` class method: + +```python server.py +import httpx +from fastmcp import FastMCP + +# Create an HTTP client for your API +client = httpx.AsyncClient(base_url="https://api.example.com") + +# Load your OpenAPI spec +openapi_spec = httpx.get("https://api.example.com/openapi.json").json() + +# Create the MCP server +mcp = FastMCP.from_openapi( + openapi_spec=openapi_spec, + client=client, + name="My API Server" +) + +if __name__ == "__main__": + mcp.run() +``` + +### Authentication + +If your API requires authentication, configure it on the HTTP client: + +```python +import httpx +from fastmcp import FastMCP + +# Bearer token authentication +api_client = httpx.AsyncClient( + base_url="https://api.example.com", + headers={"Authorization": "Bearer YOUR_TOKEN"} +) + +# Create MCP server with authenticated client +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=api_client, + timeout=30.0 # 30 second timeout for all requests +) +``` + +## Route Mapping + +By default, FastMCP converts **every endpoint** in your OpenAPI specification into an MCP **Tool**. This provides a simple, predictable starting point that ensures all your API's functionality is immediately available to the vast majority of LLM clients which only support MCP tools. + +While this is a pragmatic default for maximum compatibility, you can easily customize this behavior. Internally, FastMCP uses an ordered list of `RouteMap` objects to determine how to map OpenAPI routes to various MCP component types. + +Each `RouteMap` specifies a combination of methods, patterns, and tags, as well as a corresponding MCP component type. Each OpenAPI route is checked against each `RouteMap` in order, and the first one that matches every criteria is used to determine its converted MCP type. A special type, `EXCLUDE`, can be used to exclude routes from the MCP server entirely. + +- **Methods**: HTTP methods to match (e.g. `["GET", "POST"]` or `"*"` for all) +- **Pattern**: Regex pattern to match the route path (e.g. `r"^/users/.*"` or `r".*"` for all) +- **Tags**: A set of OpenAPI tags that must all be present. An empty set (`{}`) means no tag filtering, so the route matches regardless of its tags. +- **MCP type**: What MCP component type to create (`TOOL`, `RESOURCE`, `RESOURCE_TEMPLATE`, or `EXCLUDE`) +- **MCP tags**: A set of custom tags to add to components created from matching routes + +Here is FastMCP's default rule: + +```python +from fastmcp.server.openapi import RouteMap, MCPType + +DEFAULT_ROUTE_MAPPINGS = [ + # All routes become tools + RouteMap(mcp_type=MCPType.TOOL), +] +``` + +### Custom Route Maps + +When creating your FastMCP server, you can customize routing behavior by providing your own list of `RouteMap` objects. Your custom maps are processed before the default route maps, and routes will be assigned to the first matching custom map. + +For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `Resource` and `ResourceTemplate` components based on whether they had path parameters. (This was changed solely for client compatibility reasons.) You can restore this behavior by providing custom route maps: + +```python +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType + +# Restore pre-2.8.0 semantic mapping +semantic_maps = [ + # GET requests with path parameters become ResourceTemplates + RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE), + # All other GET requests become Resources + RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE), +] + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + route_maps=semantic_maps, +) +``` + +With these maps, `GET` requests are handled semantically, and all other methods (`POST`, `PUT`, etc.) will fall through to the default rule and become `Tool`s. + +Here is a more complete example that uses custom route maps to convert all `GET` endpoints under `/analytics/` to tools while excluding all admin endpoints and all routes tagged "internal". All other routes will be handled by the default rules: + +```python +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + route_maps=[ + # Analytics `GET` endpoints are tools + RouteMap( + methods=["GET"], + pattern=r"^/analytics/.*", + mcp_type=MCPType.TOOL, + ), + + # Exclude all admin endpoints + RouteMap( + pattern=r"^/admin/.*", + mcp_type=MCPType.EXCLUDE, + ), + + # Exclude all routes tagged "internal" + RouteMap( + tags={"internal"}, + mcp_type=MCPType.EXCLUDE, + ), + ], +) +``` + + +The default route maps are always applied after your custom maps, so you do not have to create route maps for every possible route. + + +### Excluding Routes + +To exclude routes from the MCP server, use a route map to assign them to `MCPType.EXCLUDE`. + +You can use this to remove sensitive or internal routes by targeting them specifically: + +```python +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + route_maps=[ + RouteMap(pattern=r"^/admin/.*", mcp_type=MCPType.EXCLUDE), + RouteMap(tags={"internal"}, mcp_type=MCPType.EXCLUDE), + ], +) +``` + +Or you can use a catch-all rule to exclude everything that your maps don't handle explicitly: + +```python +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + route_maps=[ + # custom mapping logic goes here + # ... your specific route maps ... + # exclude all remaining routes + RouteMap(mcp_type=MCPType.EXCLUDE), + ], +) +``` + + +Using a catch-all exclusion rule will prevent the default route mappings from being applied, since it will match every remaining route. This is useful if you want to explicitly allow-list certain routes. + + +### Advanced Route Mapping + + + +For advanced use cases that require more complex logic, you can provide a `route_map_fn` callable. After the route map logic is applied, this function is called on each matched route and its assigned MCP component type. It can optionally return a different component type to override the mapped assignment. If it returns `None`, the assigned type is used. + +In addition to more precise targeting of methods, patterns, and tags, this function can access any additional OpenAPI metadata about the route. + + +The `route_map_fn` is called on all routes, even those that matched `MCPType.EXCLUDE` in your custom maps. This gives you an opportunity to customize the mapping or even override an exclusion. + + +```python +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType, HTTPRoute + +def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None: + """Advanced route type mapping.""" + # Convert all admin routes to tools regardless of HTTP method + if "/admin/" in route.path: + return MCPType.TOOL + + elif "internal" in route.tags: + return MCPType.EXCLUDE + + # Convert user detail routes to templates even if they're POST + elif route.path.startswith("/users/") and route.method == "POST": + return MCPType.RESOURCE_TEMPLATE + + # Use defaults for all other routes + return None + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + route_map_fn=custom_route_mapper, +) +``` + +## Customization + +### Component Names + + + +FastMCP automatically generates names for MCP components based on the OpenAPI specification. By default, it uses the `operationId` from your OpenAPI spec, up to the first double underscore (`__`). + +All component names are automatically: +- **Slugified**: Spaces and special characters are converted to underscores or removed +- **Truncated**: Limited to 56 characters maximum to ensure compatibility +- **Unique**: If multiple components have the same name, a number is automatically appended to make them unique + +For more control over component names, you can provide an `mcp_names` dictionary that maps `operationId` values to your desired names. The `operationId` must be exactly as it appears in the OpenAPI spec. The provided name will always be slugified and truncated. + +```python +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + mcp_names={ + "list_users__with_pagination": "user_list", + "create_user__admin_required": "create_user", + "get_user_details__admin_required": "user_detail", + } +) +``` + +Any `operationId` not found in `mcp_names` will use the default strategy (operationId up to the first `__`). + +### Tags + + + +FastMCP provides several ways to add tags to your MCP components, allowing you to categorize and organize them for better discoverability and filtering. Tags are combined from multiple sources to create the final set of tags on each component. + +#### RouteMap Tags + +You can add custom tags to components created from specific routes using the `mcp_tags` parameter in `RouteMap`. These tags will be applied to all components created from routes that match that particular route map. + +```python +from fastmcp.server.openapi import RouteMap, MCPType + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + route_maps=[ + # Add custom tags to all POST endpoints + RouteMap( + methods=["POST"], + pattern=r".*", + mcp_type=MCPType.TOOL, + mcp_tags={"write-operation", "api-mutation"} + ), + + # Add different tags to detail view endpoints + RouteMap( + methods=["GET"], + pattern=r".*\{.*\}.*", + mcp_type=MCPType.RESOURCE_TEMPLATE, + mcp_tags={"detail-view", "parameterized"} + ), + + # Add tags to list endpoints + RouteMap( + methods=["GET"], + pattern=r".*", + mcp_type=MCPType.RESOURCE, + mcp_tags={"list-data", "collection"} + ), + ], +) +``` + +#### Global Tags + +You can add tags to **all** components by providing a `tags` parameter when creating your MCP server. These global tags will be applied to every component created from your OpenAPI specification. + +```python +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + tags={"api-v2", "production", "external"} +) +``` + +#### OpenAPI Tags in Client Meta + +FastMCP automatically includes OpenAPI tags from your specification in the component's metadata. These tags are available to MCP clients through the `_meta._fastmcp.tags` field, allowing clients to filter and organize components based on the original OpenAPI tagging: + + +```json {5} OpenAPI spec with tags +{ + "paths": { + "/users": { + "get": { + "tags": ["users", "public"], + "operationId": "list_users", + "summary": "List all users" + } + } + } +} +``` +```python {6-9} Access OpenAPI tags in MCP client +async with client: + tools = await client.list_tools() + for tool in tools: + if hasattr(tool, '_meta') and tool._meta: + # OpenAPI tags are now available in _fastmcp namespace! + fastmcp_meta = tool._meta.get('_fastmcp', {}) + openapi_tags = fastmcp_meta.get('tags', []) + if 'users' in openapi_tags: + print(f"Found user-related tool: {tool.name}") +``` + + +This makes it easy for clients to understand and organize API endpoints based on their original OpenAPI categorization. + +### Advanced Customization + + + +By default, FastMCP creates MCP components using a variety of metadata from the OpenAPI spec, such as incorporating the OpenAPI description into the MCP component description. + +At times you may want to modify those MCP components in a variety of ways, such as adding LLM-specific instructions or tags. For fine-grained customization, you can provide a `mcp_component_fn` when creating the MCP server. After each MCP component has been created, this function is called on it and has the opportunity to modify it in-place. + + +Your `mcp_component_fn` is expected to modify the component in-place, not to return a new component. The result of the function is ignored. + + +```python +from fastmcp.server.openapi import ( + HTTPRoute, + OpenAPITool, + OpenAPIResource, + OpenAPIResourceTemplate, +) + +def customize_components( + route: HTTPRoute, + component: OpenAPITool | OpenAPIResource | OpenAPIResourceTemplate, +) -> None: + # Add custom tags to all components + component.tags.add("openapi") + + # Customize based on component type + if isinstance(component, OpenAPITool): + component.description = f"🔧 {component.description} (via API)" + + if isinstance(component, OpenAPIResource): + component.description = f"📊 {component.description}" + component.tags.add("data") + +mcp = FastMCP.from_openapi( + openapi_spec=spec, + client=client, + mcp_component_fn=customize_components, +) +``` + +## Request Parameter Handling + +FastMCP intelligently handles different types of parameters in OpenAPI requests: + +### Query Parameters + +By default, FastMCP only includes query parameters that have non-empty values. Parameters with `None` values or empty strings are automatically filtered out. + +```python +# When calling this tool... +await client.call_tool("search_products", { + "category": "electronics", # ✅ Included + "min_price": 100, # ✅ Included + "max_price": None, # ❌ Excluded + "brand": "", # ❌ Excluded +}) + +# The HTTP request will be: GET /products?category=electronics&min_price=100 +``` + +### Path Parameters + +Path parameters are typically required by REST APIs. FastMCP: +- Filters out `None` values +- Validates that all required path parameters are provided +- Raises clear errors for missing required parameters + +```python +# ✅ This works +await client.call_tool("get_user", {"user_id": 123}) + +# ❌ This raises: "Missing required path parameters: {'user_id'}" +await client.call_tool("get_user", {"user_id": None}) +``` + +### Array Parameters + +FastMCP handles array parameters according to OpenAPI specifications: + +- **Query arrays**: Serialized based on the `explode` parameter (default: `True`) +- **Path arrays**: Serialized as comma-separated values (OpenAPI 'simple' style) + +```python +# Query array with explode=true (default) +# ?tags=red&tags=blue&tags=green + +# Query array with explode=false +# ?tags=red,blue,green + +# Path array (always comma-separated) +# /items/red,blue,green +``` + +### Headers + +Header parameters are automatically converted to strings and included in the HTTP request. \ No newline at end of file diff --git a/docs/v2/integrations/permit.mdx b/docs/v2/integrations/permit.mdx new file mode 100644 index 000000000..066f5b1ea --- /dev/null +++ b/docs/v2/integrations/permit.mdx @@ -0,0 +1,352 @@ +--- +title: Permit.io Authorization 🤝 FastMCP +sidebarTitle: Permit.io +description: Add fine-grained authorization to your FastMCP servers with Permit.io +icon: shield-check +--- + +Add **policy-based authorization** to your FastMCP servers with one-line code addition with the **[Permit.io][permit-github] authorization middleware**. + +Control which tools, resources and prompts MCP clients can view and execute on your server. Define dynamic policies using Permit.io's powerful RBAC, ABAC, and REBAC capabilities, and obtain comprehensive audit logs of all access attempts and violations. + +## How it Works + +Leveraging FastMCP's [Middleware][fastmcp-middleware], the Permit.io middleware intercepts all MCP requests to your server and automatically maps MCP methods to authorization checks against your Permit.io policies; covering both server methods and tool execution. + +### Policy Mapping + +The middleware automatically maps MCP methods to Permit.io resources and actions: + +- **MCP server methods** (e.g., `tools/list`, `resources/read`): + - **Resource**: `{server_name}_{component}` (e.g., `myserver_tools`) + - **Action**: The method verb (e.g., `list`, `read`) +- **Tool execution** (method `tools/call`): + - **Resource**: `{server_name}` (e.g., `myserver`) + - **Action**: The tool name (e.g., `greet`) + +![Permit.io Policy Mapping Example](./images/permit/policy_mapping.png) + +*Example: In Permit.io, the 'Admin' role is granted permissions on resources and actions as mapped by the middleware. For example, 'greet', 'greet-jwt', and 'login' are actions on the 'mcp_server' resource, and 'list' is an action on the 'mcp_server_tools' resource.* + +> **Note:** +> Don't forget to assign the relevant role (e.g., Admin, User) to the user authenticating to your MCP server (such as the user in the JWT) in the Permit.io Directory. Without the correct role assignment, users will not have access to the resources and actions you've configured in your policies. +> +> ![Permit.io Directory Role Assignment Example](./images/permit/role_assignement.png) +> +> *Example: In Permit.io Directory, both 'client' and 'admin' users are assigned the 'Admin' role, granting them the permissions defined in your policy mapping.* + +For detailed policy mapping examples and configuration, see [Detailed Policy Mapping](https://github.com/permitio/permit-fastmcp/blob/main/docs/policy-mapping.md). + +### Listing Operations + +The middleware behaves as a filter for listing operations (`tools/list`, `resources/list`, `prompts/list`), hiding to the client components that are not authorized by the defined policies. + +```mermaid +sequenceDiagram + participant MCPClient as MCP Client + participant PermitMiddleware as Permit.io Middleware + participant MCPServer as FastMCP Server + participant PermitPDP as Permit.io PDP + + MCPClient->>PermitMiddleware: MCP Listing Request (e.g., tools/list) + PermitMiddleware->>MCPServer: MCP Listing Request + MCPServer-->>PermitMiddleware: MCP Listing Response + PermitMiddleware->>PermitPDP: Authorization Checks + PermitPDP->>PermitMiddleware: Authorization Decisions + PermitMiddleware-->>MCPClient: Filtered MCP Listing Response +``` + +### Execution Operations + +The middleware behaves as an enforcement point for execution operations (`tools/call`, `resources/read`, `prompts/get`), blocking operations that are not authorized by the defined policies. + +```mermaid +sequenceDiagram + participant MCPClient as MCP Client + participant PermitMiddleware as Permit.io Middleware + participant MCPServer as FastMCP Server + participant PermitPDP as Permit.io PDP + + MCPClient->>PermitMiddleware: MCP Execution Request (e.g., tools/call) + PermitMiddleware->>PermitPDP: Authorization Check + PermitPDP->>PermitMiddleware: Authorization Decision + PermitMiddleware-->>MCPClient: MCP Unauthorized Error (if denied) + PermitMiddleware->>MCPServer: MCP Execution Request (if allowed) + MCPServer-->>PermitMiddleware: MCP Execution Response (if allowed) + PermitMiddleware-->>MCPClient: MCP Execution Response (if allowed) +``` + +## Add Authorization to Your Server + + +Permit.io is a cloud-native authorization service. You need a Permit.io account and a running Policy Decision Point (PDP) for the middleware to function. You can run the PDP locally with Docker or use Permit.io's cloud PDP. + + +### Prerequisites + +1. **Permit.io Account**: Sign up at [permit.io](https://permit.io) +2. **PDP Setup**: Run the Permit.io PDP locally or use the cloud PDP (RBAC only) +3. **API Key**: Get your Permit.io API key from the dashboard + +### Run the Permit.io PDP + +Run the PDP locally with Docker: + +```bash +docker run -p 7766:7766 permitio/pdp:latest +``` + +Or use the cloud PDP URL: `https://cloudpdp.api.permit.io` + +### Create a Server with Authorization + +First, install the `permit-fastmcp` package: + +```bash +# Using UV (recommended) +uv add permit-fastmcp + +# Using pip +pip install permit-fastmcp +``` + +Then create a FastMCP server and add the Permit.io middleware: + +```python server.py +from fastmcp import FastMCP +from permit_fastmcp.middleware.middleware import PermitMcpMiddleware + +mcp = FastMCP("Secure FastMCP Server 🔒") + +@mcp.tool +def greet(name: str) -> str: + """Greet a user by name""" + return f"Hello, {name}!" + +@mcp.tool +def add(a: int, b: int) -> int: + """Add two numbers""" + return a + b + +# Add Permit.io authorization middleware +mcp.add_middleware(PermitMcpMiddleware( + permit_pdp_url="http://localhost:7766", + permit_api_key="your-permit-api-key" +)) + +if __name__ == "__main__": + mcp.run(transport="http") +``` + +### Configure Access Policies + +Create your authorization policies in the Permit.io dashboard: + +1. **Create Resources**: Define resources like `mcp_server` and `mcp_server_tools` +2. **Define Actions**: Add actions like `greet`, `add`, `list`, `read` +3. **Create Roles**: Define roles like `Admin`, `User`, `Guest` +4. **Assign Permissions**: Grant roles access to specific resources and actions +5. **Assign Users**: Assign roles to users in the Permit.io Directory + +For step-by-step setup instructions and troubleshooting, see [Getting Started & FAQ](https://github.com/permitio/permit-fastmcp/blob/main/docs/getting-started.md). + +#### Example Policy Configuration + +Policies are defined in the Permit.io dashboard, but you can also use the [Permit.io Terraform provider](https://github.com/permitio/terraform-provider-permitio) to define policies in code. + + +```terraform +# Resources +resource "permitio_resource" "mcp_server" { + name = "mcp_server" + key = "mcp_server" + + actions = { + "greet" = { name = "greet" } + "add" = { name = "add" } + } +} + +resource "permitio_resource" "mcp_server_tools" { + name = "mcp_server_tools" + key = "mcp_server_tools" + + actions = { + "list" = { name = "list" } + } +} + +# Roles +resource "permitio_role" "Admin" { + key = "Admin" + name = "Admin" + permissions = [ + "mcp_server:greet", + "mcp_server:add", + "mcp_server_tools:list" + ] +} +``` + +You can also use the [Permit.io CLI](https://github.com/permitio/permit-cli), [API](https://api.permit.io/scalar) or [SDKs](https://github.com/permitio/permit-python) to manage policies, as well as writing policies directly in REGO (Open Policy Agent's policy language). + +For complete policy examples including ABAC and RBAC configurations, see [Example Policies](https://github.com/permitio/permit-fastmcp/tree/main/docs/example_policies). + +### Identity Management + +The middleware supports multiple identity extraction modes: + +- **Fixed Identity**: Use a fixed identity for all requests +- **Header-based**: Extract identity from HTTP headers +- **JWT-based**: Extract and verify JWT tokens +- **Source-based**: Use the MCP context source field + +For detailed identity mode configuration and environment variables, see [Identity Modes & Environment Variables](https://github.com/permitio/permit-fastmcp/blob/main/docs/identity-modes.md). + +#### JWT Authentication Example + +```python +import os + +# Configure JWT identity extraction +os.environ["PERMIT_MCP_IDENTITY_MODE"] = "jwt" +os.environ["PERMIT_MCP_IDENTITY_JWT_SECRET"] = "your-jwt-secret" + +mcp.add_middleware(PermitMcpMiddleware( + permit_pdp_url="http://localhost:7766", + permit_api_key="your-permit-api-key" +)) +``` + +### ABAC Policies with Tool Arguments + +The middleware supports Attribute-Based Access Control (ABAC) policies that can evaluate tool arguments as attributes. Tool arguments are automatically flattened as individual attributes (e.g., `arg_name`, `arg_number`) for granular policy conditions. + +![ABAC Condition Example](./images/permit/abac_condition_example.png) + +*Example: Create dynamic resources with conditions like `resource.arg_number greater-than 10` to allow the `conditional-greet` tool only when the number argument exceeds 10.* + +#### Example: Conditional Access + +Create a dynamic resource with conditions like `resource.arg_number greater-than 10` to allow the `conditional-greet` tool only when the number argument exceeds 10. + +```python +@mcp.tool +def conditional_greet(name: str, number: int) -> str: + """Greet a user only if number > 10""" + return f"Hello, {name}! Your number is {number}" +``` + +![ABAC Policy Example](./images/permit/abac_policy_example.png) + +*Example: The Admin role is granted access to the "conditional-greet" action on the "Big-greets" dynamic resource, while other tools like "greet", "greet-jwt", and "login" are granted on the base "mcp_server" resource.* + +For comprehensive ABAC configuration and advanced policy examples, see [ABAC Policies with Tool Arguments](https://github.com/permitio/permit-fastmcp/blob/main/docs/policy-mapping.md#abac-policies-with-tool-arguments). + +### Run the Server + +Start your FastMCP server normally: + +```bash +python server.py +``` + +The middleware will now intercept all MCP requests and check them against your Permit.io policies. Requests include user identification through the configured identity mode and automatic mapping of MCP methods to authorization resources and actions. + +## Advanced Configuration + +### Environment Variables + +Configure the middleware using environment variables: + +```bash +# Permit.io configuration +export PERMIT_MCP_PERMIT_PDP_URL="http://localhost:7766" +export PERMIT_MCP_PERMIT_API_KEY="your-api-key" + +# Identity configuration +export PERMIT_MCP_IDENTITY_MODE="jwt" +export PERMIT_MCP_IDENTITY_JWT_SECRET="your-jwt-secret" + +# Method configuration +export PERMIT_MCP_KNOWN_METHODS='["tools/list","tools/call"]' +export PERMIT_MCP_BYPASSED_METHODS='["initialize","ping"]' + +# Logging configuration +export PERMIT_MCP_ENABLE_AUDIT_LOGGING="true" +``` + +For a complete list of all configuration options and environment variables, see [Configuration Reference](https://github.com/permitio/permit-fastmcp/blob/main/docs/configuration-reference.md). + +### Custom Middleware Configuration + +```python +from permit_fastmcp.middleware.middleware import PermitMcpMiddleware + +middleware = PermitMcpMiddleware( + permit_pdp_url="http://localhost:7766", + permit_api_key="your-api-key", + enable_audit_logging=True, + bypass_methods=["initialize", "ping", "health/*"] +) + +mcp.add_middleware(middleware) +``` + +For advanced configuration options and custom middleware extensions, see [Advanced Configuration](https://github.com/permitio/permit-fastmcp/blob/main/docs/advanced-configuration.md). + +## Example: Complete JWT Authentication Server + +See the [example server](https://github.com/permitio/permit-fastmcp/blob/main/permit_fastmcp/example_server/example.py) for a full implementation with JWT-based authentication. For additional examples and usage patterns, see [Example Server](https://github.com/permitio/permit-fastmcp/blob/main/permit_fastmcp/example_server/): + +```python +from fastmcp import FastMCP, Context +from permit_fastmcp.middleware.middleware import PermitMcpMiddleware +import jwt +import datetime + +# Configure JWT identity extraction +os.environ["PERMIT_MCP_IDENTITY_MODE"] = "jwt" +os.environ["PERMIT_MCP_IDENTITY_JWT_SECRET"] = "mysecretkey" + +mcp = FastMCP("My MCP Server") + +@mcp.tool +def login(username: str, password: str) -> str: + """Login to get a JWT token""" + if username == "admin" and password == "password": + token = jwt.encode( + {"sub": username, "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)}, + "mysecretkey", + algorithm="HS256" + ) + return f"Bearer {token}" + raise Exception("Invalid credentials") + +@mcp.tool +def greet_jwt(ctx: Context) -> str: + """Greet a user by extracting their name from JWT""" + # JWT extraction handled by middleware + return "Hello, authenticated user!" + +mcp.add_middleware(PermitMcpMiddleware( + permit_pdp_url="http://localhost:7766", + permit_api_key="your-permit-api-key" +)) + +if __name__ == "__main__": + mcp.run(transport="http") +``` + + + For detailed policy configuration, custom authentication, and advanced + deployment patterns, visit the [Permit.io FastMCP Middleware + repository][permit-fastmcp-github]. For troubleshooting common issues, see [Troubleshooting](https://github.com/permitio/permit-fastmcp/blob/main/docs/troubleshooting.md). + + + +[permit.io]: https://www.permit.io +[permit-github]: https://github.com/permitio +[permit-fastmcp-github]: https://github.com/permitio/permit-fastmcp +[Agent.Security]: https://agent.security +[fastmcp-middleware]: /servers/middleware diff --git a/docs/v2/integrations/scalekit.mdx b/docs/v2/integrations/scalekit.mdx new file mode 100644 index 000000000..802fe16ef --- /dev/null +++ b/docs/v2/integrations/scalekit.mdx @@ -0,0 +1,194 @@ +--- +title: Scalekit 🤝 FastMCP +sidebarTitle: Scalekit +description: Secure your FastMCP server with Scalekit +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +Install auth stack to your FastMCP server with [Scalekit](https://scalekit.com) using the [Remote OAuth](/servers/auth/remote-oauth) pattern: Scalekit handles user authentication, and the MCP server validates issued tokens. + +### Prerequisites + +Before you begin + +1. Get a [Scalekit account](https://app.scalekit.com/) and grab your **Environment URL** from _Dashboard > Settings_ . +2. Have your FastMCP server's base URL ready (can be localhost for development, e.g., `http://localhost:8000/`) + +### Step 1: Configure MCP server in Scalekit environment + + + + +In your Scalekit dashboard: + 1. Open the **MCP Servers** section, then select **Create new server** + 2. Enter server details: a name, a resource identifier, and the desired MCP client authentication settings + 3. Save, then copy the **Resource ID** (for example, res_92015146095) + +In your FastMCP project's `.env`: + +```sh +SCALEKIT_ENVIRONMENT_URL= +SCALEKIT_RESOURCE_ID= # res_926EXAMPLE5878 +BASE_URL=http://localhost:8000/ +# Optional: additional scopes tokens must have +# SCALEKIT_REQUIRED_SCOPES=read,write +``` + + + + +### Step 2: Add auth to FastMCP server + +Create your FastMCP server file and use the ScalekitProvider to handle all the OAuth integration automatically: + +> **Warning:** The legacy `mcp_url` and `client_id` parameters are deprecated and will be removed in a future release. Use `base_url` instead of `mcp_url` and remove `client_id` from your configuration. + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.scalekit import ScalekitProvider + +# Discovers Scalekit endpoints and set up JWT token validation +auth_provider = ScalekitProvider( + environment_url=SCALEKIT_ENVIRONMENT_URL, # Scalekit environment URL + resource_id=SCALEKIT_RESOURCE_ID, # Resource server ID + base_url=SERVER_URL, # Public MCP endpoint + required_scopes=["read"], # Optional scope enforcement +) + +# Create FastMCP server with auth +mcp = FastMCP(name="My Scalekit Protected Server", auth=auth_provider) + +@mcp.tool +def auth_status() -> dict: + """Show Scalekit authentication status.""" + # Extract user claims from the JWT + return { + "message": "This tool requires authentication via Scalekit", + "authenticated": True, + "provider": "Scalekit" + } + +``` + + +Set `required_scopes` when you need tokens to carry specific permissions. Leave it unset to allow any token issued for the resource. + + +## Testing + +### Start the MCP server + +```sh +uv run python server.py +``` + +Use any MCP client (for example, mcp-inspector, Claude, VS Code, or Windsurf) to connect to the running serve. Verify that authentication succeeds and requests are authorized as expected. + +### Provider selection + +Setting this environment variable allows the Scalekit provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.scalekit.ScalekitProvider` to use Scalekit authentication. + + + +### Scalekit-specific configuration + +These environment variables provide default values for the Scalekit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Scalekit environment URL from the Admin Portal (e.g., `https://your-env.scalekit.com`) + + + +Your Scalekit resource server ID from the MCP Servers section + + + +Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000/` for development) + + +Legacy `FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL` is still recognized for backward compatibility but will be removed soon-rename it to `...BASE_URL`. + + +Comma-, space-, or JSON-separated list of scopes that tokens must include to access your server + + + +Example `.env`: + +```bash +# Use the Scalekit provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.scalekit.ScalekitProvider + +# Scalekit configuration +FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL=https://your-env.scalekit.com +FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID=res_456 +FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_BASE_URL=https://your-server.com/ +# FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_REQUIRED_SCOPES=read,write +# FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL=https://your-server.com/ # Deprecated +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="My Scalekit Protected Server") + +@mcp.tool +def protected_action() -> str: + """A tool that requires authentication.""" + return "Access granted via Scalekit!" +``` + +## Capabilities + +Scalekit supports OAuth 2.1 with Dynamic Client Registration for MCP clients and enterprise SSO, and provides built‑in JWT validation and security controls. + +**OAuth 2.1/DCR**: clients self‑register, use PKCE, and work with the Remote OAuth pattern without pre‑provisioned credentials. + +**Validation and SSO**: tokens are verified (keys, RS256, issuer, audience, expiry), and SAML, OIDC, OAuth 2.0, ADFS, Azure AD, and Google Workspace are supported; use HTTPS in production and review auth logs as needed. + +## Debugging + +Enable detailed logging to troubleshoot authentication issues: + +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +### Token inspection + +You can inspect JWT tokens in your tools to understand the user context: + +```python +from fastmcp.server.context import request_ctx +import jwt + +@mcp.tool +def inspect_token() -> dict: + """Inspect the current JWT token claims.""" + context = request_ctx.get() + + # Extract token from Authorization header + if hasattr(context, 'request') and hasattr(context.request, 'headers'): + auth_header = context.request.headers.get('authorization', '') + if auth_header.startswith('Bearer '): + token = auth_header[7:] + # Decode without verification (already verified by provider) + claims = jwt.decode(token, options={"verify_signature": False}) + return claims + + return {"error": "No token found"} +``` diff --git a/docs/v2/integrations/supabase.mdx b/docs/v2/integrations/supabase.mdx new file mode 100644 index 000000000..b98c9cb7d --- /dev/null +++ b/docs/v2/integrations/supabase.mdx @@ -0,0 +1,148 @@ +--- +title: Supabase 🤝 FastMCP +sidebarTitle: Supabase +description: Secure your FastMCP server with Supabase Auth +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +This guide shows you how to secure your FastMCP server using **Supabase Auth**. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where Supabase handles user authentication and your FastMCP server validates the tokens. + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. A **[Supabase Account](https://supabase.com/)** with a project or a self-hosted **Supabase Auth** instance +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Get Supabase Project URL + +In your Supabase Dashboard: +1. Go to **Project Settings** +2. Copy your **Project URL** (e.g., `https://abc123.supabase.co`) + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `SupabaseProvider`: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.supabase import SupabaseProvider + +# Configure Supabase Auth +auth = SupabaseProvider( + project_url="https://abc123.supabase.co", + base_url="http://localhost:8000", + auth_route="/my/auth/route" # if self-hosting and using custom routes +) + +mcp = FastMCP("Supabase Protected Server", auth=auth) + +@mcp.tool +def protected_tool(message: str) -> str: + """This tool requires authentication.""" + return f"Authenticated user says: {message}" + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by Supabase authentication. + +### Testing with a Client + +Create a test client that authenticates with your Supabase-protected server: + +```python client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle Supabase OAuth + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open Supabase login in your browser + print("✓ Authenticated with Supabase!") + + # Test the protected tool + result = await client.call_tool("protected_tool", {"message": "Hello!"}) + print(result) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to Supabase's authorization page +2. After you authorize, you'll be redirected back +3. The client receives the token and can make authenticated requests + +## Environment Variables + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the Supabase provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.supabase.SupabaseProvider` to use Supabase authentication. + + + +### Supabase-Specific Configuration + +These environment variables provide default values for the Supabase provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your Supabase project URL (e.g., `https://abc123.supabase.co`) + + + +Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development) + + + +Your Supabase auth route (e.g., `/auth/v1`) + + + +Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid", "email"]`) + + + +Example `.env` file: +```bash +# Use the Supabase provider +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.supabase.SupabaseProvider + +# Supabase configuration +FASTMCP_SERVER_AUTH_SUPABASE_PROJECT_URL=https://abc123.supabase.co +FASTMCP_SERVER_AUTH_SUPABASE_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_SUPABASE_REQUIRED_SCOPES=openid,email +``` + +With environment variables set, your server code simplifies to: + +```python server.py +from fastmcp import FastMCP + +# Authentication is automatically configured from environment +mcp = FastMCP(name="Supabase Protected Server") +``` diff --git a/docs/v2/integrations/workos.mdx b/docs/v2/integrations/workos.mdx new file mode 100644 index 000000000..190050c9a --- /dev/null +++ b/docs/v2/integrations/workos.mdx @@ -0,0 +1,288 @@ +--- +title: WorkOS 🤝 FastMCP +sidebarTitle: WorkOS +description: Authenticate FastMCP servers with WorkOS Connect +icon: shield-check +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +Secure your FastMCP server with WorkOS Connect authentication. This integration uses the OAuth Proxy pattern to handle authentication through WorkOS Connect while maintaining compatibility with MCP clients. + + +This guide covers WorkOS Connect applications. For Dynamic Client Registration (DCR) with AuthKit, see the [AuthKit integration](/integrations/authkit) instead. + + +## Configuration + +### Prerequisites + +Before you begin, you will need: +1. A **[WorkOS Account](https://workos.com/)** with access to create OAuth Apps +2. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`) + +### Step 1: Create a WorkOS OAuth App + +Create an OAuth App in your WorkOS dashboard to get the credentials needed for authentication: + + + +In your WorkOS dashboard: +1. Navigate to **Applications** +2. Click **Create Application** +3. Select **OAuth Application** +4. Name your application + + + +In your OAuth application settings: +1. Copy your **Client ID** (starts with `client_`) +2. Click **Generate Client Secret** and save it securely +3. Copy your **AuthKit Domain** (e.g., `https://your-app.authkit.app`) + + + +In the **Redirect URIs** section: +- Add: `http://localhost:8000/auth/callback` (for development) +- For production, add your server's public URL + `/auth/callback` + + +The callback URL must match exactly. The default path is `/auth/callback`, but you can customize it using the `redirect_path` parameter. + + + + +### Step 2: FastMCP Configuration + +Create your FastMCP server using the `WorkOSProvider`: + +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.workos import WorkOSProvider + +# Configure WorkOS OAuth +auth = WorkOSProvider( + client_id="client_YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + authkit_domain="https://your-app.authkit.app", + base_url="http://localhost:8000", + required_scopes=["openid", "profile", "email"] +) + +mcp = FastMCP("WorkOS Protected Server", auth=auth) + +@mcp.tool +def protected_tool(message: str) -> str: + """This tool requires authentication.""" + return f"Authenticated user says: {message}" + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + +## Testing + +### Running the Server + +Start your FastMCP server with HTTP transport to enable OAuth flows: + +```bash +fastmcp run server.py --transport http --port 8000 +``` + +Your server is now running and protected by WorkOS OAuth authentication. + +### Testing with a Client + +Create a test client that authenticates with your WorkOS-protected server: + +```python client.py +from fastmcp import Client +import asyncio + +async def main(): + # The client will automatically handle WorkOS OAuth + async with Client("http://localhost:8000/mcp", auth="oauth") as client: + # First-time connection will open WorkOS login in your browser + print("✓ Authenticated with WorkOS!") + + # Test the protected tool + result = await client.call_tool("protected_tool", {"message": "Hello!"}) + print(result) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +When you run the client for the first time: +1. Your browser will open to WorkOS's authorization page +2. After you authorize the app, you'll be redirected back +3. The client receives the token and can make authenticated requests + + +The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache. + + +## Production Configuration + + + +For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`: + +```python server.py +import os +from fastmcp import FastMCP +from fastmcp.server.auth.providers.workos import WorkOSProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +# Production setup with encrypted persistent token storage +auth = WorkOSProvider( + client_id="client_YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + authkit_domain="https://your-app.authkit.app", + base_url="https://your-production-domain.com", + required_scopes=["openid", "profile", "email"], + + # Production token management + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore( + host=os.environ["REDIS_HOST"], + port=int(os.environ["REDIS_PORT"]) + ), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) + +mcp = FastMCP(name="Production WorkOS App", auth=auth) +``` + + +Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments. + +For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters). + + +## Environment Variables + + + +For production deployments, use environment variables instead of hardcoding credentials. + +### Provider Selection + +Setting this environment variable allows the WorkOS provider to be used automatically without explicitly instantiating it in code. + + + +Set to `fastmcp.server.auth.providers.workos.WorkOSProvider` to use WorkOS authentication. + + + +### WorkOS-Specific Configuration + +These environment variables provide default values for the WorkOS provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. + + + +Your WorkOS OAuth App Client ID (e.g., `client_01K33Y6GGS7T3AWMPJWKW42Y3Q`) + + + +Your WorkOS OAuth App Client Secret + + + +Your WorkOS AuthKit domain (e.g., `https://your-app.authkit.app`) + + + +Public URL where OAuth endpoints will be accessible (includes any mount path) + + + +Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. + + + +Redirect path configured in your WorkOS OAuth App + + + +Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid","profile","email"]`) + + + +HTTP request timeout for WorkOS API calls + + + +Example `.env` file: +```bash +# WorkOS OAuth credentials (always used as defaults) +FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID=client_01K33Y6GGS7T3AWMPJWKW42Y3Q +FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET=your_client_secret +FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN=https://your-app.authkit.app +FASTMCP_SERVER_AUTH_WORKOS_BASE_URL=https://your-server.com +FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES=["openid","profile","email"] + +# Optional: Automatically provision WorkOS auth for all servers +FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSProvider +``` + +With environment variables set, you can either: + +**Option 1: Manual instantiation (env vars provide defaults)** +```python server.py +from fastmcp import FastMCP +from fastmcp.server.auth.providers.workos import WorkOSProvider + +# Env vars provide default values for WorkOSProvider() +auth = WorkOSProvider() # Uses env var defaults +mcp = FastMCP(name="WorkOS Protected Server", auth=auth) +``` + +**Option 2: Automatic provisioning (requires FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSProvider)** +```python server.py +from fastmcp import FastMCP + +# Auth is automatically provisioned from FASTMCP_SERVER_AUTH +mcp = FastMCP(name="WorkOS Protected Server") +``` + +## Configuration Options + + + +WorkOS OAuth application client ID + + + +WorkOS OAuth application client secret + + + +Your WorkOS AuthKit domain URL (e.g., `https://your-app.authkit.app`) + + + +Your FastMCP server's public URL + + + +OAuth scopes to request + + + +OAuth callback path + + + +API request timeout + + \ No newline at end of file diff --git a/docs/v2/patterns/cli.mdx b/docs/v2/patterns/cli.mdx new file mode 100644 index 000000000..f157e9c2d --- /dev/null +++ b/docs/v2/patterns/cli.mdx @@ -0,0 +1,577 @@ +--- +title: FastMCP CLI +sidebarTitle: CLI +description: Learn how to use the FastMCP command-line interface +icon: terminal +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + +FastMCP provides a command-line interface (CLI) that makes it easy to run, develop, and install your MCP servers. The CLI is automatically installed when you install FastMCP. + +```bash +fastmcp --help +``` + +## Commands Overview + +| Command | Purpose | Dependency Management | +| ------- | ------- | --------------------- | +| `run` | Run a FastMCP server directly | **Supports:** Local files, factory functions, URLs, fastmcp.json configs, MCP configs. **Deps:** Uses your local environment directly. With `--python`, `--with`, `--project`, or `--with-requirements`: Runs via `uv run` subprocess. With fastmcp.json: Automatically manages dependencies based on configuration | +| `dev` | Run a server with the MCP Inspector for testing | **Supports:** Local files and fastmcp.json configs. **Deps:** Always runs via `uv run` subprocess (never uses your local environment); dependencies must be specified or available in a uv-managed project. With fastmcp.json: Uses configured dependencies | +| `install` | Install a server in MCP client applications | **Supports:** Local files and fastmcp.json configs. **Deps:** Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable`. With fastmcp.json: Uses configured dependencies | +| `inspect` | Generate a JSON report about a FastMCP server | **Supports:** Local files and fastmcp.json configs. **Deps:** Uses your current environment; you are responsible for ensuring all dependencies are available | +| `project prepare` | Create a persistent uv project from fastmcp.json environment config | **Supports:** fastmcp.json configs only. **Deps:** Creates a uv project directory with all dependencies pre-installed for reuse with `--project` flag | +| `version` | Display version information | N/A | + +## `fastmcp run` + +Run a FastMCP server directly or proxy a remote server. + +```bash +fastmcp run server.py +``` + + +By default, this command runs the server directly in your current Python environment. You are responsible for ensuring all dependencies are available. When using `--python`, `--with`, `--project`, or `--with-requirements` options, it runs the server via `uv run` subprocess instead. + + +### Options + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `http`, or `sse`) | +| Host | `--host` | Host to bind to when using http transport (default: 127.0.0.1) | +| Port | `--port`, `-p` | Port to bind to when using http transport (default: 8000) | +| Path | `--path` | Path to bind to when using http transport (default: `/mcp/` or `/sse/` for SSE) | +| Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) | +| No Banner | `--no-banner` | Disable the startup banner display | +| No Environment | `--skip-env` | Skip environment setup with uv (use when already in a uv environment) | +| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) | +| Additional Packages | `--with` | Additional packages to install (can be used multiple times) | +| Project Directory | `--project` | Run the command within the given project directory | +| Requirements File | `--with-requirements` | Requirements file to install dependencies from | + + +### Entrypoints + + +The `fastmcp run` command supports the following entrypoints: + +1. **[Inferred server instance](#inferred-server-instance)**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found. +2. **[Explicit server entrypoint](#explicit-server-entrypoint)**: `server.py:custom_name` - imports and uses the specified server entrypoint +3. **[Factory function](#factory-function)**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance +4. **[Remote server proxy](#remote-server-proxy)**: `https://example.com/mcp-server` - connects to a remote server and creates a **local proxy server** +5. **[FastMCP configuration file](#fastmcp-configuration)**: `fastmcp.json` - runs servers using FastMCP's declarative configuration format (auto-detects files in current directory) +6. **MCP configuration file**: `mcp.json` - runs servers defined in a standard MCP configuration file + + +Note: When using `fastmcp run` with a local file, it **completely ignores** the `if __name__ == "__main__"` block. This means: +- Any setup code in `__main__` will NOT run +- Server configuration in `__main__` is bypassed +- `fastmcp run` finds your server entrypoint/factory and runs it with its own transport settings + +If you need setup code to run, use the **factory pattern** instead. + + +#### Inferred Server Instance + +If you provide a path to a file, `fastmcp run` will load the file and look for a FastMCP server instance stored as a variable named `mcp`, `server`, or `app`. If no such object is found, it will raise an error. + +For example, if you have a file called `server.py` with the following content: + +```python server.py +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") +``` + +You can run it with: + +```bash +fastmcp run server.py +``` + +#### Explicit Server Entrypoint + +If your server is stored as a variable with a custom name, or you want to be explicit about which server to run, you can use the following syntax to load a specific server entrypoint: + +```bash +fastmcp run server.py:custom_name +``` + +For example, if you have a file called `server.py` with the following content: + +```python +from fastmcp import FastMCP + +my_server = FastMCP("CustomServer") + +@my_server.tool +def hello() -> str: + return "Hello from custom server!" +``` + +You can run it with: + +```bash +fastmcp run server.py:my_server +``` + +#### Factory Function + + +Since `fastmcp run` ignores the `if __name__ == "__main__"` block, you can use a factory function to run setup code before your server starts. Factory functions are called without any arguments and must return a FastMCP server instance. Both sync and async factory functions are supported. + +The syntax for using a factory function is the same as for an explicit server entrypoint: `fastmcp run server.py:factory_fn`. FastMCP will automatically detect that you have identified a function rather than a server Instance + +For example, if you have a file called `server.py` with the following content: + +```python +from fastmcp import FastMCP + +async def create_server() -> FastMCP: + mcp = FastMCP("MyServer") + + @mcp.tool + def add(x: int, y: int) -> int: + return x + y + + # Setup that runs with fastmcp run + tool = await mcp.get_tool("add") + tool.disable() + + return mcp +``` + +You can run it with: + +```bash +fastmcp run server.py:create_server +``` + +#### Remote Server Proxy + +FastMCP run can also start a local proxy server that connects to a remote server. This is useful when you want to run a remote server locally for testing or development purposes, or to use with a client that doesn't support direct connections to remote servers. + +To start a local proxy, you can use the following syntax: + +```bash +fastmcp run https://example.com/mcp +``` + +#### FastMCP Configuration + + +FastMCP supports declarative configuration through `fastmcp.json` files. When you run `fastmcp run` without arguments, it automatically looks for a `fastmcp.json` file in the current directory: + +```bash +# Auto-detect fastmcp.json in current directory +fastmcp run + +# Or explicitly specify a configuration file +fastmcp run my-config.fastmcp.json +``` + +The configuration file handles dependencies, environment variables, and transport settings. Command-line arguments override configuration file values: + +```bash +# Override port from config file +fastmcp run fastmcp.json --port 8080 + +# Skip environment setup when already in a uv environment +fastmcp run fastmcp.json --skip-env +``` + + +The `--skip-env` flag is useful when: +- You're already in an activated virtual environment +- You're inside a Docker container with pre-installed dependencies +- You're in a uv-managed environment (prevents infinite recursion) +- You want to test the server without environment setup + + +See [Server Configuration](/deployment/server-configuration) for detailed documentation on fastmcp.json. + +#### MCP Configuration + +FastMCP can also run servers defined in a standard MCP configuration file. This is useful when you want to run multiple servers from a single file, or when you want to use a client that doesn't support direct connections to remote servers. + +To run a MCP configuration file, you can use the following syntax: + +```bash +fastmcp run mcp.json +``` + +This will run all the servers defined in the file. + +## `fastmcp dev` + +Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing. + +```bash +fastmcp dev server.py +``` + + +This command always runs your server via `uv run` subprocess (never your local environment) to work with the MCP Inspector. Dependencies can be: +- Specified using `--with` and/or `--with-editable` options +- Defined in a `fastmcp.json` configuration file +- Available in a uv-managed project + +When using `fastmcp.json`, the dev command automatically uses the configured dependencies. + + + +The `dev` command is a shortcut for testing a server over STDIO only. When the Inspector launches, you may need to: +1. Select "STDIO" from the transport dropdown +2. Connect manually + +This command does not support HTTP testing. To test a server over Streamable HTTP or SSE: +1. Start your server manually with the appropriate transport using either the command line: + ```bash + fastmcp run server.py --transport http + ``` + or by setting the transport in your code: + ```bash + python server.py # Assuming your __main__ block sets Streamable HTTP transport + ``` +2. Open the MCP Inspector separately and connect to your running server + + +### Options + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Editable Package | `--with-editable`, `-e` | Directory containing pyproject.toml to install in editable mode | +| Additional Packages | `--with` | Additional packages to install (can be used multiple times) | +| Inspector Version | `--inspector-version` | Version of the MCP Inspector to use | +| UI Port | `--ui-port` | Port for the MCP Inspector UI | +| Server Port | `--server-port` | Port for the MCP Inspector Proxy server | +| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) | +| Project Directory | `--project` | Run the command within the given project directory | +| Requirements File | `--with-requirements` | Requirements file to install dependencies from | + +### Entrypoints + +The `dev` command supports local FastMCP server files and configuration: + +1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found. +2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint +3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance +4. **FastMCP configuration**: `fastmcp.json` - uses FastMCP's declarative configuration (auto-detects in current directory) + + +The `dev` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files. + + +**Examples** + +```bash +# Run dev server with editable mode and additional packages +fastmcp dev server.py -e . --with pandas --with matplotlib + +# Run dev server with fastmcp.json configuration (auto-detects) +fastmcp dev + +# Run dev server with explicit fastmcp.json file +fastmcp dev dev.fastmcp.json + +# Run dev server with specific Python version +fastmcp dev server.py --python 3.11 + +# Run dev server with requirements file +fastmcp dev server.py --with-requirements requirements.txt + +# Run dev server within a specific project directory +fastmcp dev server.py --project /path/to/project +``` + +## `fastmcp install` + + +Install a MCP server in MCP client applications. FastMCP currently supports the following clients: + +- **Claude Code** - Installs via Claude Code's built-in MCP management system +- **Claude Desktop** - Installs via direct configuration file modification +- **Cursor** - Installs via deeplink that opens Cursor for user confirmation +- **MCP JSON** - Generates standard MCP JSON configuration for manual use + +```bash +fastmcp install claude-code server.py +fastmcp install claude-desktop server.py +fastmcp install cursor server.py +fastmcp install mcp-json server.py +``` + +Note that for security reasons, MCP clients usually run every server in a completely isolated environment. Therefore, all dependencies must be explicitly specified using the `--with` and/or `--with-editable` options (following `uv` conventions) or by attaching them to your server in code via the `dependencies` parameter. You should not assume that the MCP server will have access to your local environment. + + +**`uv` must be installed and available in your system PATH**. Both Claude Desktop and Cursor run in isolated environments and need `uv` to manage dependencies. On macOS, install `uv` globally with Homebrew for Claude Desktop compatibility: `brew install uv`. + + + +**Python Version Considerations**: The install commands now support the `--python` option to specify a Python version directly. You can also use `--project` to run within a specific project directory or `--with-requirements` to install dependencies from a requirements file. + + + +**FastMCP `install` commands focus on local server files with STDIO transport.** For remote servers running with HTTP or SSE transport, use your client's native configuration - FastMCP's value is simplifying the complex local setup with dependencies and `uv` commands. + + +### Options + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Server Name | `--server-name`, `-n` | Custom name for the server (defaults to server's name attribute or file name) | +| Editable Package | `--with-editable`, `-e` | Directory containing pyproject.toml to install in editable mode | +| Additional Packages | `--with` | Additional packages to install (can be used multiple times) | +| Environment Variables | `--env` | Environment variables in KEY=VALUE format (can be used multiple times) | +| Environment File | `--env-file`, `-f` | Load environment variables from a .env file | +| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) | +| Project Directory | `--project` | Run the command within the given project directory | +| Requirements File | `--with-requirements` | Requirements file to install dependencies from | + +### Entrypoints + +The `install` command supports local FastMCP server files and configuration: + +1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found. +2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint +3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance +4. **FastMCP configuration**: `fastmcp.json` - uses FastMCP's declarative configuration with dependencies and settings + + +Factory functions are particularly useful for install commands since they allow setup code to run that would otherwise be ignored when the MCP client runs your server. When using fastmcp.json, dependencies are automatically handled. + + + +The `install` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files. For remote servers, use your MCP client's native configuration. + + +**Examples** + +```bash +# Auto-detects server entrypoint (looks for 'mcp', 'server', or 'app') +fastmcp install claude-desktop server.py + +# Install with fastmcp.json configuration (auto-detects) +fastmcp install claude-desktop + +# Install with explicit fastmcp.json file +fastmcp install claude-desktop my-config.fastmcp.json + +# Uses specific server entrypoint +fastmcp install claude-desktop server.py:my_server + +# With custom name and dependencies +fastmcp install claude-desktop server.py:my_server --server-name "My Analysis Server" --with pandas + +# Install in Claude Code with environment variables +fastmcp install claude-code server.py --env API_KEY=secret --env DEBUG=true + +# Install in Cursor with environment variables +fastmcp install cursor server.py --env API_KEY=secret --env DEBUG=true + +# Install with environment file +fastmcp install cursor server.py --env-file .env + +# Install with specific Python version +fastmcp install claude-desktop server.py --python 3.11 + +# Install with requirements file +fastmcp install claude-code server.py --with-requirements requirements.txt + +# Install within a project directory +fastmcp install cursor server.py --project /path/to/project + +# Generate MCP JSON configuration +fastmcp install mcp-json server.py --name "My Server" --with pandas + +# Copy JSON configuration to clipboard +fastmcp install mcp-json server.py --copy +``` + +### MCP JSON Generation + +The `mcp-json` subcommand generates standard MCP JSON configuration that can be used with any MCP-compatible client. This is useful when: + +- Working with MCP clients not directly supported by FastMCP +- Creating configuration for CI/CD environments +- Sharing server configurations with others +- Integration with custom tooling + +The generated JSON follows the standard MCP server configuration format used by Claude Desktop, VS Code, Cursor, and other MCP clients, with the server name as the root key: + +```json +{ + "server-name": { + "command": "uv", + "args": [ + "run", + "--with", + "fastmcp", + "fastmcp", + "run", + "/path/to/server.py" + ], + "env": { + "API_KEY": "value" + } + } +} +``` + + +To use this configuration with your MCP client, you'll typically need to add it to the client's `mcpServers` object. Consult your client's documentation for any specific configuration requirements or formatting needs. + + +**Options specific to mcp-json:** + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Copy to Clipboard | `--copy` | Copy configuration to clipboard instead of printing to stdout | + +## `fastmcp inspect` + + + +Inspect a FastMCP server to view summary information or generate a detailed JSON report. + +```bash +# Show text summary +fastmcp inspect server.py + +# Output FastMCP JSON to stdout +fastmcp inspect server.py --format fastmcp + +# Save MCP JSON to file (format required with -o) +fastmcp inspect server.py --format mcp -o manifest.json +``` + +### Options + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Format | `--format`, `-f` | Output format: `fastmcp` (FastMCP-specific) or `mcp` (MCP protocol). Required when using `-o` | +| Output File | `--output`, `-o` | Save JSON report to file instead of stdout. Requires `--format` | + +### Output Formats + +#### FastMCP Format (`--format fastmcp`) +The default and most comprehensive format, includes all FastMCP-specific metadata: +- Server name, instructions, and version +- FastMCP version and MCP version +- Tool tags and enabled status +- Output schemas for tools +- Annotations and custom metadata +- Uses snake_case field names +- **Use this for**: Complete server introspection and debugging FastMCP servers + +#### MCP Protocol Format (`--format mcp`) +Shows exactly what MCP clients will see via the protocol: +- Only includes standard MCP protocol fields +- Matches output from `client.list_tools()`, `client.list_prompts()`, etc. +- Uses camelCase field names (e.g., `inputSchema`) +- Excludes FastMCP-specific fields like tags and enabled status +- **Use this for**: Debugging client visibility and ensuring MCP compatibility + +### Entrypoints + +The `inspect` command supports local FastMCP server files and configuration: + +1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found. +2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint +3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance +4. **FastMCP configuration**: `fastmcp.json` - inspects servers defined with FastMCP's declarative configuration + + +The `inspect` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files. + + +### Examples + +```bash +# Show text summary (no JSON output) +fastmcp inspect server.py +# Output: +# Server: MyServer +# Instructions: A helpful MCP server +# Version: 1.0.0 +# +# Components: +# Tools: 5 +# Prompts: 2 +# Resources: 3 +# Templates: 1 +# +# Environment: +# FastMCP: 2.0.0 +# MCP: 1.0.0 +# +# Use --format [fastmcp|mcp] for complete JSON output + +# Output FastMCP format to stdout +fastmcp inspect server.py --format fastmcp + +# Specify server entrypoint +fastmcp inspect server.py:my_server + +# Output MCP protocol format to stdout +fastmcp inspect server.py --format mcp + +# Save to file (format required) +fastmcp inspect server.py --format fastmcp -o server-manifest.json + +# Save MCP format with custom server object +fastmcp inspect server.py:my_server --format mcp -o mcp-manifest.json + +# Error: format required with output file +fastmcp inspect server.py -o output.json +# Error: --format is required when using -o/--output +``` + +## `fastmcp project prepare` + +Create a persistent uv project directory from a fastmcp.json file's environment configuration. This allows you to pre-install all dependencies once and reuse them with the `--project` flag. + +```bash +fastmcp project prepare fastmcp.json --output-dir ./env +``` + +### Options + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Output Directory | `--output-dir` | **Required.** Directory where the persistent uv project will be created | + +### Usage Pattern + +```bash +# Step 1: Prepare the environment (installs dependencies) +fastmcp project prepare fastmcp.json --output-dir ./my-env + +# Step 2: Run using the prepared environment (fast, no dependency installation) +fastmcp run fastmcp.json --project ./my-env +``` + +The prepare command creates a uv project with: +- A `pyproject.toml` containing all dependencies from the fastmcp.json +- A `.venv` with all packages pre-installed +- A `uv.lock` file for reproducible environments + +This is useful when you want to separate environment setup from server execution, such as in deployment scenarios where dependencies are installed once and the server is run multiple times. + +## `fastmcp version` + +Display version information about FastMCP and related components. + +```bash +fastmcp version +``` + +### Options + +| Option | Flag | Description | +| ------ | ---- | ----------- | +| Copy to Clipboard | `--copy` | Copy version information to clipboard | diff --git a/docs/v2/patterns/contrib.mdx b/docs/v2/patterns/contrib.mdx new file mode 100644 index 000000000..2f71c0b61 --- /dev/null +++ b/docs/v2/patterns/contrib.mdx @@ -0,0 +1,45 @@ +--- +title: "Contrib Modules" +description: "Community-contributed modules extending FastMCP" +icon: "cubes" +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +FastMCP includes a `contrib` package that holds community-contributed modules. These modules extend FastMCP's functionality but aren't officially maintained by the core team. + +Contrib modules provide additional features, integrations, or patterns that complement the core FastMCP library. They offer a way for the community to share useful extensions while keeping the core library focused and maintainable. + +The available modules can be viewed in the [contrib directory](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/contrib). + +## Usage + +To use a contrib module, import it from the `fastmcp.contrib` package: + +```python +from fastmcp.contrib import my_module +``` + +## Important Considerations + +- **Stability**: Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library. +- **Compatibility**: Changes to core FastMCP might break modules in `contrib` without explicit warnings in the main changelog. +- **Dependencies**: Contrib modules may have additional dependencies not required by the core library. These dependencies are typically documented in the module's README or separate requirements files. + +## Contributing + +We welcome contributions to the `contrib` package! If you have a module that extends FastMCP in a useful way, consider contributing it: + +1. Create a new directory in `src/fastmcp/contrib/` for your module +3. Add proper tests for your module in `tests/contrib/` +2. Include comprehensive documentation in a README.md file, including usage and examples, as well as any additional dependencies or installation instructions +5. Submit a pull request + +The ideal contrib module: +- Solves a specific use case or integration need +- Follows FastMCP coding standards +- Includes thorough documentation and examples +- Has comprehensive tests +- Specifies any additional dependencies diff --git a/docs/v2/patterns/decorating-methods.mdx b/docs/v2/patterns/decorating-methods.mdx new file mode 100644 index 000000000..2b48aa4d9 --- /dev/null +++ b/docs/v2/patterns/decorating-methods.mdx @@ -0,0 +1,225 @@ +--- +title: Decorating Methods +sidebarTitle: Decorating Methods +description: Properly use instance methods, class methods, and static methods with FastMCP decorators. +icon: at +--- + +FastMCP's decorator system is designed to work with functions, but you may see unexpected behavior if you try to decorate an instance or class method. This guide explains the correct approach for using methods with all FastMCP decorators (`@tool`, `@resource`, and `@prompt`). + +## Why Are Methods Hard? + +When you apply a FastMCP decorator like `@tool`, `@resource`, or `@prompt` to a method, the decorator captures the function at decoration time. For instance methods and class methods, this poses a challenge because: + +1. For instance methods: The decorator gets the unbound method before any instance exists +2. For class methods: The decorator gets the function before it's bound to the class + +This means directly decorating these methods doesn't work as expected. In practice, the LLM would see parameters like `self` or `cls` that it cannot provide values for. + +Additionally, **FastMCP decorators return objects (Tool, Resource, or Prompt instances) rather than the original function**. This means that when you decorate a method directly, the method becomes the returned object and is no longer callable by your code: + + +**Don't do this!** + +The method will no longer be callable from Python, and the tool won't be callable by LLMs. + +```python + +from fastmcp import FastMCP +mcp = FastMCP() + +class MyClass: + @mcp.tool + def my_method(self, x: int) -> int: + return x * 2 + +obj = MyClass() +obj.my_method(5) # Fails - my_method is a Tool, not a function +``` + + +This is another important reason to register methods functionally after defining the class. + +## Recommended Patterns + +### Instance Methods + + +**Don't do this!** + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class MyClass: + @mcp.tool # This won't work correctly + def add(self, x, y): + return x + y +``` + +When the decorator is applied this way, it captures the unbound method. When the LLM later tries to use this component, it will see `self` as a required parameter, but it won't know what to provide for it, causing errors or unexpected behavior. + + +**Do this instead**: + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class MyClass: + def add(self, x, y): + return x + y + +# Create an instance first, then register the bound methods +obj = MyClass() +mcp.tool(obj.add) + +# Now you can call it without 'self' showing up as a parameter +await mcp._mcp_call_tool('add', {'x': 1, 'y': 2}) # Returns 3 +``` + + +This approach works because: +1. You first create an instance of the class (`obj`) +2. When you access the method through the instance (`obj.add`), Python creates a bound method where `self` is already set to that instance +3. When you register this bound method, the system sees a callable that only expects the appropriate parameters, not `self` + +### Class Methods + +The behavior of decorating class methods depends on the order of decorators: + + +**Don't do this** (decorator order matters): + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class MyClass: + @classmethod + @mcp.tool # This won't work but won't raise an error + def from_string_v1(cls, s): + return cls(s) + + @mcp.tool + @classmethod # This will raise a helpful ValueError + def from_string_v2(cls, s): + return cls(s) +``` + + +- If `@classmethod` comes first, then `@mcp.tool`: No error is raised, but it won't work correctly +- If `@mcp.tool` comes first, then `@classmethod`: FastMCP will detect this and raise a helpful `ValueError` with guidance + + +**Do this instead**: + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class MyClass: + @classmethod + def from_string(cls, s): + return cls(s) + +# Register the class method after the class is defined +mcp.tool(MyClass.from_string) +``` + + +This works because: +1. The `@classmethod` decorator is applied properly during class definition +2. When you access `MyClass.from_string`, Python provides a special method object that automatically binds the class to the `cls` parameter +3. When registered, only the appropriate parameters are exposed to the LLM, hiding the implementation detail of the `cls` parameter + +### Static Methods + +Static methods "work" with FastMCP decorators, but this is not recommended because the FastMCP decorator will not return a callable method. Therefore, you should register static methods the same way as other methods. + + +**This is not recommended, though it will work.** + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class MyClass: + @mcp.tool + @staticmethod + def utility(x, y): + return x + y +``` + + +This works because `@staticmethod` converts the method to a regular function, which the FastMCP decorator can then properly process. However, this is not recommended because the FastMCP decorator will not return a callable staticmethod. Therefore, you should register static methods the same way as other methods. + + +**Prefer this pattern:** + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class MyClass: + @staticmethod + def utility(x, y): + return x + y + +# This also works +mcp.tool(MyClass.utility) +``` + + +## Additional Patterns + +### Creating Components at Class Initialization + +You can automatically register instance methods when creating an object: + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +class ComponentProvider: + def __init__(self, mcp_instance): + # Register methods + mcp_instance.tool(self.tool_method) + mcp_instance.resource("resource://data")(self.resource_method) + + def tool_method(self, x): + return x * 2 + + def resource_method(self): + return "Resource data" + +# The methods are automatically registered when creating the instance +provider = ComponentProvider(mcp) +``` + +This pattern is useful when: +- You want to encapsulate registration logic within the class itself +- You have multiple related components that should be registered together +- You want to ensure that methods are always properly registered when creating an instance + +The class automatically registers its methods during initialization, ensuring they're properly bound to the instance before registration. + +## Summary + +The current behavior of FastMCP decorators with methods is: + +- **Static methods**: Can be decorated directly and work perfectly with all FastMCP decorators +- **Class methods**: Cannot be decorated directly and will raise a helpful `ValueError` with guidance +- **Instance methods**: Should be registered after creating an instance using the decorator calls + +For class and instance methods, you should register them after creating the instance or class to ensure proper method binding. This ensures that the methods are properly bound before being registered. + + +Understanding these patterns allows you to effectively organize your components into classes while maintaining proper method binding, giving you the benefits of object-oriented design without sacrificing the simplicity of FastMCP's decorator system. diff --git a/docs/v2/patterns/testing.mdx b/docs/v2/patterns/testing.mdx new file mode 100644 index 000000000..48f6d05df --- /dev/null +++ b/docs/v2/patterns/testing.mdx @@ -0,0 +1,104 @@ +--- +title: Testing your FastMCP Server +sidebarTitle: Testing +description: How to test your FastMCP server. +icon: vial +--- + +The best way to ensure a reliable and maintainable FastMCP Server is to test it! The FastMCP Client combined with Pytest provides a simple and powerful way to test your FastMCP servers. + +## Prerequisites + +Testing FastMCP servers requires `pytest-asyncio` to handle async test functions and fixtures. Install it as a development dependency: + +```bash +pip install pytest-asyncio +``` + +We recommend configuring pytest to automatically handle async tests by setting the asyncio mode to `auto` in your `pyproject.toml`: + +```toml +[tool.pytest.ini_options] +asyncio_mode = "auto" +``` + +This eliminates the need to decorate every async test with `@pytest.mark.asyncio`. + +## Testing with Pytest Fixtures + +Using Pytest Fixtures, you can wrap your FastMCP Server in a Client instance that makes interacting with your server fast and easy. This is especially useful when building your own MCP Servers and enables a tight development loop by allowing you to avoid using a separate tool like MCP Inspector during development: + +```python +import pytest +from fastmcp.client import Client +from fastmcp.client.transports import FastMCPTransport + +from my_project.main import mcp + +@pytest.fixture +async def main_mcp_client(): + async with Client(transport=mcp) as mcp_client: + yield mcp_client + +async def test_list_tools(main_mcp_client: Client[FastMCPTransport]): + list_tools = await main_mcp_client.list_tools() + + assert len(list_tools) == 5 +``` + +We recommend the [inline-snapshot library](https://github.com/15r10nk/inline-snapshot) for asserting complex data structures coming from your MCP Server. This library allows you to write tests that are easy to read and understand, and are also easy to update when the data structure changes. + +```python +from inline_snapshot import snapshot + +async def test_list_tools(main_mcp_client: Client[FastMCPTransport]): + list_tools = await main_mcp_client.list_tools() + + assert list_tools == snapshot() +``` + +Simply run `pytest --inline-snapshot=fix,create` to fill in the `snapshot()` with actual data. + + +For values that change you can leverage the [dirty-equals](https://github.com/samuelcolvin/dirty-equals) library to perform flexible equality assertions on dynamic or non-deterministic values. + + +Using the pytest `parametrize` decorator, you can easily test your tools with a wide variety of inputs. + +```python +import pytest +from my_project.main import mcp + +from fastmcp.client import Client +from fastmcp.client.transports import FastMCPTransport +@pytest.fixture +async def main_mcp_client(): + async with Client(mcp) as client: + yield client + + +@pytest.mark.parametrize( + "first_number, second_number, expected", + [ + (1, 2, 3), + (2, 3, 5), + (3, 4, 7), + ], +) +async def test_add( + first_number: int, + second_number: int, + expected: int, + main_mcp_client: Client[FastMCPTransport], +): + result = await main_mcp_client.call_tool( + name="add", arguments={"x": first_number, "y": second_number} + ) + assert result.data is not None + assert isinstance(result.data, int) + assert result.data == expected +``` + + +The [FastMCP Repository contains thousands of tests](https://github.com/jlowin/fastmcp/tree/main/tests) for the FastMCP Client and Server. Everything from connecting to remote MCP servers, to testing tools, resources, and prompts is covered, take a look for inspiration! + \ No newline at end of file diff --git a/docs/v2/patterns/tool-transformation.mdx b/docs/v2/patterns/tool-transformation.mdx new file mode 100644 index 000000000..b37808f3b --- /dev/null +++ b/docs/v2/patterns/tool-transformation.mdx @@ -0,0 +1,707 @@ +--- +title: Tool Transformation +sidebarTitle: Tool Transformation +description: Create enhanced tool variants with modified schemas, argument mappings, and custom behavior. +icon: wand-magic-sparkles +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +Tool transformation allows you to create new, enhanced tools from existing ones. This powerful feature enables you to adapt tools for different contexts, simplify complex interfaces, or add custom logic without duplicating code. + +## Why Transform Tools? + +Often, an existing tool is *almost* perfect for your use case, but it might have: +- A confusing description (or no description at all). +- Argument names or descriptions that are not intuitive for an LLM (e.g., `q` instead of `query`). +- Unnecessary parameters that you want to hide from the LLM. +- A need for input validation before the original tool is called. +- A need to modify or format the tool's output. + +Instead of rewriting the tool from scratch, you can **transform** it to fit your needs. + +## Basic Transformation + +The primary way to create a transformed tool is with the `Tool.from_tool()` class method. At its simplest, you can use it to change a tool's top-level metadata like its `name`, `description`, or `tags`. + +In the following simple example, we take a generic `search` tool and adjust its name and description to help an LLM client better understand its purpose. + +```python {13-21} +from fastmcp import FastMCP +from fastmcp.tools import Tool + +mcp = FastMCP() + +# The original, generic tool +@mcp.tool +def search(query: str, category: str = "all") -> list[dict]: + """Searches for items in the database.""" + return database.search(query, category) + +# Create a more domain-specific version by changing its metadata +product_search_tool = Tool.from_tool( + search, + name="find_products", + description=""" + Search for products in the e-commerce catalog. + Use this when customers ask about finding specific items, + checking availability, or browsing product categories. + """, +) + +mcp.add_tool(product_search_tool) +``` + + +When you transform a tool, the original tool remains registered on the server. To avoid confusing an LLM with two similar tools, you can disable the original one: + +```python +from fastmcp import FastMCP +from fastmcp.tools import Tool + +mcp = FastMCP() + +# The original, generic tool +@mcp.tool +def search(query: str, category: str = "all") -> list[dict]: + ... + +# Create a more domain-specific version +product_search_tool = Tool.from_tool(search, ...) +mcp.add_tool(product_search_tool) + +# Disable the original tool +search.disable() +``` + + +Now, clients see a tool named `find_products` with a clear, domain-specific purpose and relevant tags, even though it still uses the original generic `search` function's logic. + +### Parameters + +The `Tool.from_tool()` class method is the primary way to create a transformed tool. It takes the following parameters: + +- `tool`: The tool to transform. This is the only required argument. +- `name`: An optional name for the new tool. +- `description`: An optional description for the new tool. +- `transform_args`: A dictionary of `ArgTransform` objects, one for each argument you want to modify. +- `transform_fn`: An optional function that will be called instead of the parent tool's logic. +- `output_schema`: Control output schema and structured outputs (see [Output Schema Control](#output-schema-control)). +- `tags`: An optional set of tags for the new tool. +- `annotations`: An optional set of `ToolAnnotations` for the new tool. +- `serializer`: An optional function that will be called to serialize the result of the new tool. +- `meta`: Control meta information for the tool. Use `None` to remove meta, any dict to set meta, or leave unset to inherit from parent. + +The result is a new `TransformedTool` object that wraps the parent tool and applies the transformations you specify. You can add this tool to your MCP server using its `add_tool()` method. + + + +## Modifying Arguments + +To modify a tool's parameters, provide a dictionary of `ArgTransform` objects to the `transform_args` parameter of `Tool.from_tool()`. Each key is the name of the *original* argument you want to modify. + + +You only need to provide a `transform_args` entry for arguments you want to modify. All other arguments will be passed through unchanged. + + +### The ArgTransform Class + +To modify an argument, you need to create an `ArgTransform` object. This object has the following parameters: + +- `name`: The new name for the argument. +- `description`: The new description for the argument. +- `default`: The new default value for the argument. +- `default_factory`: A function that will be called to generate a default value for the argument. This is useful for arguments that need to be generated for each tool call, such as timestamps or unique IDs. +- `hide`: Whether to hide the argument from the LLM. +- `required`: Whether the argument is required, usually used to make an optional argument be required instead. +- `type`: The new type for the argument. + + +Certain combinations of parameters are not allowed. For example, you can only use `default_factory` with `hide=True`, because dynamic defaults cannot be represented in a JSON schema for the client. You can only set required=True for arguments that do not declare a default value. + + + +### Descriptions + +By far the most common reason to transform a tool, after its own description, is to improve its argument descriptions. A good description is crucial for helping an LLM understand how to use a parameter correctly. This is especially important when wrapping tools from external APIs, whose argument descriptions may be missing or written for developers, not LLMs. + +In this example, we add a helpful description to the `user_id` argument: + +```python {16-19} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import ArgTransform + +mcp = FastMCP() + +@mcp.tool +def find_user(user_id: str): + """Finds a user by their ID.""" + ... + +new_tool = Tool.from_tool( + find_user, + transform_args={ + "user_id": ArgTransform( + description=( + "The unique identifier for the user, " + "usually in the format 'usr-xxxxxxxx'." + ) + ) + } +) +``` + +### Names + +At times, you may want to rename an argument to make it more intuitive for an LLM. + +For example, in the following example, we take a generic `q` argument and expand it to `search_query`: + +```python {15} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import ArgTransform + +mcp = FastMCP() + +@mcp.tool +def search(q: str): + """Searches for items in the database.""" + return database.search(q) + +new_tool = Tool.from_tool( + search, + transform_args={ + "q": ArgTransform(name="search_query") + } +) +``` + +### Default Values + +You can update the default value for any argument using the `default` parameter. Here, we change the default value of the `y` argument to 10: + +```python{15} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import ArgTransform + +mcp = FastMCP() + +@mcp.tool +def add(x: int, y: int) -> int: + """Adds two numbers.""" + return x + y + +new_tool = Tool.from_tool( + add, + transform_args={ + "y": ArgTransform(default=10) + } +) +``` + +Default values are especially useful in combination with hidden arguments. + +### Hiding Arguments + +Sometimes a tool requires arguments that shouldn't be exposed to the LLM, such as API keys, configuration flags, or internal IDs. You can hide these parameters using `hide=True`. Note that you can only hide arguments that have a default value (or for which you provide a new default), because the LLM can't provide a value at call time. + + +To pass a constant value to the parent tool, combine `hide=True` with `default=`. + + +```python {19-20} +import os +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import ArgTransform + +mcp = FastMCP() + +@mcp.tool +def send_email(to: str, subject: str, body: str, api_key: str): + """Sends an email.""" + ... + +# Create a simplified version that hides the API key +new_tool = Tool.from_tool( + send_email, + name="send_notification", + transform_args={ + "api_key": ArgTransform( + hide=True, + default=os.environ.get("EMAIL_API_KEY"), + ) + } +) +``` +The LLM now only sees the `to`, `subject`, and `body` parameters. The `api_key` is supplied automatically from an environment variable. + +For values that must be generated for each tool call (like timestamps or unique IDs), use `default_factory`, which is called with no arguments every time the tool is called. For example, + +```python {3-4} +transform_args = { + 'timestamp': ArgTransform( + hide=True, + default_factory=lambda: datetime.now(), + ) +} +``` + + +`default_factory` can only be used with `hide=True`. This is because visible parameters need static defaults that can be represented in a JSON schema for the client. + + +### Meta Information + + + +You can control meta information on transformed tools using the `meta` parameter. Meta information is additional data about the tool that doesn't affect its functionality but can be used by clients for categorization, routing, or other purposes. + +```python {15-17} +from fastmcp import FastMCP +from fastmcp.tools import Tool + +mcp = FastMCP() + +@mcp.tool +def analyze_data(data: str) -> dict: + """Analyzes the provided data.""" + return {"result": f"Analysis of {data}"} + +# Add custom meta information +enhanced_tool = Tool.from_tool( + analyze_data, + name="enhanced_analyzer", + meta={ + "category": "analytics", + "priority": "high", + "requires_auth": True + } +) + +mcp.add_tool(enhanced_tool) +``` + +You can also remove meta information entirely: + +```python {6} +# Remove meta information from parent tool +simplified_tool = Tool.from_tool( + analyze_data, + name="simple_analyzer", + meta=None # Removes any meta information +) +``` + +If you don't specify the `meta` parameter, the transformed tool inherits the parent tool's meta information. + +### Required Values + +In rare cases where you want to make an optional argument required, you can set `required=True`. This has no effect if the argument was already required. + +```python {3} +transform_args = { + 'user_id': ArgTransform( + required=True, + ) +} +``` + +## Modifying Tool Behavior + + +With great power comes great responsibility. Modifying tool behavior is a very advanced feature. + + +In addition to changing a tool's schema, advanced users can also modify its behavior. This is useful for adding validation logic, or for post-processing the tool's output. + +The `from_tool()` method takes a `transform_fn` parameter, which is an async function that replaces the parent tool's logic and gives you complete control over the tool's execution. + +### The Transform Function + +The `transform_fn` is an async function that **completely replaces** the parent tool's logic. + +Critically, the transform function's arguments are used to determine the new tool's final schema. Any arguments that are not already present in the parent tool schema OR the `transform_args` will be added to the new tool's schema. Note that when `transform_args` and your function have the same argument name, the `transform_args` metadata will take precedence, if provided. + +```python +async def my_custom_logic(user_input: str, max_length: int = 100) -> str: + # Your custom logic here - this completely replaces the parent tool + return f"Custom result for: {user_input[:max_length]}" + +Tool.from_tool(transform_fn=my_custom_logic) +``` + + +The name / docstring of the `transform_fn` are ignored. Only its arguments are used to determine the final schema. + + +### Calling the Parent Tool + +Most of the time, you don't want to completely replace the parent tool's behavior. Instead, you want to add validation, modify inputs, or post-process outputs while still leveraging the parent tool's core functionality. For this, FastMCP provides the special `forward()` and `forward_raw()` functions. + +Both `forward()` and `forward_raw()` are async functions that let you call the parent tool from within your `transform_fn`: + +- **`forward()`** (recommended): Automatically handles argument mapping based on your `ArgTransform` configurations. Call it with the transformed argument names. +- **`forward_raw()`**: Bypasses all transformation and calls the parent tool directly with its original argument names. This is rarely needed unless you're doing complex argument manipulation, perhaps without `arg_transforms`. + +The most common transformation pattern is to validate (potentially renamed) arguments before calling the parent tool. Here's an example that validates that `x` and `y` are positive before calling the parent tool: + + + +In the simplest case, your parent tool and your transform function have the same arguments. You can call `forward()` with the same argument names as the parent tool: + +```python {15} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import forward + +mcp = FastMCP() + +@mcp.tool +def add(x: int, y: int) -> int: + """Adds two numbers.""" + return x + y + +async def ensure_positive(x: int, y: int) -> int: + if x <= 0 or y <= 0: + raise ValueError("x and y must be positive") + return await forward(x=x, y=y) + +new_tool = Tool.from_tool( + add, + transform_fn=ensure_positive, +) + +mcp.add_tool(new_tool) +``` + + + +When your transformed tool has different argument names than the parent tool, you can call `forward()` with the renamed arguments and it will automatically map the arguments to the parent tool's arguments: + +```python {15, 20-23} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import forward + +mcp = FastMCP() + +@mcp.tool +def add(x: int, y: int) -> int: + """Adds two numbers.""" + return x + y + +async def ensure_positive(a: int, b: int) -> int: + if a <= 0 or b <= 0: + raise ValueError("a and b must be positive") + return await forward(a=a, b=b) + +new_tool = Tool.from_tool( + add, + transform_fn=ensure_positive, + transform_args={ + "x": ArgTransform(name="a"), + "y": ArgTransform(name="b"), + } +) + +mcp.add_tool(new_tool) +``` + + +Finally, you can use `forward_raw()` to bypass all argument mapping and call the parent tool directly with its original argument names. + +```python {15, 20-23} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import forward + +mcp = FastMCP() + +@mcp.tool +def add(x: int, y: int) -> int: + """Adds two numbers.""" + return x + y + +async def ensure_positive(a: int, b: int) -> int: + if a <= 0 or b <= 0: + raise ValueError("a and b must be positive") + return await forward_raw(x=a, y=b) + +new_tool = Tool.from_tool( + add, + transform_fn=ensure_positive, + transform_args={ + "x": ArgTransform(name="a"), + "y": ArgTransform(name="b"), + } +) + +mcp.add_tool(new_tool) +``` + + + +### Passing Arguments with **kwargs + +If your `transform_fn` includes `**kwargs` in its signature, it will receive **all arguments from the parent tool after `ArgTransform` configurations have been applied**. This is powerful for creating flexible validation functions that don't require you to add every argument to the function signature. + +In the following example, we wrap a parent tool that accepts two arguments `x` and `y`. These are renamed to `a` and `b` in the transformed tool, and the transform only validates `a`, passing the other argument through as `**kwargs`. + +```python {12, 15} +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import forward, ArgTransform + +mcp = FastMCP() + +@mcp.tool +def add(x: int, y: int) -> int: + """Adds two numbers.""" + return x + y + +async def ensure_a_positive(a: int, **kwargs) -> int: + if a <= 0: + raise ValueError("a must be positive") + return await forward(a=a, **kwargs) + +new_tool = Tool.from_tool( + add, + transform_fn=ensure_a_positive, + transform_args={ + "x": ArgTransform(name="a"), + "y": ArgTransform(name="b"), + } +) + +mcp.add_tool(new_tool) +``` + + +In the above example, `**kwargs` receives the renamed argument `b`, not the original argument `y`. It is therefore recommended to use with `forward()`, not `forward_raw()`. + + +## Modifying MCP Tools with MCPConfig + +When running MCP Servers under FastMCP with `MCPConfig`, you can also apply a subset of tool transformations +directly in the MCPConfig json file. + +```json +{ + "mcpServers": { + "weather": { + "url": "https://weather.example.com/mcp", + "transport": "http", + "tools": { + "weather_get_forecast": { + "name": "miami_weather", + "description": "Get the weather for Miami", + "meta": { + "category": "weather", + "location": "miami" + }, + "arguments": { + "city": { + "name": "city", + "default": "Miami", + "hide": True, + } + } + } + } + } + } +} +``` + +The `tools` section is a dictionary of tool names to tool configurations. Each tool configuration is a +dictionary of tool properties. + +See the [MCPConfigTransport](/clients/transports#tool-transformation-with-fastmcp-and-mcpconfig) documentation for more details. + + +## Output Schema Control + + + +Transformed tools inherit output schemas from their parent by default, but you can control this behavior: + +**Inherit from Parent (Default)** +```python +Tool.from_tool(parent_tool, name="renamed_tool") +``` +The transformed tool automatically uses the parent tool's output schema and structured output behavior. + +**Custom Output Schema** +```python +Tool.from_tool(parent_tool, output_schema={ + "type": "object", + "properties": {"status": {"type": "string"}} +}) +``` +Provide your own schema that differs from the parent. The tool must return data matching this schema. + +**Remove Output Schema** +```python +Tool.from_tool(parent_tool, output_schema=None) +``` +Removes the output schema declaration. Automatic structured content still works for object-like returns (dict, dataclass, Pydantic models) but primitive types won't be structured. + +**Full Control with Transform Functions** +```python +async def custom_output(**kwargs) -> ToolResult: + result = await forward(**kwargs) + return ToolResult(content=[...], structured_content={...}) + +Tool.from_tool(parent_tool, transform_fn=custom_output) +``` +Use a transform function returning `ToolResult` for complete control over both content blocks and structured outputs. + +## Common Patterns + +Tool transformation is a flexible feature that supports many powerful patterns. Here are a few common use cases to give you ideas. + +### Exposing Client Methods as Tools + +A powerful use case for tool transformation is exposing methods from existing Python clients (GitHub clients, API clients, database clients, etc.) directly as MCP tools. This pattern eliminates boilerplate wrapper functions and treats tools as annotations around client methods. + +**Without Tool Transformation**, you typically create wrapper functions that duplicate annotations: + +```python +async def get_repository( + owner: Annotated[str, "The owner of the repository."], + repo: Annotated[str, "The name of the repository."], +) -> Repository: + """Get basic information about a GitHub repository.""" + return await github_client.get_repository(owner=owner, repo=repo) +``` + +**With Tool Transformation**, you can wrap the client method directly: + +```python +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import ArgTransform + +mcp = FastMCP("GitHub Tools") + +# Wrap a client method directly as a tool +get_repo_tool = Tool.from_tool( + tool=Tool.from_function(fn=github_client.get_repository), + description="Get basic information about a GitHub repository.", + transform_args={ + "owner": ArgTransform(description="The owner of the repository."), + "repo": ArgTransform(description="The name of the repository."), + } +) + +mcp.add_tool(get_repo_tool) +``` + +This pattern keeps the implementation in your client and treats the tool as an annotation layer, avoiding duplicate code. + +#### Hiding Client-Specific Arguments + +Client methods often have internal parameters (debug flags, auth tokens, rate limit settings) that shouldn't be exposed to LLMs. Use `hide=True` with a default value to handle these automatically: + +```python +get_issues_tool = Tool.from_tool( + tool=Tool.from_function(fn=github_client.get_issues), + description="Get issues from a GitHub repository.", + transform_args={ + "owner": ArgTransform(description="The owner of the repository."), + "repo": ArgTransform(description="The name of the repository."), + "limit": ArgTransform(description="Maximum number of issues to return."), + # Hide internal parameters + "include_debug_info": ArgTransform(hide=True, default=False), + "error_on_not_found": ArgTransform(hide=True, default=True), + } +) + +mcp.add_tool(get_issues_tool) +``` + +The LLM only sees `owner`, `repo`, and `limit`. Internal parameters are supplied automatically. + +#### Reusable Argument Patterns + +When wrapping multiple client methods, you can define reusable argument transformations. This scales well for larger tool sets and keeps annotations consistent: + +```python +from fastmcp import FastMCP +from fastmcp.tools import Tool +from fastmcp.tools.tool_transform import ArgTransform + +mcp = FastMCP("GitHub Tools") + +# Define reusable argument patterns +OWNER_ARG = ArgTransform(description="The repository owner.") +REPO_ARG = ArgTransform(description="The repository name.") +LIMIT_ARG = ArgTransform(description="Maximum number of items to return.") +HIDE_ERROR = ArgTransform(hide=True, default=True) + +def create_github_tools(client): + """Create tools from GitHub client methods with shared argument patterns.""" + + owner_repo_args = { + "owner": OWNER_ARG, + "repo": REPO_ARG, + } + + error_args = { + "error_on_not_found": HIDE_ERROR, + } + + return [ + Tool.from_tool( + tool=Tool.from_function(fn=client.get_repository), + description="Get basic information about a GitHub repository.", + transform_args={**owner_repo_args, **error_args} + ), + Tool.from_tool( + tool=Tool.from_function(fn=client.get_issue), + description="Get a specific issue from a repository.", + transform_args={ + **owner_repo_args, + "issue_number": ArgTransform(description="The issue number."), + "limit_comments": LIMIT_ARG, + **error_args, + } + ), + Tool.from_tool( + tool=Tool.from_function(fn=client.get_pull_request), + description="Get a specific pull request from a repository.", + transform_args={ + **owner_repo_args, + "pull_request_number": ArgTransform(description="The PR number."), + "limit_comments": LIMIT_ARG, + **error_args, + } + ), + ] + +# Add all tools to the server +for tool in create_github_tools(github_client): + mcp.add_tool(tool) +``` + +This pattern provides several benefits: + +- **No duplicate implementation**: Logic stays in the client +- **Consistent annotations**: Reusable argument patterns ensure consistency +- **Easy maintenance**: Update the client, not wrapper functions +- **Scalable**: Easily add new tools by wrapping additional client methods + +### Adapting Remote or Generated Tools +This is one of the most common reasons to use tool transformation. Tools from remote MCP servers (via a [proxy](/servers/proxy)) or generated from an [OpenAPI spec](/integrations/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs. + +### Chaining Transformations +You can chain transformations by using an already transformed tool as the parent for a new transformation. This lets you build up complex behaviors in layers, for example, first renaming arguments, and then adding validation logic to the renamed tool. + +### Context-Aware Tool Factories +You can write functions that act as "factories," generating specialized versions of a tool for different contexts. For example, you could create a `get_my_data` tool that is specific to the currently logged-in user by hiding the `user_id` parameter and providing it automatically. diff --git a/docs/v2/servers/auth/authentication.mdx b/docs/v2/servers/auth/authentication.mdx new file mode 100644 index 000000000..b366066e5 --- /dev/null +++ b/docs/v2/servers/auth/authentication.mdx @@ -0,0 +1,266 @@ +--- +title: Authentication +sidebarTitle: Overview +description: Secure your FastMCP server with flexible authentication patterns, from simple API keys to full OAuth 2.1 integration with external identity providers. +icon: user-shield +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +Authentication in MCP presents unique challenges that differ from traditional web applications. MCP clients need to discover authentication requirements automatically, negotiate OAuth flows without user intervention, and work seamlessly across different identity providers. FastMCP addresses these challenges by providing authentication patterns that integrate with the MCP protocol while remaining simple to implement and deploy. + + +Authentication applies only to FastMCP's HTTP-based transports (`http` and `sse`). The STDIO transport inherits security from its local execution environment. + + + +**Authentication is rapidly evolving in MCP.** The specification and best practices are changing quickly. FastMCP aims to provide stable, secure patterns that adapt to these changes while keeping your code simple and maintainable. + + +## MCP Authentication Challenges + +Traditional web authentication assumes a human user with a browser who can interact with login forms and consent screens. MCP clients are often automated systems that need to authenticate without human intervention. This creates several unique requirements: + +**Automatic Discovery**: MCP clients must discover authentication requirements by examining server metadata rather than encountering login redirects. + +**Programmatic OAuth**: OAuth flows must work without human interaction, relying on pre-configured credentials or Dynamic Client Registration. + +**Token Management**: Clients need to obtain, refresh, and manage tokens automatically across multiple MCP servers. + +**Protocol Integration**: Authentication must integrate cleanly with MCP's transport mechanisms and error handling. + +These challenges mean that not all authentication approaches work well with MCP. The patterns that do work fall into three categories based on the level of authentication responsibility your server assumes. + +## Authentication Responsibility + +Authentication responsibility exists on a spectrum. Your MCP server can validate tokens created elsewhere, coordinate with external identity providers, or handle the complete authentication lifecycle internally. Each approach involves different trade-offs between simplicity, security, and control. + +### Token Validation + +Your server validates tokens but delegates their creation to external systems. This approach treats your MCP server as a pure resource server that trusts tokens signed by known issuers. + +Token validation works well when you already have authentication infrastructure that can issue structured tokens like JWTs. Your existing API gateway, microservices platform, or enterprise SSO system becomes the source of truth for user identity, while your MCP server focuses on its core functionality. + +The key insight is that token validation separates authentication (proving who you are) from authorization (determining what you can do). Your MCP server receives proof of identity in the form of a signed token and makes access decisions based on the claims within that token. + +This pattern excels in microservices architectures where multiple services need to validate the same tokens, or when integrating MCP servers into existing systems that already handle user authentication. + +### External Identity Providers + +Your server coordinates with established identity providers to create seamless authentication experiences for MCP clients. This approach leverages OAuth 2.0 and OpenID Connect protocols to delegate user authentication while maintaining control over authorization decisions. + +External identity providers handle the complex aspects of authentication: user credential verification, multi-factor authentication, account recovery, and security monitoring. Your MCP server receives tokens from these trusted providers and validates them using the provider's public keys. + +The MCP protocol's support for Dynamic Client Registration makes this pattern particularly powerful. MCP clients can automatically discover your authentication requirements and register themselves with your identity provider without manual configuration. + +This approach works best for production applications that need enterprise-grade authentication features without the complexity of building them from scratch. It scales well across multiple applications and provides consistent user experiences. + +### Full OAuth Implementation + +Your server implements a complete OAuth 2.0 authorization server, handling everything from user credential verification to token lifecycle management. This approach provides maximum control at the cost of significant complexity. + +Full OAuth implementation means building user interfaces for login and consent, implementing secure credential storage, managing token lifecycles, and maintaining ongoing security updates. The complexity extends beyond initial implementation to include threat monitoring, compliance requirements, and keeping pace with evolving security best practices. + +This pattern makes sense only when you need complete control over the authentication process, operate in air-gapped environments, or have specialized requirements that external providers cannot meet. + +## FastMCP Authentication Providers + +FastMCP translates these authentication responsibility levels into a variety of concrete classes that handle the complexities of MCP protocol integration. You can build on these classes to handle the complexities of MCP protocol integration. + +### TokenVerifier + +`TokenVerifier` provides pure token validation without OAuth metadata endpoints. This class focuses on the essential task of determining whether a token is valid and extracting authorization information from its claims. + +The implementation handles JWT signature verification, expiration checking, and claim extraction. It validates tokens against known issuers and audiences, ensuring that tokens intended for your server are not accepted by other systems. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.jwt import JWTVerifier + +auth = JWTVerifier( + jwks_uri="https://your-auth-system.com/.well-known/jwks.json", + issuer="https://your-auth-system.com", + audience="your-mcp-server" +) + +mcp = FastMCP(name="Protected Server", auth=auth) +``` + +This example configures token validation against a JWT issuer. The `JWTVerifier` will fetch public keys from the JWKS endpoint and validate incoming tokens against those keys. Only tokens with the correct issuer and audience claims will be accepted. + +`TokenVerifier` works well when you control both the token issuer and your MCP server, or when integrating with existing JWT-based infrastructure. + +→ **Complete guide**: [Token Verification](/servers/auth/token-verification) + +### RemoteAuthProvider + +`RemoteAuthProvider` enables authentication with identity providers that **support Dynamic Client Registration (DCR)**, such as Descope and WorkOS AuthKit. With DCR, MCP clients can automatically register themselves with the identity provider and obtain credentials without any manual configuration. + +This class combines token validation with OAuth discovery metadata. It extends `TokenVerifier` functionality by adding OAuth 2.0 protected resource endpoints that advertise your authentication requirements. MCP clients examine these endpoints to understand which identity providers you trust and how to obtain valid tokens. + +The key requirement is that your identity provider must support DCR - the ability for clients to dynamically register and obtain credentials. This is what enables the seamless, automated authentication flow that MCP requires. + +For example, the built-in `AuthKitProvider` uses WorkOS AuthKit, which fully supports DCR: + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.workos import AuthKitProvider + +auth = AuthKitProvider( + authkit_domain="https://your-project.authkit.app", + base_url="https://your-fastmcp-server.com" +) + +mcp = FastMCP(name="Enterprise Server", auth=auth) +``` + +This example uses WorkOS AuthKit as the external identity provider. The `AuthKitProvider` automatically configures token validation against WorkOS and provides the OAuth metadata that MCP clients need for automatic authentication. + +`RemoteAuthProvider` is ideal for production applications when your identity provider supports Dynamic Client Registration (DCR). This enables fully automated authentication without manual client configuration. + +→ **Complete guide**: [Remote OAuth](/servers/auth/remote-oauth) + +### OAuthProxy + + + +`OAuthProxy` enables authentication with OAuth providers that **don't support Dynamic Client Registration (DCR)**, such as GitHub, Google, Azure, AWS, and most traditional enterprise identity systems. + +When identity providers require manual app registration and fixed credentials, `OAuthProxy` bridges the gap. It presents a DCR-compliant interface to MCP clients (accepting any registration request) while using your pre-registered credentials with the upstream provider. The proxy handles the complexity of callback forwarding, enabling dynamic client callbacks to work with providers that require fixed redirect URIs. + +This class solves the fundamental incompatibility between MCP's expectation of dynamic registration and traditional OAuth providers' requirement for manual app registration. + +For example, the built-in `GitHubProvider` extends `OAuthProxy` to work with GitHub's OAuth system: + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.github import GitHubProvider + +auth = GitHubProvider( + client_id="Ov23li...", # Your GitHub OAuth App ID + client_secret="abc123...", # Your GitHub OAuth App Secret + base_url="https://your-server.com" +) + +mcp = FastMCP(name="GitHub-Protected Server", auth=auth) +``` + +This example uses the GitHub provider, which extends `OAuthProxy` with GitHub-specific token validation. The proxy handles the complete OAuth flow while making GitHub's non-DCR authentication work seamlessly with MCP clients. + +`OAuthProxy` is essential when integrating with OAuth providers that don't support DCR. This includes most established providers like GitHub, Google, and Azure, which require manual app registration through their developer consoles. + +→ **Complete guide**: [OAuth Proxy](/servers/auth/oauth-proxy) + +### OAuthProvider + +`OAuthProvider` implements a complete OAuth 2.0 authorization server within your MCP server. This class handles the full authentication lifecycle from user credential verification to token management. + +The implementation provides all required OAuth endpoints including authorization, token, and discovery endpoints. It manages client registration, user consent, and token lifecycle while integrating with your user storage and authentication logic. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.oauth import MyOAuthProvider + +auth = MyOAuthProvider( + user_store=your_user_database, + client_store=your_client_registry, + # Additional configuration... +) + +mcp = FastMCP(name="Auth Server", auth=auth) +``` + +This example shows the basic structure of a custom OAuth provider. The actual implementation requires significant additional configuration for user management, client registration, and security policies. + +`OAuthProvider` should be used only when you have specific requirements that external providers cannot meet and the expertise to implement OAuth securely. + +→ **Complete guide**: [Full OAuth Server](/servers/auth/full-oauth-server) + +## Configuration Approaches + +FastMCP supports both programmatic configuration for maximum flexibility and environment-based configuration for deployment simplicity. + +### Programmatic Configuration + +Programmatic configuration provides complete control over authentication settings and allows for complex initialization logic. This approach works well during development and when you need to customize authentication behavior based on runtime conditions. + +Authentication providers are instantiated directly in your code with their required parameters. This makes dependencies explicit and allows your IDE to provide helpful autocompletion and type checking. + +### Environment Configuration + + + +Environment-based configuration separates authentication settings from application code, enabling the same codebase to work across different deployment environments without modification. + +FastMCP automatically detects authentication configuration from environment variables when no explicit `auth` parameter is provided. The configuration system supports all authentication providers and their various options. + +#### Provider Configuration + +Authentication providers are configured by specifying the full module path to the provider class: + + +The full module path to the authentication provider class. Examples: +- `fastmcp.server.auth.providers.github.GitHubProvider` - GitHub OAuth +- `fastmcp.server.auth.providers.google.GoogleProvider` - Google OAuth +- `fastmcp.server.auth.providers.jwt.JWTVerifier` - JWT token verification +- `fastmcp.server.auth.providers.workos.WorkOSProvider` - WorkOS OAuth +- `fastmcp.server.auth.providers.workos.AuthKitProvider` - WorkOS AuthKit +- `mycompany.auth.CustomProvider` - Your custom provider class + + +When using providers like GitHub or Google, you'll need to set provider-specific environment variables: + +```bash +# GitHub OAuth +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider +export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="Ov23li..." +export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="github_pat_..." + +# Google OAuth +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleProvider +export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID="123456.apps.googleusercontent.com" +export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET="GOCSPX-..." +``` + +#### Provider-Specific Configuration + +Each provider has its own configuration options set through environment variables: + +```bash +# JWT Token Verification +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.jwt.JWTVerifier +export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.example.com/jwks" +export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.example.com" +export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-server" + +# Custom Provider +export FASTMCP_SERVER_AUTH=mycompany.auth.CustomProvider +# Plus any environment variables your custom provider expects +``` + +With these environment variables set, creating an authenticated FastMCP server requires no additional configuration: + +```python +from fastmcp import FastMCP + +# Authentication automatically configured from environment +mcp = FastMCP(name="My Server") +``` + +This approach simplifies deployment pipelines and follows twelve-factor app principles for configuration management. + +## Choosing Your Implementation + +The authentication approach you choose depends on your existing infrastructure, security requirements, and operational constraints. + +**For OAuth providers without DCR support (GitHub, Google, Azure, AWS, most enterprise systems), use OAuth Proxy.** These providers require manual app registration through their developer consoles. OAuth Proxy bridges the gap by presenting a DCR-compliant interface to MCP clients while using your fixed credentials with the provider. The proxy's callback forwarding pattern enables dynamic client ports to work with providers that require fixed redirect URIs. + +**For identity providers with DCR support (Descope, WorkOS AuthKit, modern auth platforms), use RemoteAuthProvider.** These providers allow clients to dynamically register and obtain credentials without manual configuration. This enables the fully automated authentication flow that MCP is designed for, providing the best user experience and simplest implementation. + +**Token validation works well when you already have authentication infrastructure that issues structured tokens.** If your organization already uses JWT-based systems, API gateways, or enterprise SSO that can generate tokens, this approach integrates seamlessly while keeping your MCP server focused on its core functionality. The simplicity comes from leveraging existing investment in authentication infrastructure. + +**Full OAuth implementation should be avoided unless you have compelling reasons that external providers cannot address.** Air-gapped environments, specialized compliance requirements, or unique organizational constraints might justify this approach, but it requires significant security expertise and ongoing maintenance commitment. The complexity extends far beyond initial implementation to include threat monitoring, security updates, and keeping pace with evolving attack vectors. + +FastMCP's architecture supports migration between these approaches as your requirements evolve. You can integrate with existing token systems initially and migrate to external identity providers as your application scales, or implement custom solutions when your requirements outgrow standard patterns. \ No newline at end of file diff --git a/docs/v2/servers/auth/full-oauth-server.mdx b/docs/v2/servers/auth/full-oauth-server.mdx new file mode 100644 index 000000000..529a01784 --- /dev/null +++ b/docs/v2/servers/auth/full-oauth-server.mdx @@ -0,0 +1,229 @@ +--- +title: Full OAuth Server +sidebarTitle: Full OAuth Server +description: Build a self-contained authentication system where your FastMCP server manages users, issues tokens, and validates them. +icon: users-between-lines + +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + + +**This is an extremely advanced pattern that most users should avoid.** Building a secure OAuth 2.1 server requires deep expertise in authentication protocols, cryptography, and security best practices. The complexity extends far beyond initial implementation to include ongoing security monitoring, threat response, and compliance maintenance. + +**Use [Remote OAuth](/servers/auth/remote-oauth) instead** unless you have compelling requirements that external identity providers cannot meet, such as air-gapped environments or specialized compliance needs. + + +The Full OAuth Server pattern exists to support the MCP protocol specification's requirements. Your FastMCP server becomes both an Authorization Server and Resource Server, handling the complete authentication lifecycle from user login to token validation. + +This documentation exists for completeness - the vast majority of applications should use external identity providers instead. + +## OAuthProvider + +FastMCP provides the `OAuthProvider` abstract class that implements the OAuth 2.1 specification. To use this pattern, you must subclass `OAuthProvider` and implement all required abstract methods. + + +`OAuthProvider` handles OAuth endpoints, protocol flows, and security requirements, but delegates all storage, user management, and business logic to your implementation of the abstract methods. + + +## Required Implementation + +You must implement these abstract methods to create a functioning OAuth server: + +### Client Management + + + + Retrieve client information by ID from your database. + + + + Client identifier to look up + + + + + + Client information object or `None` if client not found + + + + + + Store new client registration information in your database. + + + + Complete client registration information to store + + + + + + No return value + + + + + +### Authorization Flow + + + + Handle authorization request and return redirect URL. Must implement user authentication and consent collection. + + + + OAuth client making the authorization request + + + Authorization request parameters from the client + + + + + + Redirect URL to send the client to + + + + + + Load authorization code from storage by code string. Return `None` if code is invalid or expired. + + + + OAuth client attempting to use the authorization code + + + Authorization code string to look up + + + + + + Authorization code object or `None` if not found + + + + + +### Token Management + + + + Exchange authorization code for access and refresh tokens. Must validate code and create new tokens. + + + + OAuth client exchanging the authorization code + + + Valid authorization code object to exchange + + + + + + New OAuth token containing access and refresh tokens + + + + + + Load refresh token from storage by token string. Return `None` if token is invalid or expired. + + + + OAuth client attempting to use the refresh token + + + Refresh token string to look up + + + + + + Refresh token object or `None` if not found + + + + + + Exchange refresh token for new access/refresh token pair. Must validate scopes and token. + + + + OAuth client using the refresh token + + + Valid refresh token object to exchange + + + Requested scopes for the new access token + + + + + + New OAuth token with updated access and refresh tokens + + + + + + Load an access token by its token string. + + + + The access token to verify + + + + + + The access token object, or `None` if the token is invalid + + + + + + Revoke access or refresh token, marking it as invalid in storage. + + + + Token object to revoke and mark invalid + + + + + + No return value + + + + + + Verify bearer token for incoming requests. Return `AccessToken` if valid, `None` if invalid. + + + + Bearer token string from incoming request + + + + + + Access token object if valid, `None` if invalid or expired + + + + + +Each method must handle storage, validation, security, and error cases according to the OAuth 2.1 specification. The implementation complexity is substantial and requires expertise in OAuth security considerations. + + +**Security Notice:** OAuth server implementation involves numerous security considerations including PKCE, state parameters, redirect URI validation, token binding, replay attack prevention, and secure storage requirements. Mistakes can lead to serious security vulnerabilities. + \ No newline at end of file diff --git a/docs/v2/servers/auth/oauth-proxy.mdx b/docs/v2/servers/auth/oauth-proxy.mdx new file mode 100644 index 000000000..40422044a --- /dev/null +++ b/docs/v2/servers/auth/oauth-proxy.mdx @@ -0,0 +1,594 @@ +--- +title: OAuth Proxy +sidebarTitle: OAuth Proxy +description: Bridge traditional OAuth providers to work seamlessly with MCP's authentication flow. +icon: share +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +The OAuth proxy enables FastMCP servers to authenticate with OAuth providers that **don't support Dynamic Client Registration (DCR)**. This includes virtually all traditional OAuth providers: GitHub, Google, Azure, AWS, Discord, Facebook, and most enterprise identity systems. For providers that do support DCR (like Descope and WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead. + +MCP clients expect to register automatically and obtain credentials on the fly, but traditional providers require manual app registration through their developer consoles. The OAuth proxy bridges this gap by presenting a DCR-compliant interface to MCP clients while using your pre-registered credentials with the upstream provider. When a client attempts to register, the proxy returns your fixed credentials. When a client initiates authorization, the proxy handles the complexity of callback forwarding—storing the client's dynamic callback URL, using its own fixed callback with the provider, then forwarding back to the client after token exchange. + +This approach enables any MCP client (whether using random localhost ports or fixed URLs like Claude.ai) to authenticate with any traditional OAuth provider, all while maintaining full OAuth 2.1 and PKCE security. + + + For providers that support OIDC discovery (Auth0, Google with OIDC + configuration, Azure AD), consider using [`OIDC + Proxy`](/servers/auth/oidc-proxy) for automatic configuration. OIDC Proxy + extends the OAuth proxy to automatically discover endpoints from the provider's + `/.well-known/openid-configuration` URL, simplifying setup. + + +## Implementation + +### Provider Setup Requirements + +Before using the OAuth proxy, you need to register your application with your OAuth provider: + +1. **Register your application** in the provider's developer console (GitHub Settings, Google Cloud Console, Azure Portal, etc.) +2. **Configure the redirect URI** as your FastMCP server URL plus your chosen callback path: + - Default: `https://your-server.com/auth/callback` + - Custom: `https://your-server.com/your/custom/path` (if you set `redirect_path`) + - Development: `http://localhost:8000/auth/callback` +3. **Obtain your credentials**: Client ID and Client Secret +4. **Note the OAuth endpoints**: Authorization URL and Token URL (usually found in the provider's OAuth documentation) + + + The redirect URI you configure with your provider must exactly match your + FastMCP server's URL plus the callback path. If you customize `redirect_path` + in the OAuth proxy, update your provider's redirect URI accordingly. + + +### Basic Setup + +Here's how to implement the OAuth proxy with any provider: + +```python +from fastmcp import FastMCP +from fastmcp.server.auth import OAuthProxy +from fastmcp.server.auth.providers.jwt import JWTVerifier + +# Configure token verification for your provider +# See the Token Verification guide for provider-specific setups +token_verifier = JWTVerifier( + jwks_uri="https://your-provider.com/.well-known/jwks.json", + issuer="https://your-provider.com", + audience="your-app-id" +) + +# Create the OAuth proxy +auth = OAuthProxy( + # Provider's OAuth endpoints (from their documentation) + upstream_authorization_endpoint="https://provider.com/oauth/authorize", + upstream_token_endpoint="https://provider.com/oauth/token", + + # Your registered app credentials + upstream_client_id="your-client-id", + upstream_client_secret="your-client-secret", + + # Token validation (see Token Verification guide) + token_verifier=token_verifier, + + # Your FastMCP server's public URL + base_url="https://your-server.com", + + # Optional: customize the callback path (default is "/auth/callback") + # redirect_path="/custom/callback", +) + +mcp = FastMCP(name="My Server", auth=auth) +``` + +### Configuration Parameters + + + + URL of your OAuth provider's authorization endpoint (e.g., `https://github.com/login/oauth/authorize`) + + + + URL of your OAuth provider's token endpoint (e.g., + `https://github.com/login/oauth/access_token`) + + + + Client ID from your registered OAuth application + + + + Client secret from your registered OAuth application + + + + A [`TokenVerifier`](/servers/auth/token-verification) instance to validate the + provider's tokens + + + + Public URL where OAuth endpoints will be accessible, **including any mount path** (e.g., `https://your-server.com/api`). + + This URL is used to construct OAuth callback URLs and operational endpoints. When mounting under a path prefix, include that prefix in `base_url`. Use `issuer_url` separately to specify where auth server metadata is located (typically at root level). + + + + Path for OAuth callbacks. Must match the redirect URI configured in your OAuth + application + + + + Optional URL of provider's token revocation endpoint + + + + Issuer URL for OAuth authorization server metadata (defaults to `base_url`). + + When `issuer_url` has a path component (either explicitly or by defaulting from `base_url`), FastMCP creates path-aware discovery routes per RFC 8414. For example, if `base_url` is `http://localhost:8000/api`, the authorization server metadata will be at `/.well-known/oauth-authorization-server/api`. + + **Default behavior (recommended for most cases):** + ```python + auth = GitHubProvider( + base_url="http://localhost:8000/api", # OAuth endpoints under /api + # issuer_url defaults to base_url - path-aware discovery works automatically + ) + ``` + + **When to set explicitly:** + Set `issuer_url` to root level only if you want multiple MCP servers to share a single discovery endpoint: + ```python + auth = GitHubProvider( + base_url="http://localhost:8000/api", + issuer_url="http://localhost:8000" # Shared root-level discovery + ) + ``` + + See the [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for complete mounting examples. + + + + Optional URL to your service documentation + + + + Whether to forward PKCE (Proof Key for Code Exchange) to the upstream OAuth + provider. When enabled and the client uses PKCE, the proxy generates its own + PKCE parameters to send upstream while separately validating the client's + PKCE. This ensures end-to-end PKCE security at both layers (client-to-proxy + and proxy-to-upstream). - `True` (default): Forward PKCE for providers that + support it (Google, Azure, AWS, GitHub, etc.) - `False`: Disable only if upstream + provider doesn't support PKCE + + + + Token endpoint authentication method for the upstream OAuth server. Controls + how the proxy authenticates when exchanging authorization codes and refresh + tokens with the upstream provider. - `"client_secret_basic"`: Send credentials + in Authorization header (most common) - `"client_secret_post"`: Send + credentials in request body (required by some providers) - `"none"`: No + authentication (for public clients) - `None` (default): Uses authlib's default + (typically `"client_secret_basic"`) Set this if your provider requires a + specific authentication method and the default doesn't work. + + + + List of allowed redirect URI patterns for MCP clients. Patterns support + wildcards (e.g., `"http://localhost:*"`, `"https://*.example.com/*"`). - + `None` (default): All redirect URIs allowed (for MCP/DCR compatibility) - + Empty list `[]`: No redirect URIs allowed - Custom list: Only matching + patterns allowed These patterns apply to MCP client loopback redirects, NOT + the upstream OAuth app redirect URI. + + + + List of all possible valid scopes for the OAuth provider. These are advertised + to clients through the `/.well-known` endpoints. Defaults to `required_scopes` + from your TokenVerifier if not specified. + + + + Additional parameters to forward to the upstream authorization endpoint. Useful for provider-specific parameters that aren't part of the standard OAuth2 flow. + + For example, Auth0 requires an `audience` parameter to issue JWT tokens: + ```python + extra_authorize_params={"audience": "https://api.example.com"} + ``` + + These parameters are added to every authorization request sent to the upstream provider. + + + + Additional parameters to forward to the upstream token endpoint during code exchange and token refresh. Useful for provider-specific requirements during token operations. + +For example, some providers require additional context during token exchange: + +```python +extra_token_params={"audience": "https://api.example.com"} +``` + +These parameters are included in all token requests to the upstream provider. + + + + + + + Storage backend for persisting OAuth client registrations and upstream tokens. + + **Default behavior:** + By default, clients are automatically persisted to an encrypted disk store, allowing them to survive server restarts as long as the filesystem remains accessible. This means MCP clients only need to register once and can reconnect seamlessly. The disk store is encrypted using a key derived from the JWT Signing Key (which is derived from the upstream client secret by default). For client registrations to survive upstream client secret rotation, you should provide a JWT Signing Key or your own client_storage. + +For production deployments with multiple servers or cloud deployments, see [Storage Backends](/servers/storage-backends) for available options. + + + **When providing custom storage**, wrap it in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest: + + ```python + from key_value.aio.stores.redis import RedisStore + from key_value.aio.wrappers.encryption import FernetEncryptionWrapper + from cryptography.fernet import Fernet + import os + + auth = OAuthProxy( + ..., + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore(host="redis.example.com", port=6379), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) + ) + ``` + + Without encryption, upstream OAuth tokens are stored in plaintext. + + +Testing with in-memory storage (unencrypted): + +```python +from key_value.aio.stores.memory import MemoryStore + +# Use in-memory storage for testing (clients lost on restart) +auth = OAuthProxy(..., client_storage=MemoryStore()) +``` + + + + + + + Secret used to sign FastMCP JWT tokens issued to clients. Accepts any string or bytes - will be derived into a proper 32-byte cryptographic key using HKDF. + + **Default behavior (`None`):** + Derives a 32-byte key using PBKDF2 from the upstream client secret. + + **For production:** + Provide an explicit secret (e.g., from environment variable) to use a fixed key instead of the key derived from the upstream client secret. This allows you to manage keys securely in cloud environments, allows keys to work across multiple instances, and allows you to rotate keys without losing client registrations. + + ```python + import os + + auth = OAuthProxy( + ..., + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], # Any sufficiently complex string! + client_storage=RedisStore(...) # Persistent storage + ) + ``` + + See [HTTP Deployment - OAuth Token Security](/deployment/http#oauth-token-security) for complete production setup. + + + + + Whether to require user consent before authorizing MCP clients. When enabled (default), users see a consent screen that displays which client is requesting access, preventing [confused deputy attacks](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#confused-deputy-problem) by ensuring users explicitly approve new clients. + + **Default behavior (True):** + Users see a consent screen on first authorization. Consent choices are remembered via signed cookies, so users only need to approve each client once. This protects against malicious clients impersonating the user. + + **Disabling consent (False):** + Authorization proceeds directly to the upstream provider without user confirmation. Only use this for local development or testing environments where the security trade-off is acceptable. + + ```python + # Development/testing only - skip consent screen + auth = OAuthProxy( + ..., + require_authorization_consent=False # ⚠️ Security warning: only for local/testing + ) + ``` + + + Disabling consent removes an important security layer. Only disable for local development or testing environments where you fully control all connecting clients. + + + + + Content Security Policy for the consent page. + + - `None` (default): Uses the built-in CSP policy with appropriate directives for form submission + - Empty string `""`: Disables CSP entirely (no meta tag rendered) + - Custom string: Uses the provided value as the CSP policy + + This is useful for organizations that have their own CSP policies and need to override or disable FastMCP's built-in CSP directives. + + ```python + # Disable CSP entirely (let org CSP policies apply) + auth = OAuthProxy(..., consent_csp_policy="") + + # Use custom CSP policy + auth = OAuthProxy(..., consent_csp_policy="default-src 'self'; style-src 'unsafe-inline'") + ``` + + + +### Using Built-in Providers + +FastMCP includes pre-configured providers for common services: + +```python +from fastmcp.server.auth.providers.github import GitHubProvider + +auth = GitHubProvider( + client_id="your-github-app-id", + client_secret="your-github-app-secret", + base_url="https://your-server.com" +) + +mcp = FastMCP(name="My Server", auth=auth) +``` + +Available providers include `GitHubProvider`, `GoogleProvider`, and others. These handle token verification automatically. + +### Token Verification + +The OAuth proxy requires a compatible `TokenVerifier` to validate tokens from your provider. Different providers use different token formats: + +- **JWT tokens** (Google, Azure): Use `JWTVerifier` with the provider's JWKS endpoint +- **Opaque tokens with RFC 7662 introspection** (Auth0, Okta, WorkOS): Use `IntrospectionTokenVerifier` +- **Opaque tokens (provider-specific)** (GitHub, Discord): Use provider-specific verifiers like `GitHubTokenVerifier` + +See the [Token Verification guide](/servers/auth/token-verification) for detailed setup instructions for your provider. + +### Scope Configuration + +OAuth scopes control what permissions your application requests from users. They're configured through your `TokenVerifier` (required for the OAuth proxy to validate tokens from your provider). Set `required_scopes` to automatically request the permissions your application needs: + +```python +JWTVerifier(..., required_scopes = ["read:user", "write:data"]) +``` + +Dynamic clients created by the proxy will automatically include these scopes in their authorization requests. See the [Token Verification](#token-verification) section below for detailed setup. + +### Custom Parameters + +Some OAuth providers require additional parameters beyond the standard OAuth2 flow. Use `extra_authorize_params` and `extra_token_params` to pass provider-specific requirements. For example, Auth0 requires an `audience` parameter to issue JWT tokens instead of opaque tokens: + +```python +auth = OAuthProxy( + upstream_authorization_endpoint="https://your-domain.auth0.com/authorize", + upstream_token_endpoint="https://your-domain.auth0.com/oauth/token", + upstream_client_id="your-auth0-client-id", + upstream_client_secret="your-auth0-client-secret", + + # Auth0-specific audience parameter + extra_authorize_params={"audience": "https://your-api-identifier.com"}, + extra_token_params={"audience": "https://your-api-identifier.com"}, + + token_verifier=JWTVerifier( + jwks_uri="https://your-domain.auth0.com/.well-known/jwks.json", + issuer="https://your-domain.auth0.com/", + audience="https://your-api-identifier.com" + ), + base_url="https://your-server.com" +) +``` + +The proxy also automatically forwards RFC 8707 `resource` parameters from MCP clients to upstream providers that support them. + +## OAuth Flow + +```mermaid +sequenceDiagram + participant Client as MCP Client
(localhost:random) + participant User as User + participant Proxy as FastMCP OAuth Proxy
(server:8000) + participant Provider as OAuth Provider
(GitHub, etc.) + + Note over Client, Proxy: Dynamic Registration (Local) + Client->>Proxy: 1. POST /register
redirect_uri: localhost:54321/callback + Proxy-->>Client: 2. Returns fixed upstream credentials + + Note over Client, User: Authorization with User Consent + Client->>Proxy: 3. GET /authorize
redirect_uri=localhost:54321/callback
code_challenge=CLIENT_CHALLENGE + Note over Proxy: Store transaction with client PKCE
Generate proxy PKCE pair + Proxy->>User: 4. Show consent page
(client details, redirect URI, scopes) + User->>Proxy: 5. Approve/deny consent + Proxy->>Provider: 6. Redirect to provider
redirect_uri=server:8000/auth/callback
code_challenge=PROXY_CHALLENGE + + Note over Provider, Proxy: Provider Callback + Provider->>Proxy: 7. GET /auth/callback
with authorization code + Proxy->>Provider: 8. Exchange code for tokens
code_verifier=PROXY_VERIFIER + Provider-->>Proxy: 9. Access & refresh tokens + + Note over Proxy, Client: Client Callback Forwarding + Proxy->>Client: 10. Redirect to localhost:54321/callback
with new authorization code + + Note over Client, Proxy: Token Exchange + Client->>Proxy: 11. POST /token with code
code_verifier=CLIENT_VERIFIER + Proxy-->>Client: 12. Returns stored provider tokens +``` + +The flow diagram above illustrates the complete OAuth proxy pattern. Let's understand each phase: + +### Registration Phase + +When an MCP client calls `/register` with its dynamic callback URL, the proxy responds with your pre-configured upstream credentials. The client stores these credentials believing it has registered a new app. Meanwhile, the proxy records the client's callback URL for later use. + +### Authorization Phase + +The client initiates OAuth by redirecting to the proxy's `/authorize` endpoint. The proxy: + +1. Stores the client's transaction with its PKCE challenge +2. Generates its own PKCE parameters for upstream security +3. Shows the user a consent page with the client's details, redirect URI, and requested scopes +4. If the user approves (or the client was previously approved), redirects to the upstream provider using the fixed callback URL + +This dual-PKCE approach maintains end-to-end security at both the client-to-proxy and proxy-to-provider layers. The consent step protects against confused deputy attacks by ensuring you explicitly approve each client before it can complete authorization. + +### Callback Phase + +After user authorization, the provider redirects back to the proxy's fixed callback URL. The proxy: + +1. Exchanges the authorization code for tokens with the provider +2. Stores these tokens temporarily +3. Generates a new authorization code for the client +4. Redirects to the client's original dynamic callback URL + +### Token Exchange Phase + +Finally, the client exchanges its authorization code with the proxy to receive the provider's tokens. The proxy validates the client's PKCE verifier before returning the stored tokens. + +This entire flow is transparent to the MCP client—it experiences a standard OAuth flow with dynamic registration, unaware that a proxy is managing the complexity behind the scenes. + +### Token Architecture + +The OAuth proxy implements a **token factory pattern**: instead of directly forwarding tokens from the upstream OAuth provider, it issues its own JWT tokens to MCP clients. This maintains proper OAuth 2.0 token audience boundaries and enables better security controls. + +**How it works:** + +When an MCP client completes authorization, the proxy: + +1. **Receives upstream tokens** from the OAuth provider (GitHub, Google, etc.) +2. **Encrypts and stores** these tokens using Fernet encryption (AES-128-CBC + HMAC-SHA256) +3. **Issues FastMCP JWT tokens** to the client, signed with HS256 + +The FastMCP JWT contains minimal claims: issuer, audience, client ID, scopes, expiration, and a unique token identifier (JTI). The JTI acts as a reference linking to the encrypted upstream token. + +**Token validation:** + +When a client makes an MCP request with its FastMCP token: + +1. **FastMCP validates the JWT** signature, expiration, issuer, and audience +2. **Looks up the upstream token** using the JTI from the validated JWT +3. **Decrypts and validates** the upstream token with the provider + +This two-tier validation ensures that FastMCP tokens can only be used with this server (via audience validation) while maintaining full upstream token security. + +**Token expiry alignment:** + +FastMCP token lifetimes match the upstream token lifetimes. When the upstream token expires, the FastMCP token also expires, maintaining consistent security boundaries. + +**Refresh tokens:** + +The proxy issues its own refresh tokens that map to upstream refresh tokens. When a client uses a FastMCP refresh token, the proxy refreshes the upstream token and issues a new FastMCP access token. + +### PKCE Forwarding + +The OAuth proxy automatically handles PKCE (Proof Key for Code Exchange) when working with providers that support or require it. The proxy generates its own PKCE parameters to send upstream while separately validating the client's PKCE, ensuring end-to-end security at both layers. + +This is enabled by default via the `forward_pkce` parameter and works seamlessly with providers like Google, Azure AD, and GitHub. Only disable it for legacy providers that don't support PKCE: + +```python +# Disable PKCE forwarding only if upstream doesn't support it +auth = OAuthProxy( + ..., + forward_pkce=False # Default is True +) +``` + +### Redirect URI Validation + +While the OAuth proxy accepts all redirect URIs by default (for DCR compatibility), you can restrict which clients can connect by specifying allowed patterns: + +```python +# Allow only localhost clients (common for development) +auth = OAuthProxy( + # ... other parameters ... + allowed_client_redirect_uris=[ + "http://localhost:*", + "http://127.0.0.1:*" + ] +) + +# Allow specific known clients +auth = OAuthProxy( + # ... other parameters ... + allowed_client_redirect_uris=[ + "http://localhost:*", + "https://claude.ai/api/mcp/auth_callback", + "https://*.mycompany.com/auth/*" # Wildcard patterns supported + ] +) +``` + +Check your server logs for "Client registered with redirect_uri" messages to identify what URLs your clients use. + +## Security + +### Key and Storage Management + + +The OAuth proxy requires cryptographic keys for JWT signing and storage encryption, plus persistent storage to maintain valid tokens across server restarts. + +**Default behavior (appropriate for development only):** +- **Mac/Windows**: FastMCP automatically generates keys and stores them in your system keyring. Storage defaults to disk. Tokens survive server restarts. This is **only** suitable for development and local testing. +- **Linux**: Keys are ephemeral (random salt at startup). Storage defaults to memory. Tokens become invalid on server restart. + +**For production:** +Configure the following parameters together: provide a unique `jwt_signing_key` (for signing FastMCP JWTs), and a shared `client_storage` backend (for storing tokens). Both are required for production deployments. Use a network-accessible storage backend like Redis or DynamoDB rather than local disk storage. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** (see the `client_storage` parameter documentation above for examples). The keys accept any secret string and derive proper cryptographic keys using HKDF. See [OAuth Token Security](/deployment/http#oauth-token-security) and [Storage Backends](/servers/storage-backends) for complete production setup. + +### Confused Deputy Attacks + + + +A confused deputy attack allows a malicious client to steal your authorization by tricking you into granting it access under your identity. + +The OAuth proxy works by bridging DCR clients to traditional auth providers, which means that multiple MCP clients connect through a single upstream OAuth application. An attacker can exploit this shared application by registering a malicious client with their own redirect URI, then sending you an authorization link. When you click it, your browser goes through the OAuth flow—but since you may have already authorized this OAuth app before, the provider might auto-approve the request. The authorization code then gets sent to the attacker's redirect URI instead of a legitimate client, giving them access under your credentials. + +#### Mitigation + +FastMCP's OAuth proxy requires you to explicitly consent whenever any new or unrecognized client attempts to connect to your server. Before any authorization happens, you see a consent page showing the client's details, redirect URI, and requested scopes. This gives you the opportunity to review and deny suspicious requests. Once you approve a client, it's remembered so you don't see the consent page again for that client. The consent mechanism is implemented with CSRF tokens and cryptographically signed cookies to prevent tampering. + +![](/assets/images/oauth-proxy-consent-screen.png) + +The consent page automatically displays your server's name, icon, and website URL, if available. These visual identifiers help users confirm they're authorizing the correct server. + + + +**Learn more:** +- [MCP Security Best Practices](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#confused-deputy-problem) - Official specification guidance +- [Confused Deputy Attacks Explained](https://den.dev/blog/mcp-confused-deputy-api-management/) - Detailed walkthrough by Den Delimarsky + +## Environment Configuration + + + +For production deployments, configure the OAuth proxy through environment variables instead of hardcoding credentials: + +```bash +# Specify the provider implementation +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider + +# Provider-specific credentials +export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="Ov23li..." +export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="abc123..." +export FASTMCP_SERVER_AUTH_GITHUB_BASE_URL="https://your-production-server.com" +``` + +With environment configuration, your server code simplifies to: + +```python +from fastmcp import FastMCP + +# Authentication automatically configured from environment +mcp = FastMCP(name="My Server") + +@mcp.tool +def protected_tool(data: str) -> str: + """This tool is now protected by OAuth.""" + return f"Processed: {data}" + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` diff --git a/docs/v2/servers/auth/oidc-proxy.mdx b/docs/v2/servers/auth/oidc-proxy.mdx new file mode 100644 index 000000000..729f9884d --- /dev/null +++ b/docs/v2/servers/auth/oidc-proxy.mdx @@ -0,0 +1,269 @@ +--- +title: OIDC Proxy +sidebarTitle: OIDC Proxy +description: Bridge OIDC providers to work seamlessly with MCP's authentication flow. +icon: share +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +The OIDC proxy enables FastMCP servers to authenticate with OIDC providers that **don't support Dynamic Client Registration (DCR)** out of the box. This includes OAuth providers like: Auth0, Google, Azure, AWS, etc. For providers that do support DCR (like WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead. + +The OIDC proxy is built upon [`OAuthProxy`](/servers/auth/oauth-proxy) so it has all the same functionality under the covers. + +## Implementation + +### Provider Setup Requirements + +Before using the OIDC proxy, you need to register your application with your OAuth provider: + +1. **Register your application** in the provider's developer console (Auth0 Applications, Google Cloud Console, Azure Portal, etc.) +2. **Configure the redirect URI** as your FastMCP server URL plus your chosen callback path: + - Default: `https://your-server.com/auth/callback` + - Custom: `https://your-server.com/your/custom/path` (if you set `redirect_path`) + - Development: `http://localhost:8000/auth/callback` +3. **Obtain your credentials**: Client ID and Client Secret + + + The redirect URI you configure with your provider must exactly match your + FastMCP server's URL plus the callback path. If you customize `redirect_path` + in the OIDC proxy, update your provider's redirect URI accordingly. + + +### Basic Setup + +Here's how to implement the OIDC proxy with any provider: + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.oidc_proxy import OIDCProxy + +# Create the OIDC proxy +auth = OIDCProxy( + # Provider's configuration URL + config_url="https://provider.com/.well-known/openid-configuration", + + # Your registered app credentials + client_id="your-client-id", + client_secret="your-client-secret", + + # Your FastMCP server's public URL + base_url="https://your-server.com", + + # Optional: customize the callback path (default is "/auth/callback") + # redirect_path="/custom/callback", +) + +mcp = FastMCP(name="My Server", auth=auth) +``` + +### Configuration Parameters + + + + URL of your OAuth provider's OIDC configuration + + + + Client ID from your registered OAuth application + + + + Client secret from your registered OAuth application + + + + Public URL of your FastMCP server (e.g., `https://your-server.com`) + + + + Strict flag for configuration validation. When True, requires all OIDC + mandatory fields. + + + + Audience parameter for OIDC providers that require it (e.g., Auth0). This is + typically your API identifier. + + + + HTTP request timeout in seconds for fetching OIDC configuration + + + + + + Custom token verifier for validating tokens. When provided, FastMCP uses your custom verifier instead of creating a default `JWTVerifier`. + + Cannot be used with `algorithm` or `required_scopes` parameters - configure these on your verifier instead. The verifier's `required_scopes` are automatically loaded and advertised. + + + + JWT algorithm to use for token verification (e.g., "RS256"). If not specified, + uses the provider's default. Only used when `token_verifier` is not provided. + + + + List of OAuth scopes for token validation. These are automatically + included in authorization requests. Only used when `token_verifier` is not provided. + + + + Path for OAuth callbacks. Must match the redirect URI configured in your OAuth + application + + + + List of allowed redirect URI patterns for MCP clients. Patterns support wildcards (e.g., `"http://localhost:*"`, `"https://*.example.com/*"`). + - `None` (default): All redirect URIs allowed (for MCP/DCR compatibility) + - Empty list `[]`: No redirect URIs allowed + - Custom list: Only matching patterns allowed + +These patterns apply to MCP client loopback redirects, NOT the upstream OAuth app redirect URI. + + + + + Token endpoint authentication method for the upstream OAuth server. Controls how the proxy authenticates when exchanging authorization codes and refresh tokens with the upstream provider. + - `"client_secret_basic"`: Send credentials in Authorization header (most common) + - `"client_secret_post"`: Send credentials in request body (required by some providers) + - `"none"`: No authentication (for public clients) + - `None` (default): Uses authlib's default (typically `"client_secret_basic"`) + +Set this if your provider requires a specific authentication method and the default doesn't work. + + + + + + + Secret used to sign FastMCP JWT tokens issued to clients. Accepts any string or bytes - will be derived into a proper 32-byte cryptographic key using HKDF. + + **Default behavior (`None`):** + - **Mac/Windows**: Auto-managed via system keyring. Keys are generated once and persisted, surviving server restarts with zero configuration. Keys are automatically derived from server attributes, so this approach, while convenient, is **only** suitable for development and local testing. For production, you must provide an explicit secret. + - **Linux**: Ephemeral (random salt at startup). Tokens become invalid on server restart, triggering client re-authentication. + + **For production:** + Provide an explicit secret (e.g., from environment variable) to use a fixed key instead of the auto-generated one. + + + + + + Storage backend for persisting OAuth client registrations and upstream tokens. + + **Default behavior:** + - **Mac/Windows**: Encrypted DiskStore in your platform's data directory (derived from `platformdirs`) + - **Linux**: MemoryStore (ephemeral - clients lost on restart) + + By default on Mac/Windows, clients are automatically persisted to encrypted disk storage, allowing them to survive server restarts as long as the filesystem remains accessible. This means MCP clients only need to register once and can reconnect seamlessly. On Linux where keyring isn't available, ephemeral storage is used to match the ephemeral key strategy. + +For production deployments with multiple servers or cloud deployments, use a network-accessible storage backend rather than local disk storage. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest.** See [Storage Backends](/servers/storage-backends) for available options. + +Testing with in-memory storage (unencrypted): + +```python +from key_value.aio.stores.memory import MemoryStore + +# Use in-memory storage for testing (clients lost on restart) +auth = OIDCProxy(..., client_storage=MemoryStore()) +``` + +Production with encrypted Redis storage: + +```python +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet +import os + +auth = OIDCProxy( + ..., + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=FernetEncryptionWrapper( + key_value=RedisStore(host="redis.example.com", port=6379), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) +``` + + + + + Whether to require user consent before authorizing MCP clients. When enabled (default), users see a consent screen that displays which client is requesting access. See [OAuthProxy documentation](/servers/auth/oauth-proxy#confused-deputy-attacks) for details on confused deputy attack protection. + + + + Content Security Policy for the consent page. + + - `None` (default): Uses the built-in CSP policy with appropriate directives for form submission + - Empty string `""`: Disables CSP entirely (no meta tag rendered) + - Custom string: Uses the provided value as the CSP policy + + This is useful for organizations that have their own CSP policies and need to override or disable FastMCP's built-in CSP directives. + + + +### Using Built-in Providers + +FastMCP includes pre-configured OIDC providers for common services: + +```python +from fastmcp.server.auth.providers.auth0 import Auth0Provider + +auth = Auth0Provider( + config_url="https://.../.well-known/openid-configuration", + client_id="your-auth0-client-id", + client_secret="your-auth0-client-secret", + audience="https://...", + base_url="https://localhost:8000" +) + +mcp = FastMCP(name="My Server", auth=auth) +``` + +Available providers include `Auth0Provider` at present. + +### Scope Configuration + +OAuth scopes are configured with `required_scopes` to automatically request the permissions your application needs. + +Dynamic clients created by the proxy will automatically include these scopes in their authorization requests. + +## Environment Configuration + + + +For production deployments, configure the OIDC proxy through environment variables instead of hardcoding credentials: + +```bash +# Specify the provider implementation +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider + +# Provider-specific credentials +export FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration +export FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB +export FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq... +export FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://... +export FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://localhost:8000 +``` + +With environment configuration, your server code simplifies to: + +```python +from fastmcp import FastMCP + +# Authentication automatically configured from environment +mcp = FastMCP(name="My Server") + +@mcp.tool +def protected_tool(data: str) -> str: + """This tool is now protected by OAuth.""" + return f"Processed: {data}" + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` diff --git a/docs/v2/servers/auth/remote-oauth.mdx b/docs/v2/servers/auth/remote-oauth.mdx new file mode 100644 index 000000000..befb6ec22 --- /dev/null +++ b/docs/v2/servers/auth/remote-oauth.mdx @@ -0,0 +1,226 @@ +--- +title: Remote OAuth +sidebarTitle: Remote OAuth +description: Integrate your FastMCP server with external identity providers like Descope, WorkOS, Auth0, and corporate SSO systems. +icon: camera-cctv +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +Remote OAuth integration allows your FastMCP server to leverage external identity providers that **support Dynamic Client Registration (DCR)**. With DCR, MCP clients can automatically register themselves with the identity provider and obtain credentials without any manual configuration. This provides enterprise-grade authentication with fully automated flows, making it ideal for production applications with modern identity providers. + + +**When to use RemoteAuthProvider vs OAuth Proxy:** +- **RemoteAuthProvider**: For providers WITH Dynamic Client Registration (Descope, WorkOS AuthKit, modern OIDC providers) +- **OAuth Proxy**: For providers WITHOUT Dynamic Client Registration (GitHub, Google, Azure, AWS, Discord, etc.) + +RemoteAuthProvider requires DCR support for fully automated client registration and authentication. + + +## DCR-Enabled Providers + +RemoteAuthProvider works with identity providers that support **Dynamic Client Registration (DCR)** - a critical capability that enables automated authentication flows: + +| Feature | DCR Providers (RemoteAuth) | Non-DCR Providers (OAuth Proxy) | +|---------|---------------------------|--------------------------------| +| **Client Registration** | Automatic via API | Manual in provider console | +| **Credentials** | Dynamic per client | Fixed app credentials | +| **Configuration** | Zero client config | Pre-shared credentials | +| **Examples** | Descope, WorkOS AuthKit, modern OIDC | GitHub, Google, Azure | +| **FastMCP Class** | `RemoteAuthProvider` | [`OAuthProxy`](/servers/auth/oauth-proxy) | + +If your provider doesn't support DCR (most traditional OAuth providers), you'll need to use [`OAuth Proxy`](/servers/auth/oauth-proxy) instead, which bridges the gap between MCP's DCR expectations and fixed OAuth credentials. + +## The Remote OAuth Challenge + +Traditional OAuth flows assume human users with web browsers who can interact with login forms, consent screens, and redirects. MCP clients operate differently - they're often automated systems that need to authenticate programmatically without human intervention. + +This creates several unique requirements that standard OAuth implementations don't address well: + +**Automatic Discovery**: MCP clients must discover authentication requirements by examining server metadata rather than encountering HTTP redirects. They need to know which identity provider to use and how to reach it before making any authenticated requests. + +**Programmatic Registration**: Clients need to register themselves with identity providers automatically. Manual client registration doesn't work when clients might be dynamically created tools or services. + +**Seamless Token Management**: Clients must obtain, store, and refresh tokens without user interaction. The authentication flow needs to work in headless environments where no human is available to complete OAuth consent flows. + +**Protocol Integration**: The authentication process must integrate cleanly with MCP's JSON-RPC transport layer and error handling mechanisms. + +These requirements mean that your MCP server needs to do more than just validate tokens - it needs to provide discovery metadata that enables MCP clients to understand and navigate your authentication requirements automatically. + +## MCP Authentication Discovery + +MCP authentication discovery relies on well-known endpoints that clients can examine to understand your authentication requirements. Your server becomes a bridge between MCP clients and your chosen identity provider. + +The core discovery endpoint is `/.well-known/oauth-protected-resource`, which tells clients that your server requires OAuth authentication and identifies the authorization servers you trust. This endpoint contains static metadata that points clients to your identity provider without requiring any dynamic lookups. + +```mermaid +sequenceDiagram + participant Client + participant FastMCPServer as FastMCP Server + participant ExternalIdP as Identity Provider + + Client->>FastMCPServer: 1. GET /.well-known/oauth-protected-resource + FastMCPServer-->>Client: 2. "Use https://my-idp.com for auth" + + note over Client, ExternalIdP: Client goes directly to the IdP + Client->>ExternalIdP: 3. Authenticate & get token via DCR + ExternalIdP-->>Client: 4. Access token + + Client->>FastMCPServer: 5. MCP request with Bearer token + FastMCPServer->>FastMCPServer: 6. Verify token signature + FastMCPServer-->>Client: 7. MCP response +``` + +This flow separates concerns cleanly: your MCP server handles resource protection and token validation, while your identity provider handles user authentication and token issuance. The client coordinates between these systems using standardized OAuth discovery mechanisms. + +## FastMCP Remote Authentication + + + +FastMCP provides `RemoteAuthProvider` to handle the complexities of remote OAuth integration. This class combines token validation capabilities with the OAuth discovery metadata that MCP clients require. + +### RemoteAuthProvider + +`RemoteAuthProvider` works by composing a [`TokenVerifier`](/servers/auth/token-verification) with authorization server information. A `TokenVerifier` is another FastMCP authentication class that focuses solely on token validation - signature verification, expiration checking, and claim extraction. The `RemoteAuthProvider` takes that token validation capability and adds the OAuth discovery endpoints that enable MCP clients to automatically find and authenticate with your identity provider. + +This composition pattern means you can use any token validation strategy while maintaining consistent OAuth discovery behavior: +- **JWT tokens**: Use `JWTVerifier` for self-contained tokens +- **Opaque tokens**: Use `IntrospectionTokenVerifier` for RFC 7662 introspection +- **Custom validation**: Implement your own `TokenVerifier` subclass + +The separation allows you to change token validation approaches without affecting the client discovery experience. + +The class automatically generates the required OAuth metadata endpoints using the MCP SDK's standardized route creation functions. This ensures compatibility with MCP clients while reducing the implementation complexity for server developers. + +### Basic Implementation + +Most applications can use `RemoteAuthProvider` directly without subclassing. The implementation requires a `TokenVerifier` instance, a list of trusted authorization servers, and your server's URL for metadata generation. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth import RemoteAuthProvider +from fastmcp.server.auth.providers.jwt import JWTVerifier +from pydantic import AnyHttpUrl + +# Configure token validation for your identity provider +token_verifier = JWTVerifier( + jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json", + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" +) + +# Create the remote auth provider +auth = RemoteAuthProvider( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")], + base_url="https://api.yourcompany.com", # Your server base URL + # Optional: customize allowed client redirect URIs (defaults to localhost only) + allowed_client_redirect_uris=["http://localhost:*", "http://127.0.0.1:*"] +) + +mcp = FastMCP(name="Company API", auth=auth) +``` + +This configuration creates a server that accepts tokens issued by `auth.yourcompany.com` and provides the OAuth discovery metadata that MCP clients need. The `JWTVerifier` handles token validation using your identity provider's public keys, while the `RemoteAuthProvider` generates the required OAuth endpoints. + +The `authorization_servers` list tells MCP clients which identity providers you trust. The `base_url` identifies your server in OAuth metadata, enabling proper token audience validation. **Important**: The `base_url` should point to your server base URL - for example, if your MCP server is accessible at `https://api.yourcompany.com/mcp`, use `https://api.yourcompany.com` as the base URL. + +### Custom Endpoints + +You can extend `RemoteAuthProvider` to add additional endpoints beyond the standard OAuth protected resource metadata. These don't have to be OAuth-specific - you can add any endpoints your authentication integration requires. + +```python +import httpx +from starlette.responses import JSONResponse +from starlette.routing import Route + +class CompanyAuthProvider(RemoteAuthProvider): + def __init__(self): + token_verifier = JWTVerifier( + jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json", + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" + ) + + super().__init__( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")], + base_url="https://api.yourcompany.com" # Your server base URL + ) + + def get_routes(self) -> list[Route]: + """Add custom endpoints to the standard protected resource routes.""" + + # Get the standard OAuth protected resource routes + routes = super().get_routes() + + # Add authorization server metadata forwarding for client convenience + async def authorization_server_metadata(request): + async with httpx.AsyncClient() as client: + response = await client.get( + "https://auth.yourcompany.com/.well-known/oauth-authorization-server" + ) + response.raise_for_status() + return JSONResponse(response.json()) + + routes.append( + Route("/.well-known/oauth-authorization-server", authorization_server_metadata) + ) + + return routes + +mcp = FastMCP(name="Company API", auth=CompanyAuthProvider()) +``` + +This pattern uses `super().get_routes()` to get the standard protected resource routes, then adds additional endpoints as needed. A common use case is providing authorization server metadata forwarding, which allows MCP clients to discover your identity provider's capabilities through your MCP server rather than contacting the identity provider directly. + +## WorkOS AuthKit Integration + +WorkOS AuthKit provides an excellent example of remote OAuth integration. The `AuthKitProvider` demonstrates how to implement both token validation and OAuth metadata forwarding in a production-ready package. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.workos import AuthKitProvider + +auth = AuthKitProvider( + authkit_domain="https://your-project.authkit.app", + base_url="https://your-mcp-server.com" +) + +mcp = FastMCP(name="Protected Application", auth=auth) +``` + +The `AuthKitProvider` automatically configures JWT validation against WorkOS's public keys and provides both protected resource metadata and authorization server metadata forwarding. This implementation handles the complete remote OAuth integration with minimal configuration. + +WorkOS's support for Dynamic Client Registration makes it particularly well-suited for MCP applications. Clients can automatically register themselves with your WorkOS project and obtain the credentials needed for authentication without manual intervention. + +→ **Complete WorkOS tutorial**: [AuthKit Integration Guide](/integrations/authkit) + +## Client Redirect URI Security + + +`RemoteAuthProvider` also supports the `allowed_client_redirect_uris` parameter for controlling which redirect URIs are accepted from MCP clients during DCR: + +- `None` (default): Only localhost patterns allowed +- Custom list: Specify allowed patterns with wildcard support +- Empty list `[]`: Allow all (not recommended) + +This provides defense-in-depth even though DCR providers typically validate redirect URIs themselves. + + +## Implementation Considerations + +Remote OAuth integration requires careful attention to several technical details that affect reliability and security. + +**Token Validation Performance**: Your server validates every incoming token by checking signatures against your identity provider's public keys. Consider implementing key caching and rotation handling to minimize latency while maintaining security. + +**Error Handling**: Network issues with your identity provider can affect token validation. Implement appropriate timeouts, retry logic, and graceful degradation to maintain service availability during identity provider outages. + +**Audience Validation**: Ensure that tokens intended for your server are not accepted by other applications. Proper audience validation prevents token misuse across different services in your ecosystem. + +**Scope Management**: Map token scopes to your application's permission model consistently. Consider how scope changes affect existing tokens and plan for smooth permission updates. + +The complexity of these considerations reinforces why external identity providers are recommended over custom OAuth implementations. Established providers handle these technical details with extensive testing and operational experience. \ No newline at end of file diff --git a/docs/v2/servers/auth/token-verification.mdx b/docs/v2/servers/auth/token-verification.mdx new file mode 100644 index 000000000..e14282663 --- /dev/null +++ b/docs/v2/servers/auth/token-verification.mdx @@ -0,0 +1,341 @@ +--- +title: Token Verification +sidebarTitle: Token Verification +description: Protect your server by validating bearer tokens issued by external systems. +icon: key +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +Token verification enables your FastMCP server to validate bearer tokens issued by external systems without participating in user authentication flows. Your server acts as a pure resource server, focusing on token validation and authorization decisions while delegating identity management to other systems in your infrastructure. + + +Token verification operates somewhat outside the formal MCP authentication flow, which expects OAuth-style discovery. It's best suited for internal systems, microservices architectures, or when you have full control over token generation and distribution. + + +## Understanding Token Verification + +Token verification addresses scenarios where authentication responsibility is distributed across multiple systems. Your MCP server receives structured tokens containing identity and authorization information, validates their authenticity, and makes access control decisions based on their contents. + +This pattern emerges naturally in microservices architectures where a central authentication service issues tokens that multiple downstream services validate independently. It also works well when integrating MCP servers into existing systems that already have established token-based authentication mechanisms. + +### The Token Verification Model + +Token verification treats your MCP server as a resource server in OAuth terminology. The key insight is that token validation and token issuance are separate concerns that can be handled by different systems. + +**Token Issuance**: Another system (API gateway, authentication service, or identity provider) handles user authentication and creates signed tokens containing identity and permission information. + +**Token Validation**: Your MCP server receives these tokens, verifies their authenticity using cryptographic signatures, and extracts authorization information from their claims. + +**Access Control**: Based on token contents, your server determines what resources, tools, and prompts the client can access. + +This separation allows your MCP server to focus on its core functionality while leveraging existing authentication infrastructure. The token acts as a portable proof of identity that travels with each request. + +### Token Security Considerations + +Token-based authentication relies on cryptographic signatures to ensure token integrity. Your MCP server validates tokens using public keys corresponding to the private keys used for token creation. This asymmetric approach means your server never needs access to signing secrets. + +Token validation must address several security requirements: signature verification ensures tokens haven't been tampered with, expiration checking prevents use of stale tokens, and audience validation ensures tokens intended for your server aren't accepted by other systems. + +The challenge in MCP environments is that clients need to obtain valid tokens before making requests, but the MCP protocol doesn't provide built-in discovery mechanisms for token endpoints. Clients must obtain tokens through separate channels or prior configuration. + + +## TokenVerifier Class + +FastMCP provides the `TokenVerifier` class to handle token validation complexity while remaining flexible about token sources and validation strategies. + +`TokenVerifier` focuses exclusively on token validation without providing OAuth discovery metadata. This makes it ideal for internal systems where clients already know how to obtain tokens, or for microservices that trust tokens from known issuers. + +The class validates token signatures, checks expiration timestamps, and extracts authorization information from token claims. It supports various token formats and validation strategies while maintaining a consistent interface for authorization decisions. + +You can subclass `TokenVerifier` to implement custom validation logic for specialized token formats or validation requirements. The base class handles common patterns while allowing extension for unique use cases. + +## JWT Token Verification + +JSON Web Tokens (JWTs) represent the most common token format for modern applications. FastMCP's `JWTVerifier` validates JWTs using industry-standard cryptographic techniques and claim validation. + +### JWKS Endpoint Integration + +JWKS endpoint integration provides the most flexible approach for production systems. The verifier automatically fetches public keys from a JSON Web Key Set endpoint, enabling automatic key rotation without server configuration changes. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.jwt import JWTVerifier + +# Configure JWT verification against your identity provider +verifier = JWTVerifier( + jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json", + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" +) + +mcp = FastMCP(name="Protected API", auth=verifier) +``` + +This configuration creates a server that validates JWTs issued by `auth.yourcompany.com`. The verifier periodically fetches public keys from the JWKS endpoint and validates incoming tokens against those keys. Only tokens with the correct issuer and audience claims will be accepted. + +The `issuer` parameter ensures tokens come from your trusted authentication system, while `audience` validation prevents tokens intended for other services from being accepted by your MCP server. + +### Symmetric Key Verification (HMAC) + +Symmetric key verification uses a shared secret for both signing and validation, making it ideal for internal microservices and trusted environments where the same secret can be securely distributed to both token issuers and validators. + +This approach is commonly used in microservices architectures where services share a secret key, or when your authentication service and MCP server are both managed by the same organization. The HMAC algorithms (HS256, HS384, HS512) provide strong security when the shared secret is properly managed. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.jwt import JWTVerifier + +# Use a shared secret for symmetric key verification +verifier = JWTVerifier( + public_key="your-shared-secret-key-minimum-32-chars", # Despite the name, this accepts symmetric secrets + issuer="internal-auth-service", + audience="mcp-internal-api", + algorithm="HS256" # or HS384, HS512 for stronger security +) + +mcp = FastMCP(name="Internal API", auth=verifier) +``` + +The verifier will validate tokens signed with the same secret using the specified HMAC algorithm. This approach offers several advantages for internal systems: + +- **Simplicity**: No key pair management or certificate distribution +- **Performance**: HMAC operations are typically faster than RSA +- **Compatibility**: Works well with existing microservice authentication patterns + + +The parameter is named `public_key` for backwards compatibility, but when using HMAC algorithms (HS256/384/512), it accepts the symmetric secret string. + + + +**Security Considerations for Symmetric Keys:** +- Use a strong, randomly generated secret (minimum 32 characters recommended) +- Never expose the secret in logs, error messages, or version control +- Implement secure key distribution and rotation mechanisms +- Consider using asymmetric keys (RSA/ECDSA) for external-facing APIs + + +### Static Public Key Verification + +Static public key verification works when you have a fixed RSA or ECDSA signing key and don't need automatic key rotation. This approach is primarily useful for development environments or controlled deployments where JWKS endpoints aren't available. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.jwt import JWTVerifier + +# Use a static public key for token verification +public_key_pem = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... +-----END PUBLIC KEY-----""" + +verifier = JWTVerifier( + public_key=public_key_pem, + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" +) + +mcp = FastMCP(name="Protected API", auth=verifier) +``` + +This configuration validates tokens using a specific RSA or ECDSA public key. The key must correspond to the private key used by your token issuer. While less flexible than JWKS endpoints, this approach can be useful in development environments or when testing with fixed keys. +## Opaque Token Verification + +Many authorization servers issue opaque tokens rather than self-contained JWTs. Opaque tokens are random strings that carry no information themselves - the authorization server maintains their state and validation requires querying the server. FastMCP supports opaque token validation through OAuth 2.0 Token Introspection (RFC 7662). + +### Understanding Opaque Tokens + +Opaque tokens differ fundamentally from JWTs in their verification model. Where JWTs carry signed claims that can be validated locally, opaque tokens require network calls to the issuing authorization server for validation. The authorization server maintains token state and can revoke tokens immediately, providing stronger security guarantees for sensitive operations. + +This approach trades performance (network latency on each validation) for security and flexibility. Authorization servers can revoke opaque tokens instantly, implement complex authorization logic, and maintain detailed audit logs of token usage. Many enterprise OAuth providers default to opaque tokens for these security advantages. + +### Token Introspection Protocol + +RFC 7662 standardizes how resource servers validate opaque tokens. The protocol defines an introspection endpoint where resource servers authenticate using client credentials and receive token metadata including active status, scopes, expiration, and subject identity. + +FastMCP implements this protocol through the `IntrospectionTokenVerifier` class, handling authentication, request formatting, and response parsing according to the specification. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.introspection import IntrospectionTokenVerifier + +# Configure introspection with your OAuth provider +verifier = IntrospectionTokenVerifier( + introspection_url="https://auth.yourcompany.com/oauth/introspect", + client_id="mcp-resource-server", + client_secret="your-client-secret", + required_scopes=["api:read", "api:write"] +) + +mcp = FastMCP(name="Protected API", auth=verifier) +``` + +The verifier authenticates to the introspection endpoint using HTTP Basic Auth with your client credentials. When a request arrives with a bearer token, FastMCP queries the introspection endpoint to determine if the token is active and has sufficient scopes. + +## Development and Testing + +Development environments often need simpler token management without the complexity of full JWT infrastructure. FastMCP provides tools specifically designed for these scenarios. + +### Static Token Verification + +Static token verification enables rapid development by accepting predefined tokens with associated claims. This approach eliminates the need for token generation infrastructure during development and testing. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.jwt import StaticTokenVerifier + +# Define development tokens and their associated claims +verifier = StaticTokenVerifier( + tokens={ + "dev-alice-token": { + "client_id": "alice@company.com", + "scopes": ["read:data", "write:data", "admin:users"] + }, + "dev-guest-token": { + "client_id": "guest-user", + "scopes": ["read:data"] + } + }, + required_scopes=["read:data"] +) + +mcp = FastMCP(name="Development Server", auth=verifier) +``` + +Clients can now authenticate using `Authorization: Bearer dev-alice-token` headers. The server will recognize the token and load the associated claims for authorization decisions. This approach enables immediate development without external dependencies. + + +Static token verification stores tokens as plain text and should never be used in production environments. It's designed exclusively for development and testing scenarios. + + + +### Debug/Custom Token Verification + + + +The `DebugTokenVerifier` provides maximum flexibility for testing and special cases where standard token verification isn't applicable. It delegates validation to a user-provided callable, making it useful for prototyping, testing scenarios, or handling opaque tokens without introspection endpoints. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.debug import DebugTokenVerifier + +# Accept all tokens (useful for rapid development) +verifier = DebugTokenVerifier() + +mcp = FastMCP(name="Development Server", auth=verifier) +``` + +By default, `DebugTokenVerifier` accepts any non-empty token as valid. This eliminates authentication barriers during early development, allowing you to focus on core functionality before adding security. + +For more controlled testing, provide custom validation logic: + +```python +from fastmcp.server.auth.providers.debug import DebugTokenVerifier + +# Synchronous validation - check token prefix +verifier = DebugTokenVerifier( + validate=lambda token: token.startswith("dev-"), + client_id="development-client", + scopes=["read", "write"] +) + +mcp = FastMCP(name="Development Server", auth=verifier) +``` + +The validation callable can also be async, enabling database lookups or external service calls: + +```python +from fastmcp.server.auth.providers.debug import DebugTokenVerifier + +# Asynchronous validation - check against cache +async def validate_token(token: str) -> bool: + # Check if token exists in Redis, database, etc. + return await redis.exists(f"valid_tokens:{token}") + +verifier = DebugTokenVerifier( + validate=validate_token, + client_id="api-client", + scopes=["api:access"] +) + +mcp = FastMCP(name="Custom API", auth=verifier) +``` + +**Use Cases:** + +- **Testing**: Accept any token during integration tests without setting up token infrastructure +- **Prototyping**: Quickly validate concepts without authentication complexity +- **Opaque tokens without introspection**: When you have tokens from an IDP that provides no introspection endpoint, and you're willing to accept tokens without validation (validation happens later at the upstream service) +- **Custom token formats**: Implement validation for non-standard token formats or legacy systems + + +`DebugTokenVerifier` bypasses standard security checks. Only use in controlled environments (development, testing) or when you fully understand the security implications. For production, use proper JWT or introspection-based verification. + + +### Test Token Generation + +Test token generation helps when you need to test JWT verification without setting up complete identity infrastructure. FastMCP includes utilities for generating test key pairs and signed tokens. + +```python +from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair + +# Generate a key pair for testing +key_pair = RSAKeyPair.generate() + +# Configure your server with the public key +verifier = JWTVerifier( + public_key=key_pair.public_key, + issuer="https://test.yourcompany.com", + audience="test-mcp-server" +) + +# Generate a test token using the private key +test_token = key_pair.create_token( + subject="test-user-123", + issuer="https://test.yourcompany.com", + audience="test-mcp-server", + scopes=["read", "write", "admin"] +) + +print(f"Test token: {test_token}") +``` + +This pattern enables comprehensive testing of JWT validation logic without depending on external token issuers. The generated tokens are cryptographically valid and will pass all standard JWT validation checks. + +## Environment Configuration + + + +FastMCP supports both programmatic and environment-based configuration for token verification, enabling flexible deployment across different environments. + +Environment-based configuration separates authentication settings from application code, following twelve-factor app principles and simplifying deployment pipelines. + +```bash +# Enable JWT verification +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.jwt.JWTVerifier + +# For asymmetric verification with JWKS endpoint: +export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.company.com/.well-known/jwks.json" +export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.company.com" +export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-production-api" +export FASTMCP_SERVER_AUTH_JWT_REQUIRED_SCOPES="read:data,write:data" + +# OR for symmetric key verification (HMAC): +export FASTMCP_SERVER_AUTH_JWT_PUBLIC_KEY="your-shared-secret-key-minimum-32-chars" +export FASTMCP_SERVER_AUTH_JWT_ALGORITHM="HS256" # or HS384, HS512 +export FASTMCP_SERVER_AUTH_JWT_ISSUER="internal-auth-service" +export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-internal-api" +``` + +With these environment variables configured, your FastMCP server automatically enables JWT verification: + +```python +from fastmcp import FastMCP + +# Authentication automatically configured from environment +mcp = FastMCP(name="Production API") +``` + +This approach enables the same codebase to run across development, staging, and production environments with different authentication requirements. Development might use static tokens while production uses JWT verification, all controlled through environment configuration. + diff --git a/docs/v2/servers/composition.mdx b/docs/v2/servers/composition.mdx new file mode 100644 index 000000000..6c614000c --- /dev/null +++ b/docs/v2/servers/composition.mdx @@ -0,0 +1,326 @@ +--- +title: Server Composition +sidebarTitle: Composition +description: Combine multiple FastMCP servers into a single, larger application using mounting and importing. +icon: puzzle-piece +--- +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +As your MCP applications grow, you might want to organize your tools, resources, and prompts into logical modules or reuse existing server components. FastMCP supports composition through two methods: + +- **`import_server`**: For a one-time copy of components with prefixing (static composition). +- **`mount`**: For creating a live link where the main server delegates requests to the subserver (dynamic composition). + +## Why Compose Servers? + +- **Modularity**: Break down large applications into smaller, focused servers (e.g., a `WeatherServer`, a `DatabaseServer`, a `CalendarServer`). +- **Reusability**: Create common utility servers (e.g., a `TextProcessingServer`) and mount them wherever needed. +- **Teamwork**: Different teams can work on separate FastMCP servers that are later combined. +- **Organization**: Keep related functionality grouped together logically. + +### Importing vs Mounting + +The choice of importing or mounting depends on your use case and requirements. + +| Feature | Importing | Mounting | +|---------|----------------|---------| +| **Method** | `FastMCP.import_server(server, prefix=None)` | `FastMCP.mount(server, prefix=None)` | +| **Composition Type** | One-time copy (static) | Live link (dynamic) | +| **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected | +| **Performance** | Fast - no runtime delegation | Slower - affected by slowest mounted server | +| **Prefix** | Optional - omit for original names | Optional - omit for original names | +| **Best For** | Bundling finalized components, performance-critical setups | Modular runtime composition | + +### Proxy Servers + +FastMCP supports [MCP proxying](/servers/proxy), which allows you to mirror a local or remote server in a local FastMCP instance. Proxies are fully compatible with both importing and mounting. + + + +You can also create proxies from configuration dictionaries that follow the MCPConfig schema, which is useful for quickly connecting to one or more remote servers. See the [Proxy Servers documentation](/servers/proxy#configuration-based-proxies) for details on configuration-based proxying. Note that MCPConfig follows an emerging standard and its format may evolve over time. + +Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies. When prefixes are used, resource URIs are prefixed using path format (since 2.4.0): `resource://prefix/path/to/resource`. + +## Importing (Static Composition) + +The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). An optional `prefix` can be provided to avoid naming conflicts. If no prefix is provided, components are imported without modification. When multiple servers are imported with the same prefix (or no prefix), the most recently imported server's components take precedence. + +```python +from fastmcp import FastMCP +import asyncio + +# Define subservers +weather_mcp = FastMCP(name="WeatherService") + +@weather_mcp.tool +def get_forecast(city: str) -> dict: + """Get weather forecast.""" + return {"city": city, "forecast": "Sunny"} + +@weather_mcp.resource("data://cities/supported") +def list_supported_cities() -> list[str]: + """List cities with weather support.""" + return ["London", "Paris", "Tokyo"] + +# Define main server +main_mcp = FastMCP(name="MainApp") + +# Import subserver +async def setup(): + await main_mcp.import_server(weather_mcp, prefix="weather") + +# Result: main_mcp now contains prefixed components: +# - Tool: "weather_get_forecast" +# - Resource: "data://weather/cities/supported" + +if __name__ == "__main__": + asyncio.run(setup()) + main_mcp.run() +``` + +### How Importing Works + +When you call `await main_mcp.import_server(subserver, prefix={whatever})`: + +1. **Tools**: All tools from `subserver` are added to `main_mcp` with names prefixed using `{prefix}_`. + - `subserver.tool(name="my_tool")` becomes `main_mcp.tool(name="{prefix}_my_tool")`. +2. **Resources**: All resources are added with both URIs and names prefixed. + - URI: `subserver.resource(uri="data://info")` becomes `main_mcp.resource(uri="data://{prefix}/info")`. + - Name: `resource.name` becomes `"{prefix}_{resource.name}"`. +3. **Resource Templates**: Templates are prefixed similarly to resources. + - URI: `subserver.resource(uri="data://{id}")` becomes `main_mcp.resource(uri="data://{prefix}/{id}")`. + - Name: `template.name` becomes `"{prefix}_{template.name}"`. +4. **Prompts**: All prompts are added with names prefixed using `{prefix}_`. + - `subserver.prompt(name="my_prompt")` becomes `main_mcp.prompt(name="{prefix}_my_prompt")`. + +Note that `import_server` performs a **one-time copy** of components. Changes made to the `subserver` *after* importing **will not** be reflected in `main_mcp`. The `subserver`'s `lifespan` context is also **not** executed by the main server. + + +The `prefix` parameter is optional. If omitted, components are imported without modification. + + +#### Importing Without Prefixes + + + +You can also import servers without specifying a prefix, which copies components using their original names: + +```python + +from fastmcp import FastMCP +import asyncio + +# Define subservers +weather_mcp = FastMCP(name="WeatherService") + +@weather_mcp.tool +def get_forecast(city: str) -> dict: + """Get weather forecast.""" + return {"city": city, "forecast": "Sunny"} + +@weather_mcp.resource("data://cities/supported") +def list_supported_cities() -> list[str]: + """List cities with weather support.""" + return ["London", "Paris", "Tokyo"] + +# Define main server +main_mcp = FastMCP(name="MainApp") + +# Import subserver +async def setup(): + # Import without prefix - components keep original names + await main_mcp.import_server(weather_mcp) + +# Result: main_mcp now contains: +# - Tool: "get_forecast" (original name preserved) +# - Resource: "data://cities/supported" (original URI preserved) + +if __name__ == "__main__": + asyncio.run(setup()) + main_mcp.run() +``` + +#### Conflict Resolution + + + +When importing multiple servers with the same prefix, or no prefix, components from the **most recently imported** server take precedence. + + + + +## Mounting (Live Linking) + +The `mount()` method creates a **live link** between the `main_mcp` server and the `subserver`. Instead of copying components, requests for components matching the optional `prefix` are **delegated** to the `subserver` at runtime. If no prefix is provided, the subserver's components are accessible without prefixing. When multiple servers are mounted with the same prefix (or no prefix), the most recently mounted server takes precedence for conflicting component names. + +```python +import asyncio +from fastmcp import FastMCP, Client + +# Define subserver +dynamic_mcp = FastMCP(name="DynamicService") + +@dynamic_mcp.tool +def initial_tool(): + """Initial tool demonstration.""" + return "Initial Tool Exists" + +# Mount subserver (synchronous operation) +main_mcp = FastMCP(name="MainAppLive") +main_mcp.mount(dynamic_mcp, prefix="dynamic") + +# Add a tool AFTER mounting - it will be accessible through main_mcp +@dynamic_mcp.tool +def added_later(): + """Tool added after mounting.""" + return "Tool Added Dynamically!" + +# Testing access to mounted tools +async def test_dynamic_mount(): + tools = await main_mcp.get_tools() + print("Available tools:", list(tools.keys())) + # Shows: ['dynamic_initial_tool', 'dynamic_added_later'] + + async with Client(main_mcp) as client: + result = await client.call_tool("dynamic_added_later") + print("Result:", result.data) + # Shows: "Tool Added Dynamically!" + +if __name__ == "__main__": + asyncio.run(test_dynamic_mount()) +``` + +### How Mounting Works + +When mounting is configured: + +1. **Live Link**: The parent server establishes a connection to the mounted server. +2. **Dynamic Updates**: Changes to the mounted server are immediately reflected when accessed through the parent. +3. **Prefixed Access**: The parent server uses prefixes to route requests to the mounted server. +4. **Delegation**: Requests for components matching the prefix are delegated to the mounted server at runtime. + +The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts. This includes prefixing both the URIs/keys and the names of resources and templates for better identification in multi-server configurations. + + + The `prefix` parameter is optional. If omitted, components are mounted without modification. + + + +When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application. + + +#### Performance Considerations + +Due to the "live link", operations like `list_tools()` on the parent server will be impacted by the speed of the slowest mounted server. In particular, HTTP-based mounted servers can introduce significant latency (300-400ms vs 1-2ms for local tools), and this slowdown affects the whole server, not just interactions with the HTTP-proxied tools. If performance is important, importing tools via [`import_server()`](#importing-static-composition) may be a more appropriate solution as it copies components once at startup rather than delegating requests at runtime. + +#### Mounting Without Prefixes + + + +You can also mount servers without specifying a prefix, which makes components accessible without prefixing. This works identically to [importing without prefixes](#importing-without-prefixes), including [conflict resolution](#conflict-resolution). + + + + +### Direct vs. Proxy Mounting + + + +FastMCP supports two mounting modes: + +1. **Direct Mounting** (default): The parent server directly accesses the mounted server's objects in memory. + - No client lifecycle events occur on the mounted server + - The mounted server's lifespan context is not executed + - Communication is handled through direct method calls + +2. **Proxy Mounting**: The parent server treats the mounted server as a separate entity and communicates with it through a client interface. + - Full client lifecycle events occur on the mounted server + - The mounted server's lifespan is executed when a client connects + - Communication happens via an in-memory Client transport + +```python +# Direct mounting (default when no custom lifespan) +main_mcp.mount(api_server, prefix="api") + +# Proxy mounting (preserves full client lifecycle) +main_mcp.mount(api_server, prefix="api", as_proxy=True) + +# Mounting without a prefix (components accessible without prefixing) +main_mcp.mount(api_server) +``` + +FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior with the `as_proxy` parameter. + +#### Interaction with Proxy Servers + +When using `FastMCP.as_proxy()` to create a proxy server, mounting that server will always use proxy mounting: + +```python +# Create a proxy for a remote server +remote_proxy = FastMCP.as_proxy(Client("http://example.com/mcp")) + +# Mount the proxy (always uses proxy mounting) +main_server.mount(remote_proxy, prefix="remote") +``` + + + +## Tag Filtering with Composition + + + +When using `include_tags` or `exclude_tags` on a parent server, these filters apply **recursively** to all components, including those from mounted or imported servers. This allows you to control which components are exposed at the parent level, regardless of how your application is composed. + +```python +import asyncio +from fastmcp import FastMCP, Client + +# Create a subserver with tools tagged for different environments +api_server = FastMCP(name="APIServer") + +@api_server.tool(tags={"production"}) +def prod_endpoint() -> str: + """Production-ready endpoint.""" + return "Production data" + +@api_server.tool(tags={"development"}) +def dev_endpoint() -> str: + """Development-only endpoint.""" + return "Debug data" + +# Mount the subserver with production tag filtering at parent level +prod_app = FastMCP(name="ProductionApp", include_tags={"production"}) +prod_app.mount(api_server, prefix="api") + +# Test the filtering +async def test_filtering(): + async with Client(prod_app) as client: + tools = await client.list_tools() + print("Available tools:", [t.name for t in tools]) + # Shows: ['api_prod_endpoint'] + # The 'api_dev_endpoint' is filtered out + + # Calling the filtered tool raises an error + try: + await client.call_tool("api_dev_endpoint") + except Exception as e: + print(f"Filtered tool not accessible: {e}") + +if __name__ == "__main__": + asyncio.run(test_filtering()) +``` + +### How Recursive Filtering Works + +Tag filters apply in the following order: + +1. **Child Server Filters**: Each mounted/imported server first applies its own `include_tags`/`exclude_tags` to its components. +2. **Parent Server Filters**: The parent server then applies its own `include_tags`/`exclude_tags` to all components, including those from child servers. + +This ensures that parent server tag policies act as a global policy for everything the parent server exposes, no matter how your application is composed. + + +This filtering applies to both **listing** (e.g., `list_tools()`) and **execution** (e.g., `call_tool()`). Filtered components are neither visible nor executable through the parent server. + diff --git a/docs/v2/servers/context.mdx b/docs/v2/servers/context.mdx new file mode 100644 index 000000000..6d2d4e692 --- /dev/null +++ b/docs/v2/servers/context.mdx @@ -0,0 +1,613 @@ +--- +title: MCP Context +sidebarTitle: Context +description: Access MCP capabilities like logging, progress, and resources within your MCP objects. +icon: rectangle-code +--- +import { VersionBadge } from '/snippets/version-badge.mdx' + +When defining FastMCP [tools](/servers/tools), [resources](/servers/resources), resource templates, or [prompts](/servers/prompts), your functions might need to interact with the underlying MCP session or access advanced server capabilities. FastMCP provides the `Context` object for this purpose. + + +FastMCP uses [Docket](https://github.com/chrisguidry/docket)'s dependency injection system for managing runtime dependencies. This page covers Context and the built-in dependencies; see [Custom Dependencies](#custom-dependencies) for creating your own. + + +## What Is Context? + +The `Context` object provides a clean interface to access MCP features within your functions, including: + +- **Logging**: Send debug, info, warning, and error messages back to the client +- **Progress Reporting**: Update the client on the progress of long-running operations +- **Resource Access**: List and read data from resources registered with the server +- **Prompt Access**: List and retrieve prompts registered with the server +- **LLM Sampling**: Request the client's LLM to generate text based on provided messages +- **User Elicitation**: Request structured input from users during tool execution +- **State Management**: Store and share data between middleware and the handler within a single request +- **Request Information**: Access metadata about the current request +- **Server Access**: When needed, access the underlying FastMCP server instance + +## Accessing the Context + + + +The preferred way to access context is using the `CurrentContext()` dependency: + +```python {1, 6} +from fastmcp import FastMCP +from fastmcp.dependencies import CurrentContext +from fastmcp.server.context import Context + +mcp = FastMCP(name="Context Demo") + +@mcp.tool +async def process_file(file_uri: str, ctx: Context = CurrentContext()) -> str: + """Processes a file, using context for logging and resource access.""" + await ctx.info(f"Processing {file_uri}") + return "Processed file" +``` + +This works with tools, resources, and prompts: + +```python +from fastmcp import FastMCP +from fastmcp.dependencies import CurrentContext +from fastmcp.server.context import Context + +mcp = FastMCP(name="Context Demo") + +@mcp.resource("resource://user-data") +async def get_user_data(ctx: Context = CurrentContext()) -> dict: + await ctx.debug("Fetching user data") + return {"user_id": "example"} + +@mcp.prompt +async def data_analysis_request(dataset: str, ctx: Context = CurrentContext()) -> str: + return f"Please analyze the following dataset: {dataset}" +``` + +**Key Points:** + +- Dependency parameters are automatically excluded from the MCP schema—clients never see them. +- Context methods are async, so your function usually needs to be async as well. +- **Each MCP request receives a new context object.** Context is scoped to a single request; state or data set in one request will not be available in subsequent requests. +- Context is only available during a request; attempting to use context methods outside a request will raise errors. + +### Legacy Type-Hint Injection + +For backwards compatibility, you can still access context by simply adding a parameter with the `Context` type hint. FastMCP will automatically inject the context instance: + +```python {1, 6} +from fastmcp import FastMCP, Context + +mcp = FastMCP(name="Context Demo") + +@mcp.tool +async def process_file(file_uri: str, ctx: Context) -> str: + """Processes a file, using context for logging and resource access.""" + # Context is injected automatically based on the type hint + return "Processed file" +``` + +This approach still works for tools, resources, and prompts. The parameter name doesn't matter—only the `Context` type hint is important. The type hint can also be a union (`Context | None`) or use `Annotated[]`. + +### Via `get_context()` Function + + + +For code nested deeper within your function calls where passing context through parameters is inconvenient, use `get_context()` to retrieve the active context from anywhere within a request's execution flow: + +```python {2,9} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_context + +mcp = FastMCP(name="Dependency Demo") + +# Utility function that needs context but doesn't receive it as a parameter +async def process_data(data: list[float]) -> dict: + # Get the active context - only works when called within a request + ctx = get_context() + await ctx.info(f"Processing {len(data)} data points") + +@mcp.tool +async def analyze_dataset(dataset_name: str) -> dict: + # Call utility function that uses context internally + data = load_data(dataset_name) + await process_data(data) +``` + +**Important Notes:** + +- The `get_context()` function should only be used within the context of a server request. Calling it outside of a request will raise a `RuntimeError`. +- The `get_context()` function is server-only and should not be used in client code. + +## Context Capabilities + +FastMCP provides several advanced capabilities through the context object. Each capability has dedicated documentation with comprehensive examples and best practices: + +### Logging + +Send debug, info, warning, and error messages back to the MCP client for visibility into function execution. + +```python +await ctx.debug("Starting analysis") +await ctx.info(f"Processing {len(data)} items") +await ctx.warning("Deprecated parameter used") +await ctx.error("Processing failed") +``` + +See [Server Logging](/servers/logging) for complete documentation and examples. +### Client Elicitation + + + +Request structured input from clients during tool execution, enabling interactive workflows and progressive disclosure. This is a new feature in the 6/18/2025 MCP spec. + +```python +result = await ctx.elicit("Enter your name:", response_type=str) +if result.action == "accept": + name = result.data +``` + +See [User Elicitation](/servers/elicitation) for detailed examples and supported response types. + +### LLM Sampling + + + +Request the client's LLM to generate text based on provided messages, useful for leveraging AI capabilities within your tools. + +```python +response = await ctx.sample("Analyze this data", temperature=0.7) +``` + +See [LLM Sampling](/servers/sampling) for comprehensive usage and advanced techniques. + + +### Progress Reporting + +Update clients on the progress of long-running operations, enabling progress indicators and better user experience. + +```python +await ctx.report_progress(progress=50, total=100) # 50% complete +``` + +See [Progress Reporting](/servers/progress) for detailed patterns and examples. + +### Resource Access + +List and read data from resources registered with your FastMCP server, allowing access to files, configuration, or dynamic content. + +```python +# List available resources +resources = await ctx.list_resources() + +# Read a specific resource +content_list = await ctx.read_resource("resource://config") +content = content_list[0].content +``` + +**Method signatures:** +- **`ctx.list_resources() -> list[MCPResource]`**: Returns list of all available resources +- **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**: Returns a list of resource content parts + +### Prompt Access + + + +List and retrieve prompts registered with your FastMCP server, allowing tools and middleware to discover and use available prompts programmatically. + +```python +# List available prompts +prompts = await ctx.list_prompts() + +# Get a specific prompt with arguments +result = await ctx.get_prompt("analyze_data", {"dataset": "users"}) +messages = result.messages +``` + +**Method signatures:** +- **`ctx.list_prompts() -> list[MCPPrompt]`**: Returns list of all available prompts +- **`ctx.get_prompt(name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult`**: Get a specific prompt with optional arguments + +### State Management + + + +Store and share data between middleware and handlers within a single MCP request. Each MCP request (such as calling a tool, reading a resource, listing tools, or listing resources) receives its own context object with isolated state. Context state is particularly useful for passing information from [middleware](/servers/middleware) to your handlers. + +To store a value in the context state, use `ctx.set_state(key, value)`. To retrieve a value, use `ctx.get_state(key)`. + + +Context state is scoped to a single MCP request. Each operation (tool call, resource read, list operation, etc.) receives a new context object. State set during one request will not be available in subsequent requests. For persistent data storage across requests, use external storage mechanisms like databases, files, or in-memory caches. + + +This simplified example shows how to use MCP middleware to store user info in the context state, and how to access that state in a tool: + +```python {7-8, 16-17} +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class UserAuthMiddleware(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + + # Middleware stores user info in context state + context.fastmcp_context.set_state("user_id", "user_123") + context.fastmcp_context.set_state("permissions", ["read", "write"]) + + return await call_next(context) + +@mcp.tool +async def secure_operation(data: str, ctx: Context) -> str: + """Tool can access state set by middleware.""" + + user_id = ctx.get_state("user_id") # "user_123" + permissions = ctx.get_state("permissions") # ["read", "write"] + + if "write" not in permissions: + return "Access denied" + + return f"Processing {data} for user {user_id}" +``` + +**Method signatures:** +- **`ctx.set_state(key: str, value: Any) -> None`**: Store a value in the context state +- **`ctx.get_state(key: str) -> Any`**: Retrieve a value from the context state (returns None if not found) + +**State Inheritance:** +When a new context is created (nested contexts), it inherits a copy of its parent's state. This ensures that: +- State set on a child context never affects the parent context +- State set on a parent context after the child context is initialized is not propagated to the child context + +This makes state management predictable and prevents unexpected side effects between nested operations. + +### Change Notifications + + + +FastMCP automatically sends list change notifications when components (such as tools, resources, or prompts) are added, removed, enabled, or disabled. In rare cases where you need to manually trigger these notifications, you can use the context methods: + +```python +@mcp.tool +async def custom_tool_management(ctx: Context) -> str: + """Example of manual notification after custom tool changes.""" + # After making custom changes to tools + await ctx.send_tool_list_changed() + await ctx.send_resource_list_changed() + await ctx.send_prompt_list_changed() + return "Notifications sent" +``` + +These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly. + +### FastMCP Server + +To access the underlying FastMCP server instance, you can use the `ctx.fastmcp` property: + +```python +@mcp.tool +async def my_tool(ctx: Context) -> None: + # Access the FastMCP server instance + server_name = ctx.fastmcp.name + ... +``` + +### MCP Request + +Access metadata about the current request and client. + +```python +@mcp.tool +async def request_info(ctx: Context) -> dict: + """Return information about the current request.""" + return { + "request_id": ctx.request_id, + "client_id": ctx.client_id or "Unknown client" + } +``` + +**Available Properties:** + +- **`ctx.request_id -> str`**: Get the unique ID for the current MCP request +- **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization +- **`ctx.session_id -> str | None`**: Get the MCP session ID for session-based data sharing (HTTP transports only) + +#### Request Context Availability + + + +The `ctx.request_context` property provides access to the underlying MCP request context, but returns `None` when the MCP session has not been established yet. This typically occurs: + +- During middleware execution in the `on_request` hook before the MCP handshake completes +- During the initialization phase of client connections + +The MCP request context is distinct from the HTTP request. For HTTP transports, HTTP request data may be available even when the MCP session is not yet established. + +To safely access the request context in situations where it may not be available: + +```python +from fastmcp import FastMCP, Context +from fastmcp.server.dependencies import get_http_request + +mcp = FastMCP(name="Session Aware Demo") + +@mcp.tool +async def session_info(ctx: Context) -> dict: + """Return session information when available.""" + + # Check if MCP session is available + if ctx.request_context: + # MCP session available - can access MCP-specific attributes + return { + "session_id": ctx.session_id, + "request_id": ctx.request_id, + "has_meta": ctx.request_context.meta is not None + } + else: + # MCP session not available - use HTTP helpers for request data (if using HTTP transport) + request = get_http_request() + return { + "message": "MCP session not available", + "user_agent": request.headers.get("user-agent", "Unknown") + } +``` + +For HTTP request access that works regardless of MCP session availability (when using HTTP transports), use the [HTTP request helpers](#http-requests) like `get_http_request()` and `get_http_headers()`. + +#### Client Metadata + + + +Clients can send contextual information with their requests using the `meta` parameter. This metadata is accessible through `ctx.request_context.meta` and is available for all MCP operations (tools, resources, prompts). + +The `meta` field is `None` when clients don't provide metadata. When provided, metadata is accessible via attribute access (e.g., `meta.user_id`) rather than dictionary access. The structure of metadata is determined by the client making the request. + +```python +@mcp.tool +def send_email(to: str, subject: str, body: str, ctx: Context) -> str: + """Send an email, logging metadata about the request.""" + + # Access client-provided metadata + meta = ctx.request_context.meta + + if meta: + # Meta is accessed as an object with attribute access + user_id = meta.user_id if hasattr(meta, 'user_id') else None + trace_id = meta.trace_id if hasattr(meta, 'trace_id') else None + + # Use metadata for logging, observability, etc. + if trace_id: + log_with_trace(f"Sending email for user {user_id}", trace_id) + + # Send the email... + return f"Email sent to {to}" +``` + + +The MCP request is part of the low-level MCP SDK and intended for advanced use cases. Most users will not need to use it directly. + + +## Runtime Dependencies + +### HTTP Requests + + + +The recommended way to access the current HTTP request is through the `get_http_request()` dependency function: + +```python {2, 3, 11} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_http_request +from starlette.requests import Request + +mcp = FastMCP(name="HTTP Request Demo") + +@mcp.tool +async def user_agent_info() -> dict: + """Return information about the user agent.""" + # Get the HTTP request + request: Request = get_http_request() + + # Access request data + user_agent = request.headers.get("user-agent", "Unknown") + client_ip = request.client.host if request.client else "Unknown" + + return { + "user_agent": user_agent, + "client_ip": client_ip, + "path": request.url.path, + } +``` + +This approach works anywhere within a request's execution flow, not just within your MCP function. It's useful when: + +1. You need access to HTTP information in helper functions +2. You're calling nested functions that need HTTP request data +3. You're working with middleware or other request processing code + +### HTTP Headers + + +If you only need request headers and want to avoid potential errors, you can use the `get_http_headers()` helper: + +```python {2, 10} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_http_headers + +mcp = FastMCP(name="Headers Demo") + +@mcp.tool +async def safe_header_info() -> dict: + """Safely get header information without raising errors.""" + # Get headers (returns empty dict if no request context) + headers = get_http_headers() + + # Get authorization header + auth_header = headers.get("authorization", "") + is_bearer = auth_header.startswith("Bearer ") + + return { + "user_agent": headers.get("user-agent", "Unknown"), + "content_type": headers.get("content-type", "Unknown"), + "has_auth": bool(auth_header), + "auth_type": "Bearer" if is_bearer else "Other" if auth_header else "None", + "headers_count": len(headers) + } +``` + +By default, `get_http_headers()` excludes problematic headers like `host` and `content-length`. To include all headers, use `get_http_headers(include_all=True)`. + +### Access Tokens + + + +When using authentication with your FastMCP server, you can access the authenticated user's access token information using the `get_access_token()` dependency function: + +```python {2, 10} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_access_token, AccessToken + +mcp = FastMCP(name="Auth Token Demo") + +@mcp.tool +async def get_user_info() -> dict: + """Get information about the authenticated user.""" + # Get the access token (None if not authenticated) + token: AccessToken | None = get_access_token() + + if token is None: + return {"authenticated": False} + + return { + "authenticated": True, + "client_id": token.client_id, + "scopes": token.scopes, + "expires_at": token.expires_at, + "token_claims": token.claims, # JWT claims or custom token data + } +``` + +This is particularly useful when you need to: + +1. **Access user identification** - Get the `client_id` or subject from token claims +2. **Check permissions** - Verify scopes or custom claims before performing operations +3. **Multi-tenant applications** - Extract tenant information from token claims +4. **Audit logging** - Track which user performed which actions + +#### Working with Token Claims + +The `claims` field contains all the data from the original token (JWT claims for JWT tokens, or custom data for other token types): + +```python {2, 3, 9, 12, 15} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_access_token + +mcp = FastMCP(name="Multi-tenant Demo") + +@mcp.tool +async def get_tenant_data(resource_id: str) -> dict: + """Get tenant-specific data using token claims.""" + token: AccessToken | None = get_access_token() + + # Extract tenant ID from token claims + tenant_id = token.claims.get("tenant_id") if token else None + + # Extract user ID from standard JWT subject claim + user_id = token.claims.get("sub") if token else None + + # Use tenant and user info to authorize and filter data + if not tenant_id: + raise ValueError("No tenant information in token") + + return { + "resource_id": resource_id, + "tenant_id": tenant_id, + "user_id": user_id, + "data": f"Tenant-specific data for {tenant_id}", + } +``` + +## Custom Dependencies + + + +FastMCP's dependency injection is powered by [Docket](https://github.com/chrisguidry/docket), which provides a flexible system for injecting values into your functions. Beyond the built-in dependencies like `CurrentContext()`, you can create your own. + +### Using `Depends()` + +The simplest way to create a custom dependency is with `Depends()`. Pass any callable (sync or async function, or async context manager) and its return value will be injected: + +```python +from contextlib import asynccontextmanager +from fastmcp import FastMCP +from fastmcp.dependencies import Depends + +mcp = FastMCP(name="Custom Deps Demo") + +# Simple function dependency +def get_config() -> dict: + return {"api_url": "https://api.example.com", "timeout": 30} + +# Async function dependency +async def get_user_id() -> int: + return 42 + +@mcp.tool +async def fetch_data( + query: str, + config: dict = Depends(get_config), + user_id: int = Depends(get_user_id), +) -> str: + return f"User {user_id} fetching '{query}' from {config['api_url']}" +``` + +Dependencies using `Depends()` are automatically excluded from the MCP schema—clients never see them as parameters. + +### Resource Management with Context Managers + +For dependencies that need cleanup (database connections, file handles, etc.), use an async context manager: + +```python +from contextlib import asynccontextmanager +from fastmcp import FastMCP +from fastmcp.dependencies import Depends + +mcp = FastMCP(name="Resource Demo") + +@asynccontextmanager +async def get_database(): + db = await connect_to_database() + try: + yield db + finally: + await db.close() + +@mcp.tool +async def query_users(sql: str, db = Depends(get_database)) -> list: + return await db.execute(sql) +``` + +The context manager's cleanup code runs after your function completes, even if an error occurs. + +### Nested Dependencies + +Dependencies can depend on other dependencies: + +```python +from fastmcp import FastMCP +from fastmcp.dependencies import Depends + +mcp = FastMCP(name="Nested Demo") + +def get_base_url() -> str: + return "https://api.example.com" + +def get_api_client(base_url: str = Depends(get_base_url)) -> dict: + return {"base_url": base_url, "version": "v1"} + +@mcp.tool +async def call_api(endpoint: str, client: dict = Depends(get_api_client)) -> str: + return f"Calling {client['base_url']}/{client['version']}/{endpoint}" +``` + +### Advanced: Subclassing `Dependency` + +For more complex dependency patterns—like dependencies that need access to Docket's execution context or require custom lifecycle management—you can subclass Docket's `Dependency` class. See the [Docket documentation on dependencies](https://chrisguidry.github.io/docket/dependencies/) for details. diff --git a/docs/v2/servers/elicitation.mdx b/docs/v2/servers/elicitation.mdx new file mode 100644 index 000000000..18e615d0a --- /dev/null +++ b/docs/v2/servers/elicitation.mdx @@ -0,0 +1,462 @@ +--- +title: User Elicitation +sidebarTitle: Elicitation +description: Request structured input from users during tool execution through the MCP context. +icon: message-question +tag: NEW +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +User elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, tools can interactively ask for missing parameters, clarification, or additional context as needed. + + +Most of the examples in this document assume you have a FastMCP server instance named `mcp` and show how to use the `ctx.elicit` method to request user input from an `@mcp.tool`-decorated function. + + +## What is Elicitation? + +Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for: + +- **Missing parameters**: Ask for required information not provided initially +- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios +- **Progressive disclosure**: Collect complex information step-by-step +- **Dynamic workflows**: Adapt tool behavior based on user responses + +For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?" + +### Basic Usage + +Use the `ctx.elicit()` method within any tool function to request user input: + +```python {14-17} +from fastmcp import FastMCP, Context +from dataclasses import dataclass + +mcp = FastMCP("Elicitation Server") + +@dataclass +class UserInfo: + name: str + age: int + +@mcp.tool +async def collect_user_info(ctx: Context) -> str: + """Collect user information through interactive prompts.""" + result = await ctx.elicit( + message="Please provide your information", + response_type=UserInfo + ) + + if result.action == "accept": + user = result.data + return f"Hello {user.name}, you are {user.age} years old" + elif result.action == "decline": + return "Information not provided" + else: # cancel + return "Operation cancelled" +``` + +## Method Signature + + + + + + The prompt message to display to the user + + + + The Python type defining the expected response structure (dataclass, primitive type, etc.) Note that elicitation responses are subject to a restricted subset of JSON Schema types. See [Supported Response Types](#supported-response-types) for more details. + + + + + + Result object containing the user's response + + + + How the user responded to the request + + + + The user's input data (only present when action is "accept") + + + + + + + +## Elicitation Actions + +The elicitation result contains an `action` field indicating how the user responded: + +- **`accept`**: User provided valid input - data is available in the `data` field +- **`decline`**: User chose not to provide the requested information and the data field is `None` +- **`cancel`**: User cancelled the entire operation and the data field is `None` + +```python {5, 7} +@mcp.tool +async def my_tool(ctx: Context) -> str: + result = await ctx.elicit("Choose an action") + + if result.action == "accept": + return "Accepted!" + elif result.action == "decline": + return "Declined!" + else: + return "Cancelled!" +``` + +FastMCP also provides typed result classes for pattern matching on the `action` field: + +```python {1-5, 12, 14, 16} +from fastmcp.server.elicitation import ( + AcceptedElicitation, + DeclinedElicitation, + CancelledElicitation, +) + +@mcp.tool +async def pattern_example(ctx: Context) -> str: + result = await ctx.elicit("Enter your name:", response_type=str) + + match result: + case AcceptedElicitation(data=name): + return f"Hello {name}!" + case DeclinedElicitation(): + return "No name provided" + case CancelledElicitation(): + return "Operation cancelled" +``` + +## Response Types + +The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. If the request is `accept`-ed, the client must send a response that matches the schema. + +The MCP spec only supports a limited subset of JSON Schema types for elicitation responses. Specifically, it only supports JSON **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean` and `enum` fields. + +FastMCP makes it easy to request a broader range of types, including scalars (e.g. `str`) or no response at all, by automatically wrapping them in MCP-compatible object schemas. + + +### Scalar Types + +You can request simple scalar data types for basic input, such as a string, integer, or boolean. + +When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a corresponding schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned to your tool function as the `data` field of the `ElicitationResult` object. + +As a developer, this means you do not have to worry about creating or accessing a structured object when you only need a scalar value. + + +```python {4} title="Request a string" +@mcp.tool +async def get_user_name(ctx: Context) -> str: + """Get the user's name.""" + result = await ctx.elicit("What's your name?", response_type=str) + + if result.action == "accept": + return f"Hello, {result.data}!" + return "No name provided" +``` +```python {4} title="Request an integer" +@mcp.tool +async def pick_a_number(ctx: Context) -> str: + """Pick a number.""" + result = await ctx.elicit("Pick a number!", response_type=int) + + if result.action == "accept": + return f"You picked {result.data}" + return "No number provided" +``` +```python {4} title="Request a boolean" +@mcp.tool +async def pick_a_boolean(ctx: Context) -> str: + """Pick a boolean.""" + result = await ctx.elicit("True or false?", response_type=bool) + + if result.action == "accept": + return f"You picked {result.data}" + return "No boolean provided" +``` + + +### No Response + +Sometimes, the goal of an elicitation is to simply get a user to approve or reject an action. In this case, you can pass `None` as the response type to indicate that no response is expected. In order to comply with the MCP spec, the client will see a schema requesting an empty object in response. In this case, the `data` field of the `ElicitationResult` object will be `None` when the user accepts the elicitation. + +```python {4} title="No response" +@mcp.tool +async def approve_action(ctx: Context) -> str: + """Approve an action.""" + result = await ctx.elicit("Approve this action?", response_type=None) + + if result.action == "accept": + return do_action() + else: + raise ValueError("Action rejected") +``` + +### Constrained Options + +Often you'll want to constrain the user's response to a specific set of values. You can do this by using a `Literal` type or a Python enum as the response type, or by passing a list of strings to the `response_type` parameter as a convenient shortcut. + + +```python {6} title="Using a list of strings" +@mcp.tool +async def set_priority(ctx: Context) -> str: + """Set task priority level.""" + result = await ctx.elicit( + "What priority level?", + response_type=["low", "medium", "high"], + ) + + if result.action == "accept": + return f"Priority set to: {result.data}" +``` +```python {1, 8} title="Using a Literal type" +from typing import Literal + +@mcp.tool +async def set_priority(ctx: Context) -> str: + """Set task priority level.""" + result = await ctx.elicit( + "What priority level?", + response_type=Literal["low", "medium", "high"] + ) + + if result.action == "accept": + return f"Priority set to: {result.data}" + return "No priority set" +``` +```python {1, 11} title="Using a Python enum" +from enum import Enum + +class Priority(Enum): + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + +@mcp.tool +async def set_priority(ctx: Context) -> str: + """Set task priority level.""" + result = await ctx.elicit("What priority level?", response_type=Priority) + + if result.action == "accept": + return f"Priority set to: {result.data.value}" + return "No priority set" +``` + + +#### Multi-Select + + + +Enable multi-select by wrapping your choices in an additional list level. This allows users to select multiple values from the available options. + + +```python {6-8} title="List of a list of strings" +@mcp.tool +async def select_tags(ctx: Context) -> str: + """Select multiple tags.""" + result = await ctx.elicit( + "Choose tags", + response_type=[["bug", "feature", "documentation"]] # Note: list of a list + ) + + if result.action == "accept": + tags = result.data # List of selected strings + return f"Selected tags: {', '.join(tags)}" +``` + +```python {1, 3-6, 11-14} title="list[Enum] type annotation" +from enum import Enum + +class Tag(Enum): + BUG = "bug" + FEATURE = "feature" + DOCS = "documentation" + +@mcp.tool +async def select_tags(ctx: Context) -> str: + result = await ctx.elicit( + "Choose tags", + response_type=list[Tag] # Type annotation for multi-select + ) + if result.action == "accept": + tags = [tag.value for tag in result.data] + return f"Selected: {', '.join(tags)}" +``` + + +For titled multi-select, wrap a dict in a list (see [Titled Options](#titled-options) for dict syntax): + +```python {6-12} +@mcp.tool +async def select_priorities(ctx: Context) -> str: + """Select multiple priorities.""" + result = await ctx.elicit( + "Choose priorities", + response_type=[{ # Note: list containing a dict + "low": {"title": "Low Priority"}, + "medium": {"title": "Medium Priority"}, + "high": {"title": "High Priority"} + }] + ) + + if result.action == "accept": + priorities = result.data # List of selected strings + return f"Selected: {', '.join(priorities)}" +``` + +#### Titled Options + + + +For better UI display, you can provide human-readable titles for enum options. FastMCP generates SEP-1330 compliant schemas using the `oneOf` pattern with `const` and `title` fields. + +Use a dict to specify titles for enum values: + +```python {6-10} +@mcp.tool +async def set_priority(ctx: Context) -> str: + """Set task priority level.""" + result = await ctx.elicit( + "What priority level?", + response_type={ + "low": {"title": "Low Priority"}, + "medium": {"title": "Medium Priority"}, + "high": {"title": "High Priority"} + } + ) + + if result.action == "accept": + return f"Priority set to: {result.data}" +``` + +For multi-select with titles, wrap the dict in a list: + +```python {6-12} +@mcp.tool +async def select_priorities(ctx: Context) -> str: + """Select multiple priorities.""" + result = await ctx.elicit( + "Choose priorities", + response_type=[{ # List containing a dict for multi-select + "low": {"title": "Low Priority"}, + "medium": {"title": "Medium Priority"}, + "high": {"title": "High Priority"} + }] + ) + + if result.action == "accept": + priorities = result.data # List of selected strings + return f"Selected: {', '.join(priorities)}" +``` + +### Structured Responses + +You can request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties. + +```python {1, 16, 20} +from dataclasses import dataclass +from typing import Literal + +@dataclass +class TaskDetails: + title: str + description: str + priority: Literal["low", "medium", "high"] + due_date: str + +@mcp.tool +async def create_task(ctx: Context) -> str: + """Create a new task with user-provided details.""" + result = await ctx.elicit( + "Please provide task details", + response_type=TaskDetails + ) + + if result.action == "accept": + task = result.data + return f"Created task: {task.title} (Priority: {task.priority})" + return "Task creation cancelled" +``` + +### Default Values + + + +You can provide default values for elicitation fields using Pydantic's `Field(default=...)`. Clients will pre-populate form fields with these defaults, making it easier for users to provide input. + +Default values are supported for all primitive types: +- Strings: `Field(default="[email protected]")` +- Integers: `Field(default=50)` +- Numbers: `Field(default=3.14)` +- Booleans: `Field(default=False)` +- Enums: `Field(default=EnumValue.A)` + +Fields with default values are automatically marked as optional (not included in the `required` list), so users can accept the default or provide their own value. + +```python +from pydantic import BaseModel, Field +from enum import Enum + +class Priority(Enum): + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + +class TaskDetails(BaseModel): + title: str = Field(description="Task title") + description: str = Field(default="", description="Task description") + priority: Priority = Field(default=Priority.MEDIUM, description="Task priority") + +@mcp.tool +async def create_task(ctx: Context) -> str: + result = await ctx.elicit("Please provide task details", response_type=TaskDetails) + if result.action == "accept": + return f"Created: {result.data.title}" + return "Task creation cancelled" +``` + +## Multi-Turn Elicitation + +Tools can make multiple elicitation calls to gather information progressively: + +```python {6, 11, 16-19} +@mcp.tool +async def plan_meeting(ctx: Context) -> str: + """Plan a meeting by gathering details step by step.""" + + # Get meeting title + title_result = await ctx.elicit("What's the meeting title?", response_type=str) + if title_result.action != "accept": + return "Meeting planning cancelled" + + # Get duration + duration_result = await ctx.elicit("Duration in minutes?", response_type=int) + if duration_result.action != "accept": + return "Meeting planning cancelled" + + # Get priority + priority_result = await ctx.elicit( + "Is this urgent?", + response_type=Literal["yes", "no"] + ) + if priority_result.action != "accept": + return "Meeting planning cancelled" + + urgent = priority_result.data == "yes" + return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})" +``` + + +## Client Requirements + +Elicitation requires the client to implement an elicitation handler. See [Client Elicitation](/clients/elicitation) for details on how clients can handle these requests. + +If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported. \ No newline at end of file diff --git a/docs/v2/servers/icons.mdx b/docs/v2/servers/icons.mdx new file mode 100644 index 000000000..a7d038f6a --- /dev/null +++ b/docs/v2/servers/icons.mdx @@ -0,0 +1,139 @@ +--- +title: Icons +description: Add visual icons to your servers, tools, resources, and prompts +icon: image +tag: NEW +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +Icons provide visual representations for your MCP servers and components, helping client applications present better user interfaces. When displayed in MCP clients, icons help users quickly identify and navigate your server's capabilities. + +## Icon Format + +Icons use the standard MCP Icon type from the MCP protocol specification. Each icon specifies: + +- **src**: URL or data URI pointing to the icon image +- **mimeType** (optional): MIME type of the image (e.g., "image/png", "image/svg+xml") +- **sizes** (optional): Array of size descriptors (e.g., ["48x48"], ["any"]) + +```python +from mcp.types import Icon + +icon = Icon( + src="https://example.com/icon.png", + mimeType="image/png", + sizes=["48x48"] +) +``` + +## Server Icons + +Add icons and a website URL to your server for display in client applications: + +```python +from fastmcp import FastMCP +from mcp.types import Icon + +mcp = FastMCP( + name="WeatherService", + website_url="https://weather.example.com", + icons=[ + Icon( + src="https://weather.example.com/icon-48.png", + mimeType="image/png", + sizes=["48x48"] + ), + Icon( + src="https://weather.example.com/icon-96.png", + mimeType="image/png", + sizes=["96x96"] + ), + ] +) +``` + +Server icons appear in MCP client interfaces to help users identify your server among others they may have installed. + +## Component Icons + +Icons can be added to individual tools, resources, resource templates, and prompts: + +### Tool Icons + +```python +from mcp.types import Icon + +@mcp.tool( + icons=[Icon(src="https://example.com/calculator-icon.png")] +) +def calculate_sum(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b +``` + +### Resource Icons + +```python +@mcp.resource( + "config://settings", + icons=[Icon(src="https://example.com/config-icon.png")] +) +def get_settings() -> dict: + """Retrieve application settings.""" + return {"theme": "dark", "language": "en"} +``` + +### Resource Template Icons + +```python +@mcp.resource( + "user://{user_id}/profile", + icons=[Icon(src="https://example.com/user-icon.png")] +) +def get_user_profile(user_id: str) -> dict: + """Get a user's profile.""" + return {"id": user_id, "name": f"User {user_id}"} +``` + +### Prompt Icons + +```python +@mcp.prompt( + icons=[Icon(src="https://example.com/prompt-icon.png")] +) +def analyze_code(code: str): + """Create a prompt for code analysis.""" + return f"Please analyze this code:\n\n{code}" +``` + +## Using Data URIs + +For small icons or when you want to embed the icon directly, use data URIs: + +```python +from mcp.types import Icon +from fastmcp.utilities.types import Image + +# SVG icon as data URI +svg_icon = Icon( + src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6Ii8+PC9zdmc+", + mimeType="image/svg+xml" +) + +@mcp.tool(icons=[svg_icon]) +def my_tool() -> str: + """A tool with an embedded SVG icon.""" + return "result" + +# Generating a data URI from a local image file. +img = Image(path="./assets/brand/favicon.png") +icon = Icon(src=img.to_data_uri()) + +@mcp.tool(icons=[icon]) +def file_icon_tool() -> str: + """A tool with an icon generated from a local file.""" + return "result" +``` diff --git a/docs/v2/servers/logging.mdx b/docs/v2/servers/logging.mdx new file mode 100644 index 000000000..23283b083 --- /dev/null +++ b/docs/v2/servers/logging.mdx @@ -0,0 +1,249 @@ +--- +title: Client Logging +sidebarTitle: Logging +description: Send log messages back to MCP clients through the context. +icon: receipt +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + + +This documentation covers **MCP client logging** - sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module. + + +Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. This provides visibility into function execution and helps with debugging during development and operation. + +## Why Use Server Logging? + +Server logging is essential for: + +- **Debugging**: Send detailed execution information to help diagnose issues +- **Progress visibility**: Keep users informed about what the tool is doing +- **Error reporting**: Communicate problems and their context to clients +- **Audit trails**: Create records of tool execution for compliance or analysis + +Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs. + +### Basic Usage + +Use the context logging methods within any tool function: + +```python {8-9, 13, 17, 21} +from fastmcp import FastMCP, Context + +mcp = FastMCP("LoggingDemo") + +@mcp.tool +async def analyze_data(data: list[float], ctx: Context) -> dict: + """Analyze numerical data with comprehensive logging.""" + await ctx.debug("Starting analysis of numerical data") + await ctx.info(f"Analyzing {len(data)} data points") + + try: + if not data: + await ctx.warning("Empty data list provided") + return {"error": "Empty data list"} + + result = sum(data) / len(data) + await ctx.info(f"Analysis complete, average: {result}") + return {"average": result, "count": len(data)} + + except Exception as e: + await ctx.error(f"Analysis failed: {str(e)}") + raise +``` + +## Structured Logging with `extra` + +All logging methods (`debug`, `info`, `warning`, `error`, `log`) now accept an `extra` parameter, which is a dictionary of arbitrary data. This allows you to send structured data to the client, which is useful for creating rich, queryable logs. + +```python +@mcp.tool +async def process_transaction(transaction_id: str, amount: float, ctx: Context): + await ctx.info( + f"Processing transaction {transaction_id}", + extra={ + "transaction_id": transaction_id, + "amount": amount, + "currency": "USD" + } + ) + # ... processing logic ... +``` + +## Server Logs + +Client Logging in the form of `ctx.log()` and its convenience methods (`debug`, `info`, `warning`, `error`) are meant for sending messages to the MCP clients. Messages sent to clients are also logged to the server's log at `DEBUG` level. Enable debug logging on the server or enable debug logging on the `fastmcp.server.context.to_client` logger to see these messages in the server's log. + +```python +import logging + +from fastmcp.utilities.logging import get_logger + +to_client_logger = get_logger(name="fastmcp.server.context.to_client") +to_client_logger.setLevel(level=logging.DEBUG) +``` + +## Logging Methods + + + + Send debug-level messages for detailed execution information + + + + The debug message to send to the client + + + Optional dictionary for structured logging data + + + + + + Send informational messages about normal execution + + + + The information message to send to the client + + + Optional dictionary for structured logging data + + + + + + Send warning messages for potential issues that didn't prevent execution + + + + The warning message to send to the client + + + Optional dictionary for structured logging data + + + + + + Send error messages for problems that occurred during execution + + + + The error message to send to the client + + + Optional dictionary for structured logging data + + + + + + Generic logging method with custom level and logger name + + + + The log level for the message + + + + The message to send to the client + + + + Optional custom logger name for categorizing messages + + + Optional dictionary for structured logging data + + + + + +## Log Levels + +### Debug +Use for detailed information that's typically only useful when diagnosing problems: + +```python +@mcp.tool +async def process_file(file_path: str, ctx: Context) -> str: + """Process a file with detailed debug logging.""" + await ctx.debug(f"Starting to process file: {file_path}") + await ctx.debug("Checking file permissions") + + # File processing logic + await ctx.debug("File processing completed successfully") + return "File processed" +``` + +### Info +Use for general information about normal program execution: + +```python +@mcp.tool +async def backup_database(ctx: Context) -> str: + """Backup database with progress information.""" + await ctx.info("Starting database backup") + await ctx.info("Connecting to database") + await ctx.info("Backup completed successfully") + return "Database backed up" +``` + +### Warning +Use for potentially harmful situations that don't prevent execution: + +```python +@mcp.tool +async def validate_config(config: dict, ctx: Context) -> dict: + """Validate configuration with warnings for deprecated options.""" + if "old_api_key" in config: + await ctx.warning( + "Using deprecated 'old_api_key' field. Please use 'api_key' instead", + extra={"deprecated_field": "old_api_key"} + ) + + if config.get("timeout", 30) > 300: + await ctx.warning( + "Timeout value is very high (>5 minutes), this may cause issues", + extra={"timeout_value": config.get("timeout")} + ) + + return {"status": "valid", "warnings": "see logs"} +``` + +### Error +Use for error events that might still allow the application to continue: + +```python +@mcp.tool +async def batch_process(items: list[str], ctx: Context) -> dict: + """Process multiple items, logging errors for failed items.""" + successful = 0 + failed = 0 + + for item in items: + try: + # Process item + successful += 1 + except Exception as e: + await ctx.error( + f"Failed to process item '{item}': {str(e)}", + extra={"failed_item": item} + ) + failed += 1 + + return {"successful": successful, "failed": failed} +``` + + +## Client Handling + +Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation: + +- **Development clients**: May display logs in real-time for debugging +- **Production clients**: May store logs for later analysis or display to users +- **Integration clients**: May forward logs to external logging systems + +See [Client Logging](/clients/logging) for details on how clients can handle server log messages. diff --git a/docs/v2/servers/middleware.mdx b/docs/v2/servers/middleware.mdx new file mode 100644 index 000000000..8ffd683c0 --- /dev/null +++ b/docs/v2/servers/middleware.mdx @@ -0,0 +1,816 @@ +--- +title: MCP Middleware +sidebarTitle: Middleware +description: Add cross-cutting functionality to your MCP server with middleware that can inspect, modify, and respond to all MCP requests and responses. +icon: layer-group +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +MCP middleware is a powerful concept that allows you to add cross-cutting functionality to your FastMCP server. Unlike traditional web middleware, MCP middleware is designed specifically for the Model Context Protocol, providing hooks for different types of MCP operations like tool calls, resource reads, and prompt requests. + + +MCP middleware is a FastMCP-specific concept and is not part of the official MCP protocol specification. This middleware system is designed to work with FastMCP servers and may not be compatible with other MCP implementations. + + + +MCP middleware is a brand new concept and may be subject to breaking changes in future versions. + + +## What is MCP Middleware? + +MCP middleware lets you intercept and modify MCP requests and responses as they flow through your server. Think of it as a pipeline where each piece of middleware can inspect what's happening, make changes, and then pass control to the next middleware in the chain. + +Common use cases for MCP middleware include: +- **Authentication and Authorization**: Verify client permissions before executing operations +- **Logging and Monitoring**: Track usage patterns and performance metrics +- **Rate Limiting**: Control request frequency per client or operation type +- **Request/Response Transformation**: Modify data before it reaches tools or after it leaves +- **Caching**: Store frequently requested data to improve performance +- **Error Handling**: Provide consistent error responses across your server + +## How Middleware Works + +FastMCP middleware operates on a pipeline model. When a request comes in, it flows through your middleware in the order they were added to the server. Each middleware can: + +1. **Inspect the incoming request** and its context +2. **Modify the request** before passing it to the next middleware or handler +3. **Execute the next middleware/handler** in the chain by calling `call_next()` +4. **Inspect and modify the response** before returning it +5. **Handle errors** that occur during processing + +The key insight is that middleware forms a chain where each piece decides whether to continue processing or stop the chain entirely. + +If you're familiar with ASGI middleware, the basic structure of FastMCP middleware will feel familiar. At its core, middleware is a callable class that receives a context object containing information about the current JSON-RPC message and a handler function to continue the middleware chain. + +It's important to understand that MCP operates on the [JSON-RPC specification](https://spec.modelcontextprotocol.io/specification/basic/transports/). While FastMCP presents requests and responses in a familiar way, these are fundamentally JSON-RPC messages, not HTTP request/response pairs like you might be used to in web applications. FastMCP middleware works with all [transport types](/clients/transports), including local stdio transport and HTTP transports, though not all middleware implementations are compatible across all transports (e.g., middleware that inspects HTTP headers won't work with stdio transport). + +The most fundamental way to implement middleware is by overriding the `__call__` method on the `Middleware` base class: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class RawMiddleware(Middleware): + async def __call__(self, context: MiddlewareContext, call_next): + # This method receives ALL messages regardless of type + print(f"Raw middleware processing: {context.method}") + result = await call_next(context) + print(f"Raw middleware completed: {context.method}") + return result +``` + +This gives you complete control over every message that flows through your server, but requires you to handle all message types manually. + +## Middleware Hooks + +To make it easier for users to target specific types of messages, FastMCP middleware provides a variety of specialized hooks. Instead of implementing the raw `__call__` method, you can override specific hook methods that are called only for certain types of operations, allowing you to target exactly the level of specificity you need for your middleware logic. + +### Hook Hierarchy and Execution Order + +FastMCP provides multiple hooks that are called with varying levels of specificity. Understanding this hierarchy is crucial for effective middleware design. + +When a request comes in, **multiple hooks may be called for the same request**, going from general to specific: + +1. **`on_message`** - Called for ALL MCP messages (both requests and notifications) +2. **`on_request` or `on_notification`** - Called based on the message type +3. **Operation-specific hooks** - Called for specific MCP operations like `on_call_tool` + +For example, when a client calls a tool, your middleware will receive **multiple hook calls**: +1. `on_message` and `on_request` for any initial tool discovery operations (list_tools) +2. `on_message` (because it's any MCP message) for the tool call itself +3. `on_request` (because tool calls expect responses) for the tool call itself +4. `on_call_tool` (because it's specifically a tool execution) for the tool call itself + +Note that the MCP SDK may perform additional operations like listing tools for caching purposes, which will trigger additional middleware calls beyond just the direct tool execution. + +This hierarchy allows you to target your middleware logic with the right level of specificity. Use `on_message` for broad concerns like logging, `on_request` for authentication, and `on_call_tool` for tool-specific logic like performance monitoring. + +### Available Hooks + + +- `on_message`: Called for all MCP messages (requests and notifications) +- `on_request`: Called specifically for MCP requests (that expect responses) +- `on_notification`: Called specifically for MCP notifications (fire-and-forget) + +- `on_call_tool`: Called when tools are being executed +- `on_read_resource`: Called when resources are being read +- `on_get_prompt`: Called when prompts are being retrieved +- `on_list_tools`: Called when listing available tools +- `on_list_resources`: Called when listing available resources +- `on_list_resource_templates`: Called when listing resource templates +- `on_list_prompts`: Called when listing available prompts + +- `on_initialize`: Called when a client connects and initializes the session (returns `None`) + +The `on_initialize` hook receives the client's initialization request but **returns `None`** rather than a result. The initialization response is handled internally by the MCP protocol and cannot be modified by middleware. This hook is useful for client detection, logging connections, or initializing session state, but not for modifying the initialization handshake itself. + + +**Example:** + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from mcp import McpError +from mcp.types import ErrorData + +class InitializationMiddleware(Middleware): + async def on_initialize(self, context: MiddlewareContext, call_next): + # Check client capabilities before initialization + client_info = context.message.params.get("clientInfo", {}) + client_name = client_info.get("name", "unknown") + + # Reject unsupported clients BEFORE call_next + if client_name == "unsupported-client": + raise McpError(ErrorData(code=-32000, message="This client is not supported")) + + # Log successful initialization + await call_next(context) + print(f"Client {client_name} initialized successfully") +``` + + +If you raise `McpError` in `on_initialize` **after** calling `call_next()`, the error will only be logged and will not be sent to the client. The initialization response has already been sent at that point. Always raise `McpError` **before** `call_next()` if you want to reject the initialization. + + +### MCP Session Availability in Middleware + + + +The MCP session and request context are not available during certain phases like initialization. When middleware runs during these phases, `context.fastmcp_context.request_context` returns `None` rather than the full MCP request context. + +This typically occurs when: +- The `on_request` hook fires during client initialization +- The MCP handshake hasn't completed yet + +To handle this in middleware, check if the MCP request context is available before accessing MCP-specific attributes. Note that the MCP request context is distinct from the HTTP request - for HTTP transports, you can use HTTP helpers to access request data even when the MCP session is not available: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class SessionAwareMiddleware(Middleware): + async def on_request(self, context: MiddlewareContext, call_next): + ctx = context.fastmcp_context + + if ctx.request_context: + # MCP session available - can access session-specific attributes + session_id = ctx.session_id + request_id = ctx.request_id + else: + # MCP session not available yet - use HTTP helpers for request data (if using HTTP transport) + from fastmcp.server.dependencies import get_http_headers + headers = get_http_headers() + # Access HTTP data for auth, logging, etc. + + return await call_next(context) +``` + +For HTTP request data (headers, client IP, etc.) when using HTTP transports, use `get_http_request()` or `get_http_headers()` from `fastmcp.server.dependencies`, which work regardless of MCP session availability. See [HTTP Requests](/servers/context#http-requests) for details. + +## Component Access in Middleware + +Understanding how to access component information (tools, resources, prompts) in middleware is crucial for building powerful middleware functionality. The access patterns differ significantly between listing operations and execution operations. + +### Listing Operations vs Execution Operations + +FastMCP middleware handles two types of operations differently: + +**Listing Operations** (`on_list_tools`, `on_list_resources`, `on_list_prompts`, etc.): +- Middleware receives **FastMCP component objects** with full metadata +- These objects include FastMCP-specific properties like `tags` that can be accessed directly from the component +- The result contains complete component information before it's converted to MCP format +- Tags are included in the component's `meta` field in the listing response returned to MCP clients + +**Execution Operations** (`on_call_tool`, `on_read_resource`, `on_get_prompt`): +- Middleware runs **before** the component is executed +- The middleware result is either the execution result or an error if the component wasn't found +- Component metadata isn't directly available in the hook parameters + +### Accessing Component Metadata During Execution + +If you need to check component properties (like tags) during execution operations, use the FastMCP server instance available through the context: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.exceptions import ToolError + +class TagBasedMiddleware(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + # Access the tool object to check its metadata + if context.fastmcp_context: + try: + tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name) + + # Check if this tool has a "private" tag + if "private" in tool.tags: + raise ToolError("Access denied: private tool") + + # Check if tool is enabled + if not tool.enabled: + raise ToolError("Tool is currently disabled") + + except Exception: + # Tool not found or other error - let execution continue + # and handle the error naturally + pass + + return await call_next(context) +``` + +The same pattern works for resources and prompts: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.exceptions import ResourceError, PromptError + +class ComponentAccessMiddleware(Middleware): + async def on_read_resource(self, context: MiddlewareContext, call_next): + if context.fastmcp_context: + try: + resource = await context.fastmcp_context.fastmcp.get_resource(context.message.uri) + if "restricted" in resource.tags: + raise ResourceError("Access denied: restricted resource") + except Exception: + pass + return await call_next(context) + + async def on_get_prompt(self, context: MiddlewareContext, call_next): + if context.fastmcp_context: + try: + prompt = await context.fastmcp_context.fastmcp.get_prompt(context.message.name) + if not prompt.enabled: + raise PromptError("Prompt is currently disabled") + except Exception: + pass + return await call_next(context) +``` + +### Working with Listing Results + +For listing operations, the middleware `call_next` function returns a list of FastMCP components prior to being converted to MCP format. You can filter or modify this list and return it to the client. For example: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class ListingFilterMiddleware(Middleware): + async def on_list_tools(self, context: MiddlewareContext, call_next): + result = await call_next(context) + + # Filter out tools with "private" tag + filtered_tools = [ + tool for tool in result + if "private" not in tool.tags + ] + + # Return modified list + return filtered_tools +``` + +This filtering happens before the components are converted to MCP format and returned to the client. Tags are accessible both during filtering and are included in the component's `meta` field in the final listing response. + + +When filtering components in listing operations, ensure you also prevent execution of filtered components in the corresponding execution hooks (`on_call_tool`, `on_read_resource`, `on_get_prompt`) to maintain consistency. + + +### Tool Call Denial + +You can deny access to specific tools by raising a `ToolError` in your middleware. This is the correct way to block tool execution, as it integrates properly with the FastMCP error handling system. + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.exceptions import ToolError + +class AuthMiddleware(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + tool_name = context.message.name + + # Deny access to restricted tools + if tool_name.lower() in ["delete", "admin_config"]: + raise ToolError("Access denied: tool requires admin privileges") + + # Allow other tools to proceed + return await call_next(context) +``` + + +When denying tool calls, always raise `ToolError` rather than returning `ToolResult` objects or other values. `ToolError` ensures proper error propagation through the middleware chain and converts to the correct MCP error response format. + + +### Tool Call Modification + +For execution operations like tool calls, you can modify arguments before execution or transform results afterward: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class ToolCallMiddleware(Middleware): + async def on_call_tool(self, context: MiddlewareContext, call_next): + # Modify arguments before execution + if context.message.name == "calculate": + # Ensure positive inputs + if context.message.arguments.get("value", 0) < 0: + context.message.arguments["value"] = abs(context.message.arguments["value"]) + + result = await call_next(context) + + # Transform result after execution + if context.message.name == "get_data": + # Add metadata to result + if result.structured_content: + result.structured_content["processed_at"] = "2024-01-01T00:00:00Z" + + return result +``` + + +For more complex tool rewriting scenarios, consider using [Tool Transformation](/patterns/tool-transformation) patterns which provide a more structured approach to creating modified tool variants. + + +### Anatomy of a Hook + +Every middleware hook follows the same pattern. Let's examine the `on_message` hook to understand the structure: + +```python +async def on_message(self, context: MiddlewareContext, call_next): + # 1. Pre-processing: Inspect and optionally modify the request + print(f"Processing {context.method}") + + # 2. Chain continuation: Call the next middleware/handler + result = await call_next(context) + + # 3. Post-processing: Inspect and optionally modify the response + print(f"Completed {context.method}") + + # 4. Return the result (potentially modified) + return result +``` + +### Hook Parameters + +Every hook receives two parameters: + +1. **`context: MiddlewareContext`** - Contains information about the current request: + - `context.method` - The MCP method name (e.g., "tools/call") + - `context.source` - Where the request came from ("client" or "server") + - `context.type` - Message type ("request" or "notification") + - `context.message` - The MCP message data + - `context.timestamp` - When the request was received + - `context.fastmcp_context` - FastMCP Context object (if available) + +2. **`call_next`** - A function that continues the middleware chain. You **must** call this to proceed, unless you want to stop processing entirely. + +### Control Flow + +You have complete control over the request flow: +- **Continue processing**: Call `await call_next(context)` to proceed +- **Modify the request**: Change the context before calling `call_next` +- **Modify the response**: Change the result after calling `call_next` +- **Stop the chain**: Don't call `call_next` (rarely needed) +- **Handle errors**: Wrap `call_next` in try/catch blocks + +#### State Management + + + +In addition to modifying the request and response, you can also store state data that your tools can (optionally) access later. To do so, use the FastMCP Context to either `set_state` or `get_state` as appropriate. For more information, see the [Context State Management](/servers/context#state-management) docs. + +## Creating Middleware + +FastMCP middleware is implemented by subclassing the `Middleware` base class and overriding the hooks you need. You only need to implement the hooks that are relevant to your use case. + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class LoggingMiddleware(Middleware): + """Middleware that logs all MCP operations.""" + + async def on_message(self, context: MiddlewareContext, call_next): + """Called for all MCP messages.""" + print(f"Processing {context.method} from {context.source}") + + result = await call_next(context) + + print(f"Completed {context.method}") + return result + +# Add middleware to your server +mcp = FastMCP("MyServer") +mcp.add_middleware(LoggingMiddleware()) +``` + +This creates a basic logging middleware that will print information about every request that flows through your server. + +## Adding Middleware to Your Server + +### Single Middleware + +Adding middleware to your server is straightforward: + +```python +mcp = FastMCP("MyServer") +mcp.add_middleware(LoggingMiddleware()) +``` + +### Multiple Middleware + +Middleware executes in the order it's added to the server. The first middleware added runs first on the way in, and last on the way out: + +```python +mcp = FastMCP("MyServer") + +mcp.add_middleware(AuthenticationMiddleware("secret-token")) +mcp.add_middleware(PerformanceMiddleware()) +mcp.add_middleware(LoggingMiddleware()) +``` + +This creates the following execution flow: +1. AuthenticationMiddleware (pre-processing) +2. PerformanceMiddleware (pre-processing) +3. LoggingMiddleware (pre-processing) +4. Actual tool/resource handler +5. LoggingMiddleware (post-processing) +6. PerformanceMiddleware (post-processing) +7. AuthenticationMiddleware (post-processing) + +## Server Composition and Middleware + +When using [Server Composition](/servers/composition) with `mount` or `import_server`, middleware behavior follows these rules: + +1. **Parent server middleware** runs for all requests, including those routed to mounted servers +2. **Mounted server middleware** only runs for requests handled by that specific server +3. **Middleware order** is preserved within each server + +This allows you to create layered middleware architectures where parent servers handle cross-cutting concerns like authentication, while child servers focus on domain-specific middleware. + +```python +# Parent server with middleware +parent = FastMCP("Parent") +parent.add_middleware(AuthenticationMiddleware("token")) + +# Child server with its own middleware +child = FastMCP("Child") +child.add_middleware(LoggingMiddleware()) + +@child.tool +def child_tool() -> str: + return "from child" + +# Mount the child server +parent.mount(child, prefix="child") +``` + +When a client calls "child_tool", the request will flow through the parent's authentication middleware first, then route to the child server where it will go through the child's logging middleware. + +## Built-in Middleware Examples + +FastMCP includes several middleware implementations that demonstrate best practices and provide immediately useful functionality. Let's explore how each type works by building simplified versions, then see how to use the full implementations. + +### Timing Middleware + +Performance monitoring is essential for understanding your server's behavior and identifying bottlenecks. FastMCP includes timing middleware at `fastmcp.server.middleware.timing`. + +Here's an example of how it works: + +```python +import time +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class SimpleTimingMiddleware(Middleware): + async def on_request(self, context: MiddlewareContext, call_next): + start_time = time.perf_counter() + + try: + result = await call_next(context) + duration_ms = (time.perf_counter() - start_time) * 1000 + print(f"Request {context.method} completed in {duration_ms:.2f}ms") + return result + except Exception as e: + duration_ms = (time.perf_counter() - start_time) * 1000 + print(f"Request {context.method} failed after {duration_ms:.2f}ms: {e}") + raise +``` + +To use the full version with proper logging and configuration: + +```python +from fastmcp.server.middleware.timing import ( + TimingMiddleware, + DetailedTimingMiddleware +) + +# Basic timing for all requests +mcp.add_middleware(TimingMiddleware()) + +# Detailed per-operation timing (tools, resources, prompts) +mcp.add_middleware(DetailedTimingMiddleware()) +``` + +The built-in versions include custom logger support, proper formatting, and **DetailedTimingMiddleware** provides operation-specific hooks like `on_call_tool` and `on_read_resource` for granular timing. + +### Tool Injection Middleware + +Tool injection middleware is a middleware that injects tools into the server during the request lifecycle: + +```python +from fastmcp.server.middleware.tool_injection import ToolInjectionMiddleware + +def my_tool_fn(a: int, b: int) -> int: + return a + b + +my_tool = Tool.from_function(fn=my_tool_fn, name="my_tool") + +mcp.add_middleware(ToolInjectionMiddleware(tools=[my_tool])) +``` + +### Prompt Tool Middleware + +Prompt tool middleware is a compatibility middleware for clients that are unable to list or get prompts. It provides two tools: `list_prompts` and `get_prompt` which allow clients to list and get prompts respectively using only tool calls. + +```python +from fastmcp.server.middleware.tool_injection import PromptToolMiddleware + +mcp.add_middleware(PromptToolMiddleware()) +``` + +### Resource Tool Middleware + +Resource tool middleware is a compatibility middleware for clients that are unable to list or read resources. It provides two tools: `list_resources` and `read_resource` which allow clients to list and read resources respectively using only tool calls. + +```python +from fastmcp.server.middleware.tool_injection import ResourceToolMiddleware + +mcp.add_middleware(ResourceToolMiddleware()) +``` + +### Caching Middleware + +Caching middleware is essential for improving performance and reducing server load. FastMCP provides caching middleware at `fastmcp.server.middleware.caching`. + +Here's how to use the full version: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +mcp.add_middleware(ResponseCachingMiddleware()) +``` + +Out of the box, it caches call/list tool, resources, and prompts to an in-memory cache with TTL-based expiration. Cache entries expire based on their TTL; there is no event-based cache invalidation. List calls are stored under global keys—when sharing a storage backend across multiple servers, consider namespacing collections to prevent conflicts. See [Storage Backends](/servers/storage-backends) for advanced configuration options. + +Each method can be configured individually, for example, caching list tools for 30 seconds, limiting caching to specific tools, and disabling caching for resource reads: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware, CallToolSettings, ListToolsSettings, ReadResourceSettings + +mcp.add_middleware(ResponseCachingMiddleware( + list_tools_settings=ListToolsSettings( + ttl=30, + ), + call_tool_settings=CallToolSettings( + included_tools=["tool1"], + ), + read_resource_settings=ReadResourceSettings( + enabled=False + ) +)) +``` + +#### Storage Backends + +By default, caching uses in-memory storage, which is fast but doesn't persist across restarts. For production or persistent caching across server restarts, configure a different storage backend. See [Storage Backends](/servers/storage-backends) for complete options including disk, Redis, DynamoDB, and custom implementations. + +Disk-based caching example: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.disk import DiskStore + +mcp.add_middleware(ResponseCachingMiddleware( + cache_storage=DiskStore(directory="cache"), +)) +``` + +Redis for distributed deployments: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.redis import RedisStore + +mcp.add_middleware(ResponseCachingMiddleware( + cache_storage=RedisStore(host="redis.example.com", port=6379), +)) +``` + +#### Cache Statistics + +The caching middleware collects operation statistics (hits, misses, etc.) through the underlying storage layer. Access statistics from the middleware instance: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +middleware = ResponseCachingMiddleware() +mcp.add_middleware(middleware) + +# Later, retrieve statistics +stats = middleware.statistics() +print(f"Total cache operations: {stats}") +``` + +### Logging Middleware + +Request and response logging is crucial for debugging, monitoring, and understanding usage patterns in your MCP server. FastMCP provides comprehensive logging middleware at `fastmcp.server.middleware.logging`. + +Here's an example of how it works: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class SimpleLoggingMiddleware(Middleware): + async def on_message(self, context: MiddlewareContext, call_next): + print(f"Processing {context.method} from {context.source}") + + try: + result = await call_next(context) + print(f"Completed {context.method}") + return result + except Exception as e: + print(f"Failed {context.method}: {e}") + raise +``` + +To use the full versions with advanced features: + +```python +from fastmcp.server.middleware.logging import ( + LoggingMiddleware, + StructuredLoggingMiddleware +) + +# Human-readable logging with payload support +mcp.add_middleware(LoggingMiddleware( + include_payloads=True, + max_payload_length=1000 +)) + +# JSON-structured logging for log aggregation tools +mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True)) +``` + +The built-in versions include payload logging, structured JSON output, custom logger support, payload size limits, and operation-specific hooks for granular control. + +### Rate Limiting Middleware + +Rate limiting is essential for protecting your server from abuse, ensuring fair resource usage, and maintaining performance under load. FastMCP includes sophisticated rate limiting middleware at `fastmcp.server.middleware.rate_limiting`. + +Here's an example of how it works: + +```python +import time +from collections import defaultdict +from fastmcp.server.middleware import Middleware, MiddlewareContext +from mcp import McpError +from mcp.types import ErrorData + +class SimpleRateLimitMiddleware(Middleware): + def __init__(self, requests_per_minute: int = 60): + self.requests_per_minute = requests_per_minute + self.client_requests = defaultdict(list) + + async def on_request(self, context: MiddlewareContext, call_next): + current_time = time.time() + client_id = "default" # In practice, extract from headers or context + + # Clean old requests and check limit + cutoff_time = current_time - 60 + self.client_requests[client_id] = [ + req_time for req_time in self.client_requests[client_id] + if req_time > cutoff_time + ] + + if len(self.client_requests[client_id]) >= self.requests_per_minute: + raise McpError(ErrorData(code=-32000, message="Rate limit exceeded")) + + self.client_requests[client_id].append(current_time) + return await call_next(context) +``` + +To use the full versions with advanced algorithms: + +```python +from fastmcp.server.middleware.rate_limiting import ( + RateLimitingMiddleware, + SlidingWindowRateLimitingMiddleware +) + +# Token bucket rate limiting (allows controlled bursts) +mcp.add_middleware(RateLimitingMiddleware( + max_requests_per_second=10.0, + burst_capacity=20 +)) + +# Sliding window rate limiting (precise time-based control) +mcp.add_middleware(SlidingWindowRateLimitingMiddleware( + max_requests=100, + window_minutes=1 +)) +``` + +The built-in versions include token bucket algorithms, per-client identification, global rate limiting, and async-safe implementations with configurable client identification functions. + +### Error Handling Middleware + +Consistent error handling and recovery is critical for robust MCP servers. FastMCP provides comprehensive error handling middleware at `fastmcp.server.middleware.error_handling`. + +Here's an example of how it works: + +```python +import logging +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class SimpleErrorHandlingMiddleware(Middleware): + def __init__(self): + self.logger = logging.getLogger("errors") + self.error_counts = {} + + async def on_message(self, context: MiddlewareContext, call_next): + try: + return await call_next(context) + except Exception as error: + # Log the error and track statistics + error_key = f"{type(error).__name__}:{context.method}" + self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1 + + self.logger.error(f"Error in {context.method}: {type(error).__name__}: {error}") + raise +``` + +To use the full versions with advanced features: + +```python +from fastmcp.server.middleware.error_handling import ( + ErrorHandlingMiddleware, + RetryMiddleware +) + +# Comprehensive error logging and transformation +mcp.add_middleware(ErrorHandlingMiddleware( + include_traceback=True, + transform_errors=True, + error_callback=my_error_callback +)) + +# Automatic retry with exponential backoff +mcp.add_middleware(RetryMiddleware( + max_retries=3, + retry_exceptions=(ConnectionError, TimeoutError) +)) +``` + +The built-in versions include error transformation, custom callbacks, configurable retry logic, and proper MCP error formatting. + +### Combining Middleware + +These middleware work together seamlessly: + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.timing import TimingMiddleware +from fastmcp.server.middleware.logging import LoggingMiddleware +from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware +from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware + +mcp = FastMCP("Production Server") + +# Add middleware in logical order +mcp.add_middleware(ErrorHandlingMiddleware()) # Handle errors first +mcp.add_middleware(RateLimitingMiddleware(max_requests_per_second=50)) +mcp.add_middleware(TimingMiddleware()) # Time actual execution +mcp.add_middleware(LoggingMiddleware()) # Log everything + +@mcp.tool +def my_tool(data: str) -> str: + return f"Processed: {data}" +``` + +This configuration provides comprehensive monitoring, protection, and observability for your MCP server. + +### Custom Middleware Example + +You can also create custom middleware by extending the base class: + +```python +from fastmcp.server.middleware import Middleware, MiddlewareContext + +class CustomHeaderMiddleware(Middleware): + async def on_request(self, context: MiddlewareContext, call_next): + # Add custom logic here + print(f"Processing {context.method}") + + result = await call_next(context) + + print(f"Completed {context.method}") + return result + +mcp.add_middleware(CustomHeaderMiddleware()) +``` diff --git a/docs/v2/servers/progress.mdx b/docs/v2/servers/progress.mdx new file mode 100644 index 000000000..b5625864b --- /dev/null +++ b/docs/v2/servers/progress.mdx @@ -0,0 +1,189 @@ +--- +title: Progress Reporting +sidebarTitle: Progress +description: Update clients on the progress of long-running operations through the MCP context. +icon: chart-line +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + +Progress reporting allows MCP tools to notify clients about the progress of long-running operations. This enables clients to display progress indicators and provide better user experience during time-consuming tasks. + +## Why Use Progress Reporting? + +Progress reporting is valuable for: + +- **User experience**: Keep users informed about long-running operations +- **Progress indicators**: Enable clients to show progress bars or percentages +- **Timeout prevention**: Demonstrate that operations are actively progressing +- **Debugging**: Track execution progress for performance analysis + +### Basic Usage + +Use `ctx.report_progress()` to send progress updates to the client: + +```python {14, 21} +from fastmcp import FastMCP, Context +import asyncio + +mcp = FastMCP("ProgressDemo") + +@mcp.tool +async def process_items(items: list[str], ctx: Context) -> dict: + """Process a list of items with progress updates.""" + total = len(items) + results = [] + + for i, item in enumerate(items): + # Report progress as we process each item + await ctx.report_progress(progress=i, total=total) + + # Simulate processing time + await asyncio.sleep(0.1) + results.append(item.upper()) + + # Report 100% completion + await ctx.report_progress(progress=total, total=total) + + return {"processed": len(results), "results": results} +``` + +## Method Signature + + + + Report progress to the client for long-running operations + + + + Current progress value (e.g., 24, 0.75, 1500) + + + + Optional total value (e.g., 100, 1.0, 2000). When provided, clients may interpret this as enabling percentage calculation. + + + + + +## Progress Patterns + +### Percentage-Based Progress + +Report progress as a percentage (0-100): + +```python {13-14} +@mcp.tool +async def download_file(url: str, ctx: Context) -> str: + """Download a file with percentage progress.""" + total_size = 1000 # KB + downloaded = 0 + + while downloaded < total_size: + # Download chunk + chunk_size = min(50, total_size - downloaded) + downloaded += chunk_size + + # Report percentage progress + percentage = (downloaded / total_size) * 100 + await ctx.report_progress(progress=percentage, total=100) + + await asyncio.sleep(0.1) # Simulate download time + + return f"Downloaded file from {url}" +``` + +### Absolute Progress + +Report progress with absolute values: + +```python {10} +@mcp.tool +async def backup_database(ctx: Context) -> str: + """Backup database tables with absolute progress.""" + tables = ["users", "orders", "products", "inventory", "logs"] + + for i, table in enumerate(tables): + await ctx.info(f"Backing up table: {table}") + + # Report absolute progress + await ctx.report_progress(progress=i + 1, total=len(tables)) + + # Simulate backup time + await asyncio.sleep(0.5) + + return "Database backup completed" +``` + +### Indeterminate Progress + +Report progress without a known total for operations where the endpoint is unknown: + +```python {11} +@mcp.tool +async def scan_directory(directory: str, ctx: Context) -> dict: + """Scan directory with indeterminate progress.""" + files_found = 0 + + # Simulate directory scanning + for i in range(10): # Unknown number of files + files_found += 1 + + # Report progress without total for indeterminate operations + await ctx.report_progress(progress=files_found) + + await asyncio.sleep(0.2) + + return {"files_found": files_found, "directory": directory} +``` + +### Multi-Stage Operations + +Break complex operations into stages with progress for each: + +```python +@mcp.tool +async def data_migration(source: str, destination: str, ctx: Context) -> str: + """Migrate data with multi-stage progress reporting.""" + + # Stage 1: Validation (0-25%) + await ctx.info("Validating source data") + for i in range(5): + await ctx.report_progress(progress=i * 5, total=100) + await asyncio.sleep(0.1) + + # Stage 2: Export (25-60%) + await ctx.info("Exporting data from source") + for i in range(7): + progress = 25 + (i * 5) + await ctx.report_progress(progress=progress, total=100) + await asyncio.sleep(0.1) + + # Stage 3: Transform (60-80%) + await ctx.info("Transforming data format") + for i in range(4): + progress = 60 + (i * 5) + await ctx.report_progress(progress=progress, total=100) + await asyncio.sleep(0.1) + + # Stage 4: Import (80-100%) + await ctx.info("Importing to destination") + for i in range(4): + progress = 80 + (i * 5) + await ctx.report_progress(progress=progress, total=100) + await asyncio.sleep(0.1) + + # Final completion + await ctx.report_progress(progress=100, total=100) + + return f"Migration from {source} to {destination} completed" +``` + + +## Client Requirements + +Progress reporting requires clients to support progress handling: + +- Clients must send a `progressToken` in the initial request to receive progress updates +- If no progress token is provided, progress calls will have no effect (they won't error) +- See [Client Progress](/clients/progress) for details on implementing client-side progress handling diff --git a/docs/v2/servers/prompts.mdx b/docs/v2/servers/prompts.mdx new file mode 100644 index 000000000..710b35ce3 --- /dev/null +++ b/docs/v2/servers/prompts.mdx @@ -0,0 +1,353 @@ +--- +title: Prompts +sidebarTitle: Prompts +description: Create reusable, parameterized prompt templates for MCP clients. +icon: message-lines +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + +Prompts are reusable message templates that help LLMs generate structured, purposeful responses. FastMCP simplifies defining these templates, primarily using the `@mcp.prompt` decorator. + +## What Are Prompts? + +Prompts provide parameterized message templates for LLMs. When a client requests a prompt: + +1. FastMCP finds the corresponding prompt definition. +2. If it has parameters, they are validated against your function signature. +3. Your function executes with the validated inputs. +4. The generated message(s) are returned to the LLM to guide its response. + +This allows you to define consistent, reusable templates that LLMs can use across different clients and contexts. + +## Prompts + +### The `@prompt` Decorator + +The most common way to define a prompt is by decorating a Python function. The decorator uses the function name as the prompt's identifier. + +```python +from fastmcp import FastMCP +from fastmcp.prompts.prompt import Message, PromptMessage, TextContent + +mcp = FastMCP(name="PromptServer") + +# Basic prompt returning a string (converted to user message automatically) +@mcp.prompt +def ask_about_topic(topic: str) -> str: + """Generates a user message asking for an explanation of a topic.""" + return f"Can you please explain the concept of '{topic}'?" + +# Prompt returning a specific message type +@mcp.prompt +def generate_code_request(language: str, task_description: str) -> PromptMessage: + """Generates a user message requesting code generation.""" + content = f"Write a {language} function that performs the following task: {task_description}" + return PromptMessage(role="user", content=TextContent(type="text", text=content)) +``` + +**Key Concepts:** + +* **Name:** By default, the prompt name is taken from the function name. +* **Parameters:** The function parameters define the inputs needed to generate the prompt. +* **Inferred Metadata:** By default: + * Prompt Name: Taken from the function name (`ask_about_topic`). + * Prompt Description: Taken from the function's docstring. + +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. + + +#### Decorator Arguments + +While FastMCP infers the name and description from your function, you can override these and add additional metadata using arguments to the `@mcp.prompt` decorator: + +```python +@mcp.prompt( + name="analyze_data_request", # Custom prompt name + description="Creates a request to analyze data with specific parameters", # Custom description + tags={"analysis", "data"}, # Optional categorization tags + meta={"version": "1.1", "author": "data-team"} # Custom metadata +) +def data_analysis_prompt( + data_uri: str = Field(description="The URI of the resource containing the data."), + analysis_type: str = Field(default="summary", description="Type of analysis.") +) -> str: + """This docstring is ignored when description is provided.""" + return f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}." +``` + + + + Sets the explicit prompt name exposed via MCP. If not provided, uses the function name + + + + A human-readable title for the prompt + + + + Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose + + + + A set of strings used to categorize the prompt. These can be used by the server and, in some cases, by clients to filter or group available prompts. + + + + A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information + + + + + + Optional list of icon representations for this prompt. See [Icons](/servers/icons) for detailed examples + + + + + + Optional meta information about the prompt. This data is passed through to the MCP client as the `_meta` field of the client-side prompt object and can be used for custom metadata, versioning, or other application-specific purposes. + + + +### Argument Types + + + +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. + + + +```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 + } + ] +} +``` + + + +**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 +}) +``` + + +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 + + +### Return Values + +FastMCP intelligently handles different return types from your prompt function: + +- **`str`**: Automatically converted to a single `PromptMessage`. +- **`PromptMessage`**: Used directly as provided. (Note a more user-friendly `Message` constructor is available that can accept raw strings instead of `TextContent` objects.) +- **`list[PromptMessage | str]`**: Used as a sequence of messages (a conversation). +- **`Any`**: If the return type is not one of the above, the return value is attempted to be converted to a string and used as a `PromptMessage`. + +```python +from fastmcp.prompts.prompt import Message, PromptResult + +@mcp.prompt +def roleplay_scenario(character: str, situation: str) -> PromptResult: + """Sets up a roleplaying scenario with initial messages.""" + return [ + Message(f"Let's roleplay. You are {character}. The situation is: {situation}"), + Message("Okay, I understand. I am ready. What happens next?", role="assistant") + ] +``` + + +### Required vs. Optional Parameters + +Parameters in your function signature are considered **required** unless they have a default value. + +```python +@mcp.prompt +def data_analysis_prompt( + data_uri: str, # Required - no default value + analysis_type: str = "summary", # Optional - has default value + include_charts: bool = False # Optional - has default value +) -> str: + """Creates a request to analyze data with specific parameters.""" + prompt = f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}." + if include_charts: + prompt += " Include relevant charts and visualizations." + return prompt +``` + +In this example, the client *must* provide `data_uri`. If `analysis_type` or `include_charts` are omitted, their default values will be used. + +### Disabling Prompts + + + +You can control the visibility and availability of prompts by enabling or disabling them. Disabled prompts will not appear in the list of available prompts, and attempting to call a disabled prompt will result in an "Unknown prompt" error. + +By default, all prompts are enabled. You can disable a prompt upon creation using the `enabled` parameter in the decorator: + +```python +@mcp.prompt(enabled=False) +def experimental_prompt(): + """This prompt is not ready for use.""" + return "This is an experimental prompt." +``` + +You can also toggle a prompt's state programmatically after it has been created: + +```python +@mcp.prompt +def seasonal_prompt(): return "Happy Holidays!" + +# Disable and re-enable the prompt +seasonal_prompt.disable() +seasonal_prompt.enable() +``` + +### Async Prompts + +FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts. + +```python +# Synchronous prompt +@mcp.prompt +def simple_question(question: str) -> str: + """Generates a simple question to ask the LLM.""" + return f"Question: {question}" + +# Asynchronous prompt +@mcp.prompt +async def data_based_prompt(data_id: str) -> str: + """Generates a prompt based on data that needs to be fetched.""" + # In a real scenario, you might fetch data from a database or API + async with aiohttp.ClientSession() as session: + async with session.get(f"https://api.example.com/data/{data_id}") as response: + data = await response.json() + return f"Analyze this data: {data['content']}" +``` + +Use `async def` when your prompt function performs I/O operations like network requests, database queries, file I/O, or external service calls. + +### Accessing MCP Context + + + +Prompts can access additional MCP information and features through the `Context` object. To access it, add a parameter to your prompt function with a type annotation of `Context`: + +```python {6} +from fastmcp import FastMCP, Context + +mcp = FastMCP(name="PromptServer") + +@mcp.prompt +async def generate_report_request(report_type: str, ctx: Context) -> str: + """Generates a request for a report.""" + return f"Please create a {report_type} report. Request ID: {ctx.request_id}" +``` + +For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context). + +### Notifications + + + +FastMCP automatically sends `notifications/prompts/list_changed` notifications to connected clients when prompts are added, enabled, or disabled. This allows clients to stay up-to-date with the current prompt set without manually polling for changes. + +```python +@mcp.prompt +def example_prompt() -> str: + return "Hello!" + +# These operations trigger notifications: +mcp.add_prompt(example_prompt) # Sends prompts/list_changed notification +example_prompt.disable() # Sends prompts/list_changed notification +example_prompt.enable() # Sends prompts/list_changed notification +``` + +Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications. + +Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their prompt lists or update their interfaces. + +## Server Behavior + +### Duplicate Prompts + + + +You can configure how the FastMCP server handles attempts to register multiple prompts with the same name. Use the `on_duplicate_prompts` setting during `FastMCP` initialization. + +```python +from fastmcp import FastMCP + +mcp = FastMCP( + name="PromptServer", + on_duplicate_prompts="error" # Raise an error if a prompt name is duplicated +) + +@mcp.prompt +def greeting(): return "Hello, how can I help you today?" + +# This registration attempt will raise a ValueError because +# "greeting" is already registered and the behavior is "error". +# @mcp.prompt +# def greeting(): return "Hi there! What can I do for you?" +``` + +The duplicate behavior options are: + +- `"warn"` (default): Logs a warning, and the new prompt replaces the old one. +- `"error"`: Raises a `ValueError`, preventing the duplicate registration. +- `"replace"`: Silently replaces the existing prompt with the new one. +- `"ignore"`: Keeps the original prompt and ignores the new registration attempt. diff --git a/docs/v2/servers/proxy.mdx b/docs/v2/servers/proxy.mdx new file mode 100644 index 000000000..401005dea --- /dev/null +++ b/docs/v2/servers/proxy.mdx @@ -0,0 +1,349 @@ +--- +title: Proxy Servers +sidebarTitle: Proxy Servers +description: Use FastMCP to act as an intermediary or change transport for other MCP servers. +icon: arrows-retweet +--- +import { VersionBadge } from '/snippets/version-badge.mdx' + + + +FastMCP provides a powerful proxying capability that allows one FastMCP server instance to act as a frontend for another MCP server (which could be remote, running on a different transport, or even another FastMCP instance). This is achieved using the `FastMCP.as_proxy()` class method. + +## What is Proxying? + +Proxying means setting up a FastMCP server that doesn't implement its own tools or resources directly. Instead, when it receives a request (like `tools/call` or `resources/read`), it forwards that request to a *backend* MCP server, receives the response, and then relays that response back to the original client. + +```mermaid +sequenceDiagram + participant ClientApp as Your Client (e.g., Claude Desktop) + participant FastMCPProxy as FastMCP Proxy Server + participant BackendServer as Backend MCP Server (e.g., remote SSE) + + ClientApp->>FastMCPProxy: MCP Request (e.g. stdio) + Note over FastMCPProxy, BackendServer: Proxy forwards the request + FastMCPProxy->>BackendServer: MCP Request (e.g. sse) + BackendServer-->>FastMCPProxy: MCP Response (e.g. sse) + Note over ClientApp, FastMCPProxy: Proxy relays the response + FastMCPProxy-->>ClientApp: MCP Response (e.g. stdio) +``` + +### Key Benefits + + + +- **Session Isolation**: Each request gets its own isolated session, ensuring safe concurrent operations +- **Transport Bridging**: Expose servers running on one transport via a different transport +- **Advanced MCP Features**: Automatic forwarding of sampling, elicitation, logging, and progress +- **Security**: Acts as a controlled gateway to backend servers +- **Simplicity**: Single endpoint even if backend location or transport changes + +### Performance Considerations + +When using proxy servers, especially those connecting to HTTP-based backend servers, be aware that latency can be significant. Operations like `list_tools()` may take hundreds of milliseconds compared to 1-2ms for local tools. When mounting proxy servers, this latency affects all operations on the parent server, not just interactions with the proxied tools. + +If low latency is a requirement for your use-case, consider using [`import_server()`](/servers/composition#importing-static-composition) to copy tools at startup rather than proxying them at runtime. + +## Quick Start + + + +The recommended way to create a proxy is using `ProxyClient`, which provides full MCP feature support with automatic session isolation: + +```python +from fastmcp import FastMCP +from fastmcp.server.proxy import ProxyClient + +# Create a proxy with full MCP feature support +proxy = FastMCP.as_proxy( + ProxyClient("backend_server.py"), + name="MyProxy" +) + +# Run the proxy (e.g., via stdio for Claude Desktop) +if __name__ == "__main__": + proxy.run() +``` + +This single setup gives you: +- Safe concurrent request handling +- Automatic forwarding of advanced MCP features (sampling, elicitation, etc.) +- Session isolation to prevent context mixing +- Full compatibility with all MCP clients + +You can also pass a FastMCP [client transport](/clients/transports) (or parameter that can be inferred to a transport) to `as_proxy()`. This will automatically create a `ProxyClient` instance for you. + +Finally, you can pass a regular FastMCP `Client` instance to `as_proxy()`. This will work for many use cases, but may break if advanced MCP features like sampling or elicitation are invoked by the server. + +## Session Isolation & Concurrency + + + +FastMCP proxies provide session isolation to ensure safe concurrent operations. The session strategy depends on how the proxy is configured: + +### Fresh Sessions + +When you pass a disconnected client (which is the normal case), each request gets its own isolated backend session: + +```python +from fastmcp.server.proxy import ProxyClient + +# Each request creates a fresh backend session (recommended) +proxy = FastMCP.as_proxy(ProxyClient("backend_server.py")) + +# Multiple clients can use this proxy simultaneously without interference: +# - Client A calls a tool -> gets isolated backend session +# - Client B calls a tool -> gets different isolated backend session +# - No context mixing between requests +``` + +### Session Reuse with Connected Clients + +When you pass an already-connected client, the proxy will reuse that session for all requests: + +```python +from fastmcp import Client + +# Create and connect a client +async with Client("backend_server.py") as connected_client: + # This proxy will reuse the connected session for all requests + proxy = FastMCP.as_proxy(connected_client) + + # ⚠️ Warning: All requests share the same backend session + # This may cause context mixing in concurrent scenarios +``` + +**Important**: Using shared sessions with concurrent requests from multiple clients may lead to context mixing and race conditions. This approach should only be used in single-threaded scenarios or when you have explicit synchronization. + +## Transport Bridging + +A common use case is bridging transports - exposing a server running on one transport via a different transport. For example, making a remote SSE server available locally via stdio: + +```python +from fastmcp import FastMCP +from fastmcp.server.proxy import ProxyClient + +# Bridge remote SSE server to local stdio +remote_proxy = FastMCP.as_proxy( + ProxyClient("http://example.com/mcp/sse"), + name="Remote-to-Local Bridge" +) + +# Run locally via stdio for Claude Desktop +if __name__ == "__main__": + remote_proxy.run() # Defaults to stdio transport +``` + +Or expose a local server via HTTP for remote access: + +```python +# Bridge local server to HTTP +local_proxy = FastMCP.as_proxy( + ProxyClient("local_server.py"), + name="Local-to-HTTP Bridge" +) + +# Run via HTTP for remote clients +if __name__ == "__main__": + local_proxy.run(transport="http", host="0.0.0.0", port=8080) +``` + + +## Advanced MCP Features + + + +`ProxyClient` automatically forwards advanced MCP protocol features between the backend server and clients connected to the proxy, ensuring full MCP compatibility. + +### Supported Features + +- **Roots**: Forwards filesystem root access requests to the client +- **Sampling**: Forwards LLM completion requests from backend to client +- **Elicitation**: Forwards user input requests to the client +- **Logging**: Forwards log messages from backend through to client +- **Progress**: Forwards progress notifications during long operations + +```python +from fastmcp.server.proxy import ProxyClient + +# ProxyClient automatically handles all these features +backend = ProxyClient("advanced_backend.py") +proxy = FastMCP.as_proxy(backend) + +# When the backend server: +# - Requests LLM sampling -> forwarded to your client +# - Logs messages -> appear in your client +# - Reports progress -> shown in your client +# - Needs user input -> prompts your client +``` + +### Customizing Feature Support + +You can selectively disable forwarding by passing `None` for specific handlers: + +```python +# Disable sampling but keep other features +backend = ProxyClient( + "backend_server.py", + sampling_handler=None, # Disable LLM sampling forwarding + log_handler=None # Disable log forwarding +) +``` + +When you use a transport string directly with `FastMCP.as_proxy()`, it automatically creates a `ProxyClient` internally to ensure full feature support. + +## Configuration-Based Proxies + + + +You can create a proxy directly from a configuration dictionary that follows the MCPConfig schema. This is useful for quickly setting up proxies to remote servers without manually configuring each connection detail. + +```python +from fastmcp import FastMCP + +# Create a proxy directly from a config dictionary +config = { + "mcpServers": { + "default": { # For single server configs, 'default' is commonly used + "url": "https://example.com/mcp", + "transport": "http" + } + } +} + +# Create a proxy to the configured server (auto-creates ProxyClient) +proxy = FastMCP.as_proxy(config, name="Config-Based Proxy") + +# Run the proxy with stdio transport for local access +if __name__ == "__main__": + proxy.run() +``` + + +The MCPConfig format follows an emerging standard for MCP server configuration and may evolve as the specification matures. While FastMCP aims to maintain compatibility with future versions, be aware that field names or structure might change. + + +### Multi-Server Configurations + +You can create a proxy to multiple servers by specifying multiple entries in the config. They are automatically mounted with their config names as prefixes: + +```python +# Multi-server configuration +config = { + "mcpServers": { + "weather": { + "url": "https://weather-api.example.com/mcp", + "transport": "http" + }, + "calendar": { + "url": "https://calendar-api.example.com/mcp", + "transport": "http" + } + } +} + +# Create a unified proxy to multiple servers +composite_proxy = FastMCP.as_proxy(config, name="Composite Proxy") + +# Tools, resources, prompts, and templates are accessible with prefixes: +# - Tools: weather_get_forecast, calendar_add_event +# - Prompts: weather_daily_summary, calendar_quick_add +# - Resources: weather://weather/icons/sunny, calendar://calendar/events/today +# - Templates: weather://weather/locations/{id}, calendar://calendar/events/{date} +``` + +## Component Prefixing + +When proxying one or more servers, component names are prefixed the same way as with mounting and importing: + +- Tools: `{prefix}_{tool_name}` +- Prompts: `{prefix}_{prompt_name}` +- Resources: `protocol://{prefix}/path/to/resource` (default path format) +- Resource templates: `protocol://{prefix}/...` and template names are also prefixed + +These rules apply uniformly whether you: +- Mount a proxy on another server +- Create a multi-server proxy from an `MCPConfig` +- Use `FastMCP.as_proxy()` directly + +## Mirrored Components + + + +When you access tools, resources, or prompts from a proxy server, they are "mirrored" from the remote server. Mirrored components cannot be modified directly since they reflect the state of the remote server. For example, you can not simply "disable" a mirrored component. + +However, you can create a copy of a mirrored component and store it as a new locally-defined component. Local components always take precedence over mirrored ones because the proxy server will check its own registry before it attempts to engage the remote server. + +Therefore, to enable or disable a proxy tool, resource, or prompt, you should first create a local copy and add it to your own server. Here's an example of how to do that for a tool: + +```python +# Create your own server +my_server = FastMCP("MyServer") + +# Get a proxy server +proxy = FastMCP.as_proxy("backend_server.py") + +# Get mirrored components from proxy +mirrored_tool = await proxy.get_tool("useful_tool") + +# Create a local copy that you can modify +local_tool = mirrored_tool.copy() + +# Add the local copy to your server +my_server.add_tool(local_tool) + +# Now you can disable YOUR copy +local_tool.disable() +``` + + +## `FastMCPProxy` Class + +Internally, `FastMCP.as_proxy()` uses the `FastMCPProxy` class. You generally don't need to interact with this class directly, but it's available if needed for advanced scenarios. + +### Direct Usage + +```python +from fastmcp.server.proxy import FastMCPProxy, ProxyClient + +# Provide a client factory for explicit session control +def create_client(): + return ProxyClient("backend_server.py") + +proxy = FastMCPProxy(client_factory=create_client) +``` + +### Parameters + +- **`client_factory`**: A callable that returns a `Client` instance when called. This gives you full control over session creation and reuse strategies. + +### Explicit Session Management + +`FastMCPProxy` requires explicit session management - no automatic detection is performed. You must choose your session strategy: + +```python +# Share session across all requests (be careful with concurrency) +shared_client = ProxyClient("backend_server.py") +def shared_session_factory(): + return shared_client + +proxy = FastMCPProxy(client_factory=shared_session_factory) + +# Create fresh sessions per request (recommended) +def fresh_session_factory(): + return ProxyClient("backend_server.py") + +proxy = FastMCPProxy(client_factory=fresh_session_factory) +``` + +For automatic session strategy selection, use the convenience method `FastMCP.as_proxy()` instead. + +```python +# Custom factory with specific configuration +def custom_client_factory(): + client = ProxyClient("backend_server.py") + # Add any custom configuration here + return client + +proxy = FastMCPProxy(client_factory=custom_client_factory) +``` diff --git a/docs/v2/servers/resources.mdx b/docs/v2/servers/resources.mdx new file mode 100644 index 000000000..86c043d35 --- /dev/null +++ b/docs/v2/servers/resources.mdx @@ -0,0 +1,663 @@ +--- +title: Resources & Templates +sidebarTitle: Resources +description: Expose data sources and dynamic content generators to your MCP client. +icon: folder-open +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + +Resources represent data or files that an MCP client can read, and resource templates extend this concept by allowing clients to request dynamically generated resources based on parameters passed in the URI. + +FastMCP simplifies defining both static and dynamic resources, primarily using the `@mcp.resource` decorator. + +## What Are Resources? + +Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI: + +1. FastMCP finds the corresponding resource definition. +2. If it's dynamic (defined by a function), the function is executed. +3. The content (text, JSON, binary data) is returned to the client. + +This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation. + +## Resources + +### The `@resource` Decorator + +The most common way to define a resource is by decorating a Python function. The decorator requires the resource's unique URI. + +```python +import json +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + +# Basic dynamic resource returning a string +@mcp.resource("resource://greeting") +def get_greeting() -> str: + """Provides a simple greeting message.""" + return "Hello from FastMCP Resources!" + +# Resource returning JSON data (dict is auto-serialized) +@mcp.resource("data://config") +def get_config() -> dict: + """Provides application configuration as JSON.""" + return { + "theme": "dark", + "version": "1.2.0", + "features": ["tools", "resources"], + } +``` + +**Key Concepts:** + +* **URI:** The first argument to `@resource` is the unique URI (e.g., `"resource://greeting"`) clients use to request this data. +* **Lazy Loading:** The decorated function (`get_greeting`, `get_config`) is only executed when a client specifically requests that resource URI via `resources/read`. +* **Inferred Metadata:** By default: + * Resource Name: Taken from the function name (`get_greeting`). + * Resource Description: Taken from the function's docstring. + +#### Decorator Arguments + +You can customize the resource's properties using arguments in the `@mcp.resource` decorator: + +```python +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + +# Example specifying metadata +@mcp.resource( + uri="data://app-status", # Explicit URI (required) + name="ApplicationStatus", # Custom name + description="Provides the current status of the application.", # Custom description + mime_type="application/json", # Explicit MIME type + tags={"monitoring", "status"}, # Categorization tags + meta={"version": "2.1", "team": "infrastructure"} # Custom metadata +) +def get_application_status() -> dict: + """Internal function description (ignored if description is provided above).""" + return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # Example usage +``` + + + + The unique identifier for the resource + + + + A human-readable name. If not provided, defaults to function name + + + + Explanation of the resource. If not provided, defaults to docstring + + + + Specifies the content type. FastMCP often infers a default like `text/plain` or `application/json`, but explicit is better for non-text types + + + + A set of strings used to categorize the resource. These can be used by the server and, in some cases, by clients to filter or group available resources. + + + + A boolean to enable or disable the resource. See [Disabling Resources](#disabling-resources) for more information + + + + + + Optional list of icon representations for this resource or template. See [Icons](/servers/icons) for detailed examples + + + + An optional `Annotations` object or dictionary to add additional metadata about the resource. + + + If true, the resource is read-only and does not modify its environment. + + + If true, reading the resource repeatedly will have no additional effect on its environment. + + + + + + + + Optional meta information about the resource. This data is passed through to the MCP client as the `_meta` field of the client-side resource object and can be used for custom metadata, versioning, or other application-specific purposes. + + + +### Return Values + +FastMCP automatically converts your function's return value into the appropriate MCP resource content: + +- **`str`**: Sent as `TextResourceContents` (with `mime_type="text/plain"` by default). +- **`dict`, `list`, `pydantic.BaseModel`**: Automatically serialized to a JSON string and sent as `TextResourceContents` (with `mime_type="application/json"` by default). +- **`bytes`**: Base64 encoded and sent as `BlobResourceContents`. You should specify an appropriate `mime_type` (e.g., `"image/png"`, `"application/octet-stream"`). +- **`None`**: Results in an empty resource content list being returned. + +### Disabling Resources + + + +You can control the visibility and availability of resources and templates by enabling or disabling them. Disabled resources will not appear in the list of available resources or templates, and attempting to read a disabled resource will result in an "Unknown resource" error. + +By default, all resources are enabled. You can disable a resource upon creation using the `enabled` parameter in the decorator: + +```python +@mcp.resource("data://secret", enabled=False) +def get_secret_data(): + """This resource is currently disabled.""" + return "Secret data" +``` + +You can also toggle a resource's state programmatically after it has been created: + +```python +@mcp.resource("data://config") +def get_config(): return {"version": 1} + +# Disable and re-enable the resource +get_config.disable() +get_config.enable() +``` + + +### Accessing MCP Context + + + +Resources and resource templates can access additional MCP information and features through the `Context` object. To access it, add a parameter to your resource function with a type annotation of `Context`: + +```python {6, 14} +from fastmcp import FastMCP, Context + +mcp = FastMCP(name="DataServer") + +@mcp.resource("resource://system-status") +async def get_system_status(ctx: Context) -> dict: + """Provides system status information.""" + return { + "status": "operational", + "request_id": ctx.request_id + } + +@mcp.resource("resource://{name}/details") +async def get_details(name: str, ctx: Context) -> dict: + """Get details for a specific name.""" + return { + "name": name, + "accessed_at": ctx.request_id + } +``` + +For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context). + + +### Async Resources + +Use `async def` for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server. + +```python +import aiofiles +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + +@mcp.resource("file:///app/data/important_log.txt", mime_type="text/plain") +async def read_important_log() -> str: + """Reads content from a specific log file asynchronously.""" + try: + async with aiofiles.open("/app/data/important_log.txt", mode="r") as f: + content = await f.read() + return content + except FileNotFoundError: + return "Log file not found." +``` + + +### Resource Classes + +While `@mcp.resource` is ideal for dynamic content, you can directly register pre-defined resources (like static files or simple text) using `mcp.add_resource()` and concrete `Resource` subclasses. + +```python +from pathlib import Path +from fastmcp import FastMCP +from fastmcp.resources import FileResource, TextResource, DirectoryResource + +mcp = FastMCP(name="DataServer") + +# 1. Exposing a static file directly +readme_path = Path("./README.md").resolve() +if readme_path.exists(): + # Use a file:// URI scheme + readme_resource = FileResource( + uri=f"file://{readme_path.as_posix()}", + path=readme_path, # Path to the actual file + name="README File", + description="The project's README.", + mime_type="text/markdown", + tags={"documentation"} + ) + mcp.add_resource(readme_resource) + +# 2. Exposing simple, predefined text +notice_resource = TextResource( + uri="resource://notice", + name="Important Notice", + text="System maintenance scheduled for Sunday.", + tags={"notification"} +) +mcp.add_resource(notice_resource) + +# 3. Using a custom key different from the URI +special_resource = TextResource( + uri="resource://common-notice", + name="Special Notice", + text="This is a special notice with a custom storage key.", +) +mcp.add_resource(special_resource, key="resource://custom-key") + +# 4. Exposing a directory listing +data_dir_path = Path("./app_data").resolve() +if data_dir_path.is_dir(): + data_listing_resource = DirectoryResource( + uri="resource://data-files", + path=data_dir_path, # Path to the directory + name="Data Directory Listing", + description="Lists files available in the data directory.", + recursive=False # Set to True to list subdirectories + ) + mcp.add_resource(data_listing_resource) # Returns JSON list of files +``` + +**Common Resource Classes:** + +- `TextResource`: For simple string content. +- `BinaryResource`: For raw `bytes` content. +- `FileResource`: Reads content from a local file path. Handles text/binary modes and lazy reading. +- `HttpResource`: Fetches content from an HTTP(S) URL (requires `httpx`). +- `DirectoryResource`: Lists files in a local directory (returns JSON). +- (`FunctionResource`: Internal class used by `@mcp.resource`). + +Use these when the content is static or sourced directly from a file/URL, bypassing the need for a dedicated Python function. + +#### Custom Resource Keys + + + +When adding resources directly with `mcp.add_resource()`, you can optionally provide a custom storage key: + +```python +# Creating a resource with standard URI as the key +resource = TextResource(uri="resource://data") +mcp.add_resource(resource) # Will be stored and accessed using "resource://data" + +# Creating a resource with a custom key +special_resource = TextResource(uri="resource://special-data") +mcp.add_resource(special_resource, key="internal://data-v2") # Will be stored and accessed using "internal://data-v2" +``` + +Note that this parameter is only available when using `add_resource()` directly and not through the `@resource` decorator, as URIs are provided explicitly when using the decorator. + +### Notifications + + + +FastMCP automatically sends `notifications/resources/list_changed` notifications to connected clients when resources or templates are added, enabled, or disabled. This allows clients to stay up-to-date with the current resource set without manually polling for changes. + +```python +@mcp.resource("data://example") +def example_resource() -> str: + return "Hello!" + +# These operations trigger notifications: +mcp.add_resource(example_resource) # Sends resources/list_changed notification +example_resource.disable() # Sends resources/list_changed notification +example_resource.enable() # Sends resources/list_changed notification +``` + +Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications. + +Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their resource lists or update their interfaces. + +### Annotations + + + +FastMCP allows you to add specialized metadata to your resources through annotations. These annotations communicate how resources behave to client applications without consuming token context in LLM prompts. + +Annotations serve several purposes in client applications: +- Indicating whether resources are read-only or may have side effects +- Describing the safety profile of resources (idempotent vs. non-idempotent) +- Helping clients optimize caching and access patterns + +You can add annotations to a resource using the `annotations` parameter in the `@mcp.resource` decorator: + +```python +@mcp.resource( + "data://config", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +def get_config() -> dict: + """Get application configuration.""" + return {"version": "1.0", "debug": False} +``` + +FastMCP supports these standard annotations: + +| Annotation | Type | Default | Purpose | +| :--------- | :--- | :------ | :------ | +| `readOnlyHint` | boolean | true | Indicates if the resource only provides data without side effects | +| `idempotentHint` | boolean | true | Indicates if repeated reads have the same effect as a single read | + +Remember that annotations help make better user experiences but should be treated as advisory hints. They help client applications present appropriate UI elements and optimize access patterns, but won't enforce behavior on their own. Always focus on making your annotations accurately represent what your resource actually does. + +## Resource Templates + +Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature. + +Resource templates share most configuration options with regular resources (name, description, mime_type, tags, annotations), but add the ability to define URI parameters that map to function parameters. + +Resource templates generate a new resource for each unique set of parameters, which means that resources can be dynamically created on-demand. For example, if the resource template `"user://profile/{name}"` is registered, MCP clients could request `"user://profile/ford"` or `"user://profile/marvin"` to retrieve either of those two user profiles as resources, without having to register each resource individually. + + +Functions with `*args` are not supported as resource templates. However, unlike tools and prompts, resource templates do support `**kwargs` because the URI template defines specific parameter names that will be collected and passed as keyword arguments. + + +Here is a complete example that shows how to define two resource templates: + +```python +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + +# Template URI includes {city} placeholder +@mcp.resource("weather://{city}/current") +def get_weather(city: str) -> dict: + """Provides weather information for a specific city.""" + # In a real implementation, this would call a weather API + # Here we're using simplified logic for example purposes + return { + "city": city.capitalize(), + "temperature": 22, + "condition": "Sunny", + "unit": "celsius" + } + +# Template with multiple parameters and annotations +@mcp.resource( + "repos://{owner}/{repo}/info", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +def get_repo_info(owner: str, repo: str) -> dict: + """Retrieves information about a GitHub repository.""" + # In a real implementation, this would call the GitHub API + return { + "owner": owner, + "name": repo, + "full_name": f"{owner}/{repo}", + "stars": 120, + "forks": 48 + } +``` + +With these two templates defined, clients can request a variety of resources: +- `weather://london/current` → Returns weather for London +- `weather://paris/current` → Returns weather for Paris +- `repos://jlowin/fastmcp/info` → Returns info about the jlowin/fastmcp repository +- `repos://prefecthq/prefect/info` → Returns info about the prefecthq/prefect repository + +### RFC 6570 URI Templates + + +FastMCP implements [RFC 6570 URI Templates](https://datatracker.ietf.org/doc/html/rfc6570) for resource templates, providing a standardized way to define parameterized URIs. This includes support for simple expansion, wildcard path parameters, and form-style query parameters. + +#### Wildcard Parameters + + + +Resource templates support wildcard parameters that can match multiple path segments. While standard parameters (`{param}`) only match a single path segment and don't cross "/" boundaries, wildcard parameters (`{param*}`) can capture multiple segments including slashes. Wildcards capture all subsequent path segments *up until* the defined part of the URI template (whether literal or another parameter). This allows you to have multiple wildcard parameters in a single URI template. + +```python {15, 23} +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + + +# Standard parameter only matches one segment +@mcp.resource("files://{filename}") +def get_file(filename: str) -> str: + """Retrieves a file by name.""" + # Will only match files:// + return f"File content for: {filename}" + + +# Wildcard parameter can match multiple segments +@mcp.resource("path://{filepath*}") +def get_path_content(filepath: str) -> str: + """Retrieves content at a specific path.""" + # Can match path://docs/server/resources.mdx + return f"Content at path: {filepath}" + + +# Mixing standard and wildcard parameters +@mcp.resource("repo://{owner}/{path*}/template.py") +def get_template_file(owner: str, path: str) -> dict: + """Retrieves a file from a specific repository and path, but + only if the resource ends with `template.py`""" + # Can match repo://jlowin/fastmcp/src/resources/template.py + return { + "owner": owner, + "path": path + "/template.py", + "content": f"File at {path}/template.py in {owner}'s repository" + } +``` + +Wildcard parameters are useful when: + +- Working with file paths or hierarchical data +- Creating APIs that need to capture variable-length path segments +- Building URL-like patterns similar to REST APIs + +Note that like regular parameters, each wildcard parameter must still be a named parameter in your function signature, and all required function parameters must appear in the URI template. + +#### Query Parameters + + + +FastMCP supports RFC 6570 form-style query parameters using the `{?param1,param2}` syntax. Query parameters provide a clean way to pass optional configuration to resources without cluttering the path. + +Query parameters must be optional function parameters (have default values), while path parameters map to required function parameters. This enforces a clear separation: required data goes in the path, optional configuration in query params. + +```python +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + +# Basic query parameters +@mcp.resource("data://{id}{?format}") +def get_data(id: str, format: str = "json") -> str: + """Retrieve data in specified format.""" + if format == "xml": + return f"" + return f'{{"id": "{id}"}}' + +# Multiple query parameters with type coercion +@mcp.resource("api://{endpoint}{?version,limit,offset}") +def call_api(endpoint: str, version: int = 1, limit: int = 10, offset: int = 0) -> dict: + """Call API endpoint with pagination.""" + return { + "endpoint": endpoint, + "version": version, + "limit": limit, + "offset": offset, + "results": fetch_results(endpoint, version, limit, offset) + } + +# Query parameters with wildcards +@mcp.resource("files://{path*}{?encoding,lines}") +def read_file(path: str, encoding: str = "utf-8", lines: int = 100) -> str: + """Read file with optional encoding and line limit.""" + return read_file_content(path, encoding, lines) +``` + +**Example requests:** +- `data://123` → Uses default format `"json"` +- `data://123?format=xml` → Uses format `"xml"` +- `api://users?version=2&limit=50` → `version=2, limit=50, offset=0` +- `files://src/main.py?encoding=ascii&lines=50` → Custom encoding and line limit + +FastMCP automatically coerces query parameter string values to the correct types based on your function's type hints (`int`, `float`, `bool`, `str`). + +**Query parameters vs. hidden defaults:** + +Query parameters expose optional configuration to clients. To hide optional parameters from clients entirely (always use defaults), simply omit them from the URI template: + +```python +# Clients CAN override max_results via query string +@mcp.resource("search://{query}{?max_results}") +def search_configurable(query: str, max_results: int = 10) -> dict: + return {"query": query, "limit": max_results} + +# Clients CANNOT override max_results (not in URI template) +@mcp.resource("search://{query}") +def search_fixed(query: str, max_results: int = 10) -> dict: + return {"query": query, "limit": max_results} +``` + +### Template Parameter Rules + + + +FastMCP enforces these validation rules when creating resource templates: + +1. **Required function parameters** (no default values) must appear in the URI path template +2. **Query parameters** (specified with `{?param}` syntax) must be optional function parameters with default values +3. **All URI template parameters** (path and query) must exist as function parameters + +Optional function parameters (those with default values) can be: +- Included as query parameters (`{?param}`) - clients can override via query string +- Omitted from URI template - always uses default value, not exposed to clients +- Used in alternative path templates - enables multiple ways to access the same resource + +**Multiple templates for one function:** + +Create multiple resource templates that expose the same function through different URI patterns by manually applying decorators: + +```python +from fastmcp import FastMCP + +mcp = FastMCP(name="DataServer") + +# Define a user lookup function that can be accessed by different identifiers +def lookup_user(name: str | None = None, email: str | None = None) -> dict: + """Look up a user by either name or email.""" + if email: + return find_user_by_email(email) # pseudocode + elif name: + return find_user_by_name(name) # pseudocode + else: + return {"error": "No lookup parameters provided"} + +# Manually apply multiple decorators to the same function +mcp.resource("users://email/{email}")(lookup_user) +mcp.resource("users://name/{name}")(lookup_user) +``` + +Now an LLM or client can retrieve user information in two different ways: +- `users://email/alice@example.com` → Looks up user by email (with name=None) +- `users://name/Bob` → Looks up user by name (with email=None) + +This approach allows a single function to be registered with multiple URI patterns while keeping the implementation clean and straightforward. + +Templates provide a powerful way to expose parameterized data access points following REST-like principles. + +## Error Handling + + + +If your resource function encounters an error, you can raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.) or a FastMCP `ResourceError`. + +By default, all exceptions (including their details) are logged and converted into an MCP error response to be sent back to the client LLM. This helps the LLM understand failures and react appropriately. + +If you want to mask internal error details for security reasons, you can: + +1. Use the `mask_error_details=True` parameter when creating your `FastMCP` instance: +```python +mcp = FastMCP(name="SecureServer", mask_error_details=True) +``` + +2. Or use `ResourceError` to explicitly control what error information is sent to clients: +```python +from fastmcp import FastMCP +from fastmcp.exceptions import ResourceError + +mcp = FastMCP(name="DataServer") + +@mcp.resource("resource://safe-error") +def fail_with_details() -> str: + """This resource provides detailed error information.""" + # ResourceError contents are always sent back to clients, + # regardless of mask_error_details setting + raise ResourceError("Unable to retrieve data: file not found") + +@mcp.resource("resource://masked-error") +def fail_with_masked_details() -> str: + """This resource masks internal error details when mask_error_details=True.""" + # This message would be masked if mask_error_details=True + raise ValueError("Sensitive internal file path: /etc/secrets.conf") + +@mcp.resource("data://{id}") +def get_data_by_id(id: str) -> dict: + """Template resources also support the same error handling pattern.""" + if id == "secure": + raise ValueError("Cannot access secure data") + elif id == "missing": + raise ResourceError("Data ID 'missing' not found in database") + return {"id": id, "value": "data"} +``` + +When `mask_error_details=True`, only error messages from `ResourceError` will include details, other exceptions will be converted to a generic message. + +## Server Behavior + +### Duplicate Resources + + + +You can configure how the FastMCP server handles attempts to register multiple resources or templates with the same URI. Use the `on_duplicate_resources` setting during `FastMCP` initialization. + +```python +from fastmcp import FastMCP + +mcp = FastMCP( + name="ResourceServer", + on_duplicate_resources="error" # Raise error on duplicates +) + +@mcp.resource("data://config") +def get_config_v1(): return {"version": 1} + +# This registration attempt will raise a ValueError because +# "data://config" is already registered and the behavior is "error". +# @mcp.resource("data://config") +# def get_config_v2(): return {"version": 2} +``` + +The duplicate behavior options are: + +- `"warn"` (default): Logs a warning, and the new resource/template replaces the old one. +- `"error"`: Raises a `ValueError`, preventing the duplicate registration. +- `"replace"`: Silently replaces the existing resource/template with the new one. +- `"ignore"`: Keeps the original resource/template and ignores the new registration attempt. \ No newline at end of file diff --git a/docs/v2/servers/sampling.mdx b/docs/v2/servers/sampling.mdx new file mode 100644 index 000000000..841d5b01f --- /dev/null +++ b/docs/v2/servers/sampling.mdx @@ -0,0 +1,488 @@ +--- +title: LLM Sampling +sidebarTitle: Sampling +description: Request LLM text generation from the client or a configured provider through the MCP context. +icon: robot +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx"; + + + +LLM sampling allows your MCP tools to request text generation from an LLM during execution. This enables tools to leverage AI capabilities for analysis, generation, reasoning, and more—without the client needing to orchestrate multiple calls. + +By default, sampling requests are routed to the client's LLM. You can also configure a fallback handler to use a specific provider (like OpenAI) when the client doesn't support sampling, or to always use your own LLM regardless of client capabilities. + +## Method Reference + + + + Request text generation from the LLM, running to completion automatically. + + + + The prompt to send. Can be a simple string or a list of messages for multi-turn conversations. + + + + Instructions that establish the LLM's role and behavior. + + + + Controls randomness (0.0 = deterministic, 1.0 = creative). + + + + Maximum tokens to generate. + + + + Hints for which model the client should use. + + + + Functions the LLM can call during sampling. + + + + A type for validated structured output. Supports Pydantic models, dataclasses, and basic types like `int`, `list[str]`, or `dict[str, int]`. + + + + If True, mask detailed error messages from tool execution. When None (default), uses the global `settings.mask_error_details` value. Tools can raise `ToolError` to bypass masking and provide specific error messages to the LLM. + + + + + + + - `.text`: The raw text response (or JSON for structured output) + - `.result`: The typed result—same as `.text` for plain text, or a validated Pydantic object for structured output + - `.history`: All messages exchanged during sampling + + + + + + + + Make a single LLM sampling call. Use this for fine-grained control over the sampling loop. + + + Same as `sample()`, plus: + + + Controls tool usage: `"auto"`, `"required"`, or `"none"`. + + + + If True, execute tool calls and append results to history. If False, return immediately with tool calls available for manual execution. + + + + If True, mask detailed error messages from tool execution. When None (default), uses the global `settings.mask_error_details` value. Tools can raise `ToolError` to bypass masking. + + + + + + + - `.response`: The raw LLM response + - `.history`: Messages including input, assistant response, and tool results + - `.is_tool_use`: True if the LLM requested tool execution + - `.tool_calls`: List of tool calls (if any) + - `.text`: The text content (if any) + + + + + +## Basic Sampling + +The simplest use of sampling is passing a prompt string to `ctx.sample()`. The method sends the prompt to the LLM, waits for the complete response, and returns a `SamplingResult`. You can access the generated text through the `.text` attribute. + +```python +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def summarize(content: str, ctx: Context) -> str: + """Generate a summary of the provided content.""" + result = await ctx.sample(f"Please summarize this:\n\n{content}") + return result.text or "" +``` + +The `SamplingResult` also provides `.result` (identical to `.text` for plain text responses) and `.history` containing the full message exchange—useful if you need to continue the conversation or debug the interaction. + +### System Prompts + +System prompts let you establish the LLM's role and behavioral guidelines before it processes your request. This is useful for controlling tone, enforcing constraints, or providing context that shouldn't clutter the user-facing prompt. + +````python +@mcp.tool +async def generate_code(concept: str, ctx: Context) -> str: + """Generate a Python code example for a concept.""" + result = await ctx.sample( + messages=f"Write a Python example demonstrating '{concept}'.", + system_prompt=( + "You are an expert Python programmer. " + "Provide concise, working code without explanations." + ), + temperature=0.7, + max_tokens=300 + ) + return f"```python\n{result.text}\n```" +```` + +The `temperature` parameter controls randomness—higher values (up to 1.0) produce more varied outputs, while lower values make responses more deterministic. The `max_tokens` parameter limits response length. + +### Model Preferences + +Model preferences let you hint at which LLM the client should use for a request. You can pass a single model name or a list of preferences in priority order. These are hints rather than requirements—the actual model used depends on what the client has available. + +```python +@mcp.tool +async def technical_analysis(data: str, ctx: Context) -> str: + """Analyze data using a reasoning-focused model.""" + result = await ctx.sample( + messages=f"Analyze this data:\n\n{data}", + model_preferences=["claude-opus-4-5", "gpt-5-2"], + temperature=0.2, + ) + return result.text or "" +``` + +Use model preferences when different tasks benefit from different model characteristics. Creative writing might prefer faster models with higher temperature, while complex analysis might benefit from larger reasoning-focused models. + +### Multi-Turn Conversations + +For requests that need conversational context, construct a list of `SamplingMessage` objects representing the conversation history. Each message has a `role` ("user" or "assistant") and `content` (a `TextContent` object). + +```python +from mcp.types import SamplingMessage, TextContent + +@mcp.tool +async def contextual_analysis(query: str, data: str, ctx: Context) -> str: + """Analyze data with conversational context.""" + messages = [ + SamplingMessage( + role="user", + content=TextContent(type="text", text=f"Here's my data: {data}"), + ), + SamplingMessage( + role="assistant", + content=TextContent(type="text", text="I see the data. What would you like to know?"), + ), + SamplingMessage( + role="user", + content=TextContent(type="text", text=query), + ), + ] + result = await ctx.sample(messages=messages) + return result.text or "" +``` + +The LLM receives the full conversation thread and responds with awareness of the preceding context. + +## Structured Output + + + +When you need validated, typed data instead of free-form text, use the `result_type` parameter. FastMCP ensures the LLM returns data matching your type, handling validation and retries automatically. The `result_type` parameter accepts Pydantic models, dataclasses, and basic types like `int`, `list[str]`, or `dict[str, int]`. + +```python +from pydantic import BaseModel +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +class SentimentResult(BaseModel): + sentiment: str + confidence: float + reasoning: str + +@mcp.tool +async def analyze_sentiment(text: str, ctx: Context) -> SentimentResult: + """Analyze text sentiment with structured output.""" + result = await ctx.sample( + messages=f"Analyze the sentiment of: {text}", + result_type=SentimentResult, + ) + return result.result # A validated SentimentResult object +``` + +When you call this tool, the LLM returns a structured response that FastMCP validates against your Pydantic model. You access the validated object through `result.result`, while `result.text` contains the JSON representation. + + + When you pass `result_type`, `sample()` automatically creates a + `final_response` tool that the LLM calls to provide its response. If + validation fails, the error is sent back to the LLM for retry. This automatic + handling only applies to `sample()`—with `sample_step()`, you must manage + structured output yourself. + + +## Sampling with Tools + + + +Sampling with tools enables agentic workflows where the LLM can call functions to gather information before responding. This implements [SEP-1577](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577), allowing the LLM to autonomously orchestrate multi-step operations. + +Pass Python functions to the `tools` parameter, and FastMCP handles the execution loop automatically—calling tools, returning results to the LLM, and continuing until the LLM provides a final response. + +### Defining Tools + +Define regular Python functions with type hints and docstrings. FastMCP extracts the function's name, docstring, and parameter types to create tool schemas that the LLM can understand. + +```python +from fastmcp import FastMCP, Context + +def search(query: str) -> str: + """Search the web for information.""" + return f"Results for: {query}" + +def get_time() -> str: + """Get the current time.""" + from datetime import datetime + return datetime.now().strftime("%H:%M:%S") + +mcp = FastMCP() + +@mcp.tool +async def research(question: str, ctx: Context) -> str: + """Answer questions using available tools.""" + result = await ctx.sample( + messages=question, + tools=[search, get_time], + ) + return result.text or "" +``` + +The LLM sees each function's signature and docstring, using this information to decide when and how to call them. Tool errors are caught and sent back to the LLM, allowing it to recover gracefully. An internal safety limit prevents infinite loops. + +### Tool Error Handling + +By default, when a sampling tool raises an exception, the error message (including details) is sent back to the LLM so it can attempt recovery. To prevent sensitive information from leaking to the LLM, use the `mask_error_details` parameter: + +```python +result = await ctx.sample( + messages=question, + tools=[search], + mask_error_details=True, # Generic error messages only +) +``` + +When `mask_error_details=True`, tool errors become generic messages like `"Error executing tool 'search'"` instead of exposing stack traces or internal details. + +To intentionally provide specific error messages to the LLM regardless of masking, raise `ToolError`: + +```python +from fastmcp.exceptions import ToolError + +def search(query: str) -> str: + """Search for information.""" + if not query.strip(): + raise ToolError("Search query cannot be empty") + return f"Results for: {query}" +``` + +`ToolError` messages always pass through to the LLM, making it the escape hatch for errors you want the LLM to see and handle. + +For custom names or descriptions, use `SamplingTool.from_function()`: + +```python +from fastmcp.server.sampling import SamplingTool + +tool = SamplingTool.from_function( + my_func, + name="custom_name", + description="Custom description" +) + +result = await ctx.sample(messages="...", tools=[tool]) +``` + +### Combining with Structured Output + +Combine tools with `result_type` for agentic workflows that return validated, structured data. The LLM uses your tools to gather information, then returns a response matching your type. + +```python +result = await ctx.sample( + messages="Research Python async patterns", + tools=[search, fetch_url], + result_type=ResearchResult, +) +``` + +## Loop Control + + + +While `sample()` handles the tool execution loop automatically, some scenarios require fine-grained control over each step. The `sample_step()` method makes a single LLM call and returns a `SampleStep` containing the response and updated history. + +Unlike `sample()`, `sample_step()` is stateless—it doesn't remember previous calls. You control the conversation by passing the full message history each time. The returned `step.history` includes all messages up through the current response, making it easy to continue the loop. + +Use `sample_step()` when you need to: + +- Inspect tool calls before they execute +- Implement custom termination conditions +- Add logging, metrics, or checkpointing between steps +- Build custom agentic loops with domain-specific logic + +### Using sample_step() + +By default, `sample_step()` executes any tool calls and includes the results in the history. Call it in a loop, passing the updated history each time, until a stop condition is met. + +```python +from mcp.types import SamplingMessage + +@mcp.tool +async def controlled_agent(question: str, ctx: Context) -> str: + """Agent with manual loop control.""" + messages: list[str | SamplingMessage] = [question] # strings auto-convert + + while True: + step = await ctx.sample_step( + messages=messages, + tools=[search, get_time], + ) + + if step.is_tool_use: + # Tools already executed (execute_tools=True by default) + # Log what was called before continuing + for call in step.tool_calls: + print(f"Called tool: {call.name}") + + if not step.is_tool_use: + return step.text or "" + + # Continue with updated history + messages = step.history +``` + +### SampleStep Properties + +Each `SampleStep` provides information about what the LLM returned: + +- `step.is_tool_use` — True if the LLM requested tool calls +- `step.tool_calls` — List of tool calls requested (if any) +- `step.text` — The text content (if any) +- `step.history` — All messages exchanged so far + +The contents of `step.history` depend on `execute_tools`: +- **`execute_tools=True`** (default): Includes tool results, ready for the next iteration +- **`execute_tools=False`**: Includes the assistant's tool request, but you add results yourself + +### Manual Tool Execution + +Set `execute_tools=False` to handle tool execution yourself. When disabled, `step.history` contains the user message and the assistant's response with tool calls—but no tool results. You execute the tools and append the results as a user message. + +```python +from mcp.types import SamplingMessage, ToolResultContent, TextContent +from fastmcp import FastMCP, Context + +mcp = FastMCP() + +@mcp.tool +async def research(question: str, ctx: Context) -> str: + """Research with manual tool handling.""" + + def search(query: str) -> str: + return f"Results for: {query}" + + def get_time() -> str: + return "12:00 PM" + + # Map tool names to functions + tools = {"search": search, "get_time": get_time} + + messages: list[SamplingMessage] = [question] # strings are converted automatically + + while True: + step = await ctx.sample_step( + messages=messages, + tools=list(tools.values()), + execute_tools=False, + ) + + if not step.is_tool_use: + return step.text or "" + + # Execute tools and collect results + tool_results = [] + for call in step.tool_calls: + fn = tools[call.name] + result = fn(**call.input) + tool_results.append( + ToolResultContent( + type="tool_result", + toolUseId=call.id, + content=[TextContent(type="text", text=result)], + ) + ) + + messages = list(step.history) + messages.append(SamplingMessage(role="user", content=tool_results)) +``` + +#### Error Handling + +To report an error, set `isError=True`. The LLM will see the error and can decide how to proceed: + +```python +tool_result = ToolResultContent( + type="tool_result", + toolUseId=call.id, + content=[TextContent(type="text", text="Permission denied")], + isError=True, +) +``` + +## Fallback Handlers + +Client support for sampling is optional—some clients may not implement it. To ensure your tools work regardless of client capabilities, configure a `sampling_handler` that sends requests directly to an LLM provider. + +FastMCP provides built-in handlers for [OpenAI and Anthropic APIs](/clients/sampling#built-in-handlers). These handlers support the full sampling API including tools, automatically converting your Python functions to each provider's format. + + +Install handlers with `pip install fastmcp[openai]` or `pip install fastmcp[anthropic]`. + + +```python +from fastmcp import FastMCP +from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler + +server = FastMCP( + name="My Server", + sampling_handler=OpenAISamplingHandler(default_model="gpt-4o-mini"), + sampling_handler_behavior="fallback", +) +``` + +Or with Anthropic: + +```python +from fastmcp import FastMCP +from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler + +server = FastMCP( + name="My Server", + sampling_handler=AnthropicSamplingHandler(default_model="claude-sonnet-4-5"), + sampling_handler_behavior="fallback", +) +``` + +### Behavior Modes + +The `sampling_handler_behavior` parameter controls when the handler is used: + +- **`"fallback"`** (default): Use the handler only when the client doesn't support sampling. This lets capable clients use their own LLM while ensuring your tools still work with clients that lack sampling support. +- **`"always"`**: Always use the handler, bypassing the client entirely. Use this when you need guaranteed control over which LLM processes requests—for cost control, compliance requirements, or when specific model characteristics are essential. + + + Sampling with tools requires the client to advertise the `sampling.tools` + capability. FastMCP clients do this automatically. For external clients that + don't support tool-enabled sampling, configure a fallback handler with + `sampling_handler_behavior="always"`. + diff --git a/docs/v2/servers/server.mdx b/docs/v2/servers/server.mdx new file mode 100644 index 000000000..8d8d362f8 --- /dev/null +++ b/docs/v2/servers/server.mdx @@ -0,0 +1,436 @@ +--- +title: The FastMCP Server +sidebarTitle: Overview +description: The core FastMCP server class for building MCP applications with tools, resources, and prompts. +icon: server +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + +The central piece of a FastMCP application is the `FastMCP` server class. This class acts as the main container for your application's tools, resources, and prompts, and manages communication with MCP clients. + +## Creating a Server + +Instantiating a server is straightforward. You typically provide a name for your server, which helps identify it in client applications or logs. + +```python +from fastmcp import FastMCP + +# Create a basic server instance +mcp = FastMCP(name="MyAssistantServer") + +# You can also add instructions for how to interact with the server +mcp_with_instructions = FastMCP( + name="HelpfulAssistant", + instructions=""" + This server provides data analysis tools. + Call get_average() to analyze numerical data. + """, +) +``` + +The `FastMCP` constructor accepts several arguments: + + + + A human-readable name for your server + + + + Description of how to interact with this server. These instructions help clients understand the server's purpose and available functionality + + + + Version string for your server. If not provided, defaults to the FastMCP library version + + + + + + URL to a website with more information about your server. Displayed in client applications + + + + + + List of icon representations for your server. Icons help users visually identify your server in client applications. See [Icons](/servers/icons) for detailed examples + + + + Authentication provider for securing HTTP-based transports. See [Authentication](/servers/auth/authentication) for configuration options + + + + An async context manager function for server startup and shutdown logic + + + + A list of tools (or functions to convert to tools) to add to the server. In some cases, providing tools programmatically may be more convenient than using the `@mcp.tool` decorator + + + + + Only expose components with at least one matching tag + + + + Hide components with any matching tag + + + + How to handle duplicate tool registrations + + + + How to handle duplicate resource registrations + + + + How to handle duplicate prompt registrations + + + + + + Controls how tool input parameters are validated. When `False` (default), FastMCP uses Pydantic's flexible validation that coerces compatible inputs (e.g., `"10"` → `10` for int parameters). When `True`, uses the MCP SDK's JSON Schema validation to validate inputs against the exact schema before passing them to your function, rejecting any type mismatches. The default mode improves compatibility with LLM clients while maintaining type safety. See [Input Validation Modes](/servers/tools#input-validation-modes) for details + + + + + + Whether to include FastMCP metadata in component responses. When `True`, component tags and other FastMCP-specific metadata are included in the `_fastmcp` namespace within each component's `meta` field. When `False`, this metadata is omitted, resulting in cleaner integration with external systems. Can be overridden globally via `FASTMCP_INCLUDE_FASTMCP_META` environment variable + + +## Components + +FastMCP servers expose several types of components to the client: + +### Tools + +Tools are functions that the client can call to perform actions or access external systems. + +```python +@mcp.tool +def multiply(a: float, b: float) -> float: + """Multiplies two numbers together.""" + return a * b +``` + +See [Tools](/servers/tools) for detailed documentation. + +### Resources + +Resources expose data sources that the client can read. + +```python +@mcp.resource("data://config") +def get_config() -> dict: + """Provides the application configuration.""" + return {"theme": "dark", "version": "1.0"} +``` + +See [Resources & Templates](/servers/resources) for detailed documentation. + +### Resource Templates + +Resource templates are parameterized resources that allow the client to request specific data. + +```python +@mcp.resource("users://{user_id}/profile") +def get_user_profile(user_id: int) -> dict: + """Retrieves a user's profile by ID.""" + # The {user_id} in the URI is extracted and passed to this function + return {"id": user_id, "name": f"User {user_id}", "status": "active"} +``` + +See [Resources & Templates](/servers/resources) for detailed documentation. + +### Prompts + +Prompts are reusable message templates for guiding the LLM. + +```python +@mcp.prompt +def analyze_data(data_points: list[float]) -> str: + """Creates a prompt asking for analysis of numerical data.""" + formatted_data = ", ".join(str(point) for point in data_points) + return f"Please analyze these data points: {formatted_data}" +``` + +See [Prompts](/servers/prompts) for detailed documentation. + +## Tag-Based Filtering + + + +FastMCP supports tag-based filtering to selectively expose components based on configurable include/exclude tag sets. This is useful for creating different views of your server for different environments or users. + +Components can be tagged when defined using the `tags` parameter: + +```python +@mcp.tool(tags={"public", "utility"}) +def public_tool() -> str: + return "This tool is public" + +@mcp.tool(tags={"internal", "admin"}) +def admin_tool() -> str: + return "This tool is for admins only" +``` + + +The filtering logic works as follows: +- **Include tags**: If specified, only components with at least one matching tag are exposed +- **Exclude tags**: Components with any matching tag are filtered out +- **Precedence**: Exclude tags always take priority over include tags + + +To ensure a component is never exposed, you can set `enabled=False` on the component itself. To learn more, see the component-specific documentation. + + +You configure tag-based filtering when creating your server: + +```python +# Only expose components tagged with "public" +mcp = FastMCP(include_tags={"public"}) + +# Hide components tagged as "internal" or "deprecated" +mcp = FastMCP(exclude_tags={"internal", "deprecated"}) + +# Combine both: show admin tools but hide deprecated ones +mcp = FastMCP(include_tags={"admin"}, exclude_tags={"deprecated"}) +``` + +This filtering applies to all component types (tools, resources, resource templates, and prompts) and affects both listing and access. + +## Running the Server + +FastMCP servers need a transport mechanism to communicate with clients. You typically start your server by calling the `mcp.run()` method on your `FastMCP` instance, often within an `if __name__ == "__main__":` block in your main server script. This pattern ensures compatibility with various MCP clients. + +```python +# my_server.py +from fastmcp import FastMCP + +mcp = FastMCP(name="MyServer") + +@mcp.tool +def greet(name: str) -> str: + """Greet a user by name.""" + return f"Hello, {name}!" + +if __name__ == "__main__": + # This runs the server, defaulting to STDIO transport + mcp.run() + + # To use a different transport, e.g., HTTP: + # mcp.run(transport="http", host="127.0.0.1", port=9000) +``` + +FastMCP supports several transport options: +- STDIO (default, for local tools) +- HTTP (recommended for web services, uses Streamable HTTP protocol) +- SSE (legacy web transport, deprecated) + +The server can also be run using the FastMCP CLI. + +For detailed information on each transport, how to configure them (host, port, paths), and when to use which, please refer to the [**Running Your FastMCP Server**](/deployment/running-server) guide. + +## Custom Routes + +When running your server with HTTP transport, you can add custom web routes alongside your MCP endpoint using the `@custom_route` decorator. This is useful for simple endpoints like health checks that need to be served alongside your MCP server: + +```python +from fastmcp import FastMCP +from starlette.requests import Request +from starlette.responses import PlainTextResponse + +mcp = FastMCP("MyServer") + +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(request: Request) -> PlainTextResponse: + return PlainTextResponse("OK") + +if __name__ == "__main__": + mcp.run(transport="http") # Health check at http://localhost:8000/health +``` + +Custom routes are served alongside your MCP endpoint and are useful for: +- Health check endpoints for monitoring +- Simple status or info endpoints +- Basic webhooks or callbacks + +For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/http#integration-with-web-frameworks). + +## Composing Servers + + + +FastMCP supports composing multiple servers together using `import_server` (static copy) and `mount` (live link). This allows you to organize large applications into modular components or reuse existing servers. + +See the [Server Composition](/servers/composition) guide for full details, best practices, and examples. + +```python +# Example: Importing a subserver +from fastmcp import FastMCP +import asyncio + +main = FastMCP(name="Main") +sub = FastMCP(name="Sub") + +@sub.tool +def hello(): + return "hi" + +# Mount directly +main.mount(sub, prefix="sub") +``` + +## Proxying Servers + + + +FastMCP can act as a proxy for any MCP server (local or remote) using `FastMCP.as_proxy`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa. + +Proxies automatically handle concurrent operations safely by creating fresh sessions for each request when using disconnected clients. + +See the [Proxying Servers](/servers/proxy) guide for details and advanced usage. + +```python +from fastmcp import FastMCP, Client + +backend = Client("http://example.com/mcp/sse") +proxy = FastMCP.as_proxy(backend, name="ProxyServer") +# Now use the proxy like any FastMCP server +``` + +## OpenAPI Integration + + + +FastMCP can automatically generate servers from OpenAPI specifications or existing FastAPI applications using `FastMCP.from_openapi()` and `FastMCP.from_fastapi()`. This allows you to instantly convert existing APIs into MCP servers without manual tool creation. + +See the [FastAPI Integration](/integrations/fastapi) and [OpenAPI Integration](/integrations/openapi) guides for detailed examples and configuration options. + +```python +import httpx +from fastmcp import FastMCP + +# From OpenAPI spec +spec = httpx.get("https://api.example.com/openapi.json").json() +mcp = FastMCP.from_openapi(openapi_spec=spec, client=httpx.AsyncClient()) + +# From FastAPI app +from fastapi import FastAPI +app = FastAPI() +mcp = FastMCP.from_fastapi(app=app) +``` + +## Server Configuration + +Servers can be configured using a combination of initialization arguments, global settings, and transport-specific settings. + +### Server-Specific Configuration + +Server-specific settings are passed when creating the `FastMCP` instance and control server behavior: + +```python +from fastmcp import FastMCP + +# Configure server-specific settings +mcp = FastMCP( + name="ConfiguredServer", + include_tags={"public", "api"}, # Only expose these tagged components + exclude_tags={"internal", "deprecated"}, # Hide these tagged components + on_duplicate_tools="error", # Handle duplicate registrations + on_duplicate_resources="warn", + on_duplicate_prompts="replace", + include_fastmcp_meta=False, # Disable FastMCP metadata for cleaner integration +) +``` + +### Global Settings + +Global settings affect all FastMCP servers and can be configured via environment variables (prefixed with `FASTMCP_`) or in a `.env` file: + +```python +import fastmcp + +# Access global settings +print(fastmcp.settings.log_level) # Default: "INFO" +print(fastmcp.settings.mask_error_details) # Default: False +print(fastmcp.settings.strict_input_validation) # Default: False +print(fastmcp.settings.include_fastmcp_meta) # Default: True +``` + +Common global settings include: +- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL` +- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS` +- **`strict_input_validation`**: Controls tool input validation mode (default: False for flexible coercion), set with `FASTMCP_STRICT_INPUT_VALIDATION`. See [Input Validation Modes](/servers/tools#input-validation-modes) +- **`include_fastmcp_meta`**: Whether to include FastMCP metadata in component responses (default: True), set with `FASTMCP_INCLUDE_FASTMCP_META` +- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv + +### Transport-Specific Configuration + +Transport settings are provided when running the server and control network behavior: + +```python +# Configure transport when running +mcp.run( + transport="http", + host="0.0.0.0", # Bind to all interfaces + port=9000, # Custom port + log_level="DEBUG", # Override global log level +) + +# Or for async usage +await mcp.run_async( + transport="http", + host="127.0.0.1", + port=8080, +) +``` + +### Setting Global Configuration + +Global FastMCP settings can be configured via environment variables (prefixed with `FASTMCP_`): + +```bash +# Configure global FastMCP behavior +export FASTMCP_LOG_LEVEL=DEBUG +export FASTMCP_MASK_ERROR_DETAILS=True +export FASTMCP_STRICT_INPUT_VALIDATION=False +export FASTMCP_INCLUDE_FASTMCP_META=False +``` + +### Custom Tool Serialization + + + +By default, FastMCP serializes tool return values to JSON when they need to be converted to text. You can customize this behavior by providing a `tool_serializer` function when creating your server: + +```python +import yaml +from fastmcp import FastMCP + +# Define a custom serializer that formats dictionaries as YAML +def yaml_serializer(data): + return yaml.dump(data, sort_keys=False) + +# Create a server with the custom serializer +mcp = FastMCP(name="MyServer", tool_serializer=yaml_serializer) + +@mcp.tool +def get_config(): + """Returns configuration in YAML format.""" + return {"api_key": "abc123", "debug": True, "rate_limit": 100} +``` + +The serializer function takes any data object and returns a string representation. This is applied to **all non-string return values** from your tools. Tools that already return strings bypass the serializer. + +This customization is useful when you want to: +- Format data in a specific way (like YAML or custom formats) +- Control specific serialization options (like indentation or sorting) +- Add metadata or transform data before sending it to clients + + +If the serializer function raises an exception, the tool will fall back to the default JSON serialization to avoid breaking the server. + diff --git a/docs/v2/servers/storage-backends.mdx b/docs/v2/servers/storage-backends.mdx new file mode 100644 index 000000000..f60ea1821 --- /dev/null +++ b/docs/v2/servers/storage-backends.mdx @@ -0,0 +1,275 @@ +--- +title: Storage Backends +sidebarTitle: Storage Backends +description: Configure persistent and distributed storage for caching and OAuth state management +icon: database +tag: NEW +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +FastMCP uses pluggable storage backends for caching responses and managing OAuth state. By default, all storage is in-memory, which is perfect for development but doesn't persist across restarts. FastMCP includes support for multiple storage backends, and you can easily extend it with custom implementations. + + +The storage layer is powered by **[py-key-value-aio](https://github.com/strawgate/py-key-value)**, an async key-value library maintained by a core FastMCP maintainer. This library provides a unified interface for multiple backends, making it easy to swap implementations based on your deployment needs. + + +## Available Backends + +### In-Memory Storage + +**Best for:** Development, testing, single-process deployments + +In-memory storage is the default for all FastMCP storage needs. It's fast, requires no setup, and is perfect for getting started. + +```python +from key_value.aio.stores.memory import MemoryStore + +# Used by default - no configuration needed +# But you can also be explicit: +cache_store = MemoryStore() +``` + +**Characteristics:** +- ✅ No setup required +- ✅ Very fast +- ❌ Data lost on restart +- ❌ Not suitable for multi-process deployments + +### Disk Storage + +**Best for:** Single-server production deployments, persistent caching + +Disk storage persists data to the filesystem, allowing it to survive server restarts. + +```python +from key_value.aio.stores.disk import DiskStore +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +# Persistent response cache +middleware = ResponseCachingMiddleware( + cache_storage=DiskStore(directory="/var/cache/fastmcp") +) +``` + +Or with OAuth token storage: + +```python +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.disk import DiskStore + +auth = GitHubProvider( + client_id="your-id", + client_secret="your-secret", + base_url="https://your-server.com", + client_storage=DiskStore(directory="/var/lib/fastmcp/oauth") +) +``` + +**Characteristics:** +- ✅ Data persists across restarts +- ✅ Good performance for moderate load +- ❌ Not suitable for distributed deployments +- ❌ Filesystem access required + +### Redis + +**Best for:** Distributed production deployments, shared caching across multiple servers + + +Redis support requires an optional dependency: `pip install 'py-key-value-aio[redis]'` + + +Redis provides distributed caching and state management, ideal for production deployments with multiple server instances. + +```python +from key_value.aio.stores.redis import RedisStore +from fastmcp.server.middleware.caching import ResponseCachingMiddleware + +# Distributed response cache +middleware = ResponseCachingMiddleware( + cache_storage=RedisStore(host="redis.example.com", port=6379) +) +``` + +With authentication: + +```python +from key_value.aio.stores.redis import RedisStore + +cache_store = RedisStore( + host="redis.example.com", + port=6379, + password="your-redis-password" +) +``` + +For OAuth token storage: + +```python +import os +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.redis import RedisStore + +auth = GitHubProvider( + client_id=os.environ["GITHUB_CLIENT_ID"], + client_secret=os.environ["GITHUB_CLIENT_SECRET"], + base_url="https://your-server.com", + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + client_storage=RedisStore(host="redis.example.com", port=6379) +) +``` + +**Characteristics:** +- ✅ Distributed and highly available +- ✅ Fast in-memory performance +- ✅ Works across multiple server instances +- ✅ Built-in TTL support +- ❌ Requires Redis infrastructure +- ❌ Network latency vs local storage + +### Other Backends from py-key-value-aio + +The py-key-value-aio library includes additional implementations for various storage systems: + +- **DynamoDB** - AWS distributed database +- **MongoDB** - NoSQL document store +- **Elasticsearch** - Distributed search and analytics +- **Memcached** - Distributed memory caching +- **RocksDB** - Embedded high-performance key-value store +- **Valkey** - Redis-compatible server + +For configuration details on these backends, consult the [py-key-value-aio documentation](https://github.com/strawgate/py-key-value). + + +Before using these backends in production, review the [py-key-value documentation](https://github.com/strawgate/py-key-value) to understand the maturity level and limitations of your chosen backend. Some backends may be in preview or have specific constraints that make them unsuitable for production use. + + +## Use Cases in FastMCP + +### Server-Side OAuth Token Storage + +The [OAuth Proxy](/servers/auth/oauth-proxy) and OAuth auth providers use storage for persisting OAuth client registrations and upstream tokens. **By default, storage is automatically encrypted using `FernetEncryptionWrapper`.** When providing custom storage, wrap it in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest. + +**Development (default behavior):** + +By default, FastMCP automatically manages keys and storage based on your platform: +- **Mac/Windows**: Keys are auto-managed via system keyring, storage defaults to disk. Suitable **only** for development and local testing. +- **Linux**: Keys are ephemeral, storage defaults to memory. + +No configuration needed: + +```python +from fastmcp.server.auth.providers.github import GitHubProvider + +auth = GitHubProvider( + client_id="your-id", + client_secret="your-secret", + base_url="https://your-server.com" +) +``` + +**Production:** + +For production deployments, configure explicit keys and persistent network-accessible storage with encryption: + +```python +import os +from fastmcp.server.auth.providers.github import GitHubProvider +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.encryption import FernetEncryptionWrapper +from cryptography.fernet import Fernet + +auth = GitHubProvider( + client_id=os.environ["GITHUB_CLIENT_ID"], + client_secret=os.environ["GITHUB_CLIENT_SECRET"], + base_url="https://your-server.com", + # Explicit JWT signing key (required for production) + jwt_signing_key=os.environ["JWT_SIGNING_KEY"], + # Encrypted persistent storage (required for production) + client_storage=FernetEncryptionWrapper( + key_value=RedisStore(host="redis.example.com", port=6379), + fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"]) + ) +) +``` + +Both parameters are required for production. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. See [OAuth Token Security](/deployment/http#oauth-token-security) and [Key and Storage Management](/servers/auth/oauth-proxy#key-and-storage-management) for complete setup details. + +### Response Caching Middleware + +The [Response Caching Middleware](/servers/middleware#caching-middleware) caches tool calls, resource reads, and prompt requests. Storage configuration is passed via the `cache_storage` parameter: + +```python +from fastmcp import FastMCP +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.disk import DiskStore + +mcp = FastMCP("My Server") + +# Cache to disk instead of memory +mcp.add_middleware(ResponseCachingMiddleware( + cache_storage=DiskStore(directory="cache") +)) +``` + +For multi-server deployments sharing a Redis instance: + +```python +from fastmcp.server.middleware.caching import ResponseCachingMiddleware +from key_value.aio.stores.redis import RedisStore +from key_value.aio.wrappers.prefix_collections import PrefixCollectionsWrapper + +base_store = RedisStore(host="redis.example.com") +namespaced_store = PrefixCollectionsWrapper( + key_value=base_store, + prefix="my-server" +) + +middleware = ResponseCachingMiddleware(cache_storage=namespaced_store) +``` + +### Client-Side OAuth Token Storage + +The [FastMCP Client](/clients/client) uses storage for persisting OAuth tokens locally. By default, tokens are stored in memory: + +```python +from fastmcp.client.auth import OAuthClientProvider +from key_value.aio.stores.disk import DiskStore + +# Store tokens on disk for persistence across restarts +token_storage = DiskStore(directory="~/.local/share/fastmcp/tokens") + +oauth_provider = OAuthClientProvider( + mcp_url="https://your-mcp-server.com/mcp/sse", + token_storage=token_storage +) +``` + +This allows clients to reconnect without re-authenticating after restarts. + +## Choosing a Backend + +| Backend | Development | Single Server | Multi-Server | Cloud Native | +|---------|-------------|---------------|--------------|--------------| +| Memory | ✅ Best | ⚠️ Limited | ❌ | ❌ | +| Disk | ✅ Good | ✅ Recommended | ❌ | ⚠️ | +| Redis | ⚠️ Overkill | ✅ Good | ✅ Best | ✅ Best | +| DynamoDB | ❌ | ⚠️ | ✅ | ✅ Best (AWS) | +| MongoDB | ❌ | ⚠️ | ✅ | ✅ Good | + +**Decision tree:** + +1. **Just starting?** Use **Memory** (default) - no configuration needed +2. **Single server, needs persistence?** Use **Disk** +3. **Multiple servers or cloud deployment?** Use **Redis** or **DynamoDB** +4. **Existing infrastructure?** Look for a matching py-key-value-aio backend + +## More Resources + +- [py-key-value-aio GitHub](https://github.com/strawgate/py-key-value) - Full library documentation +- [Response Caching Middleware](/servers/middleware#caching-middleware) - Using storage for caching +- [OAuth Token Security](/deployment/http#oauth-token-security) - Production OAuth configuration +- [HTTP Deployment](/deployment/http) - Complete deployment guide diff --git a/docs/v2/servers/tasks.mdx b/docs/v2/servers/tasks.mdx new file mode 100644 index 000000000..daae4750b --- /dev/null +++ b/docs/v2/servers/tasks.mdx @@ -0,0 +1,223 @@ +--- +title: Background Tasks +sidebarTitle: Background Tasks +description: Run long-running operations asynchronously with progress tracking +icon: clock +tag: "NEW" +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +FastMCP implements the MCP background task protocol ([SEP-1686](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)), giving your servers a production-ready distributed task scheduler with a single decorator change. + + +**What is Docket?** FastMCP's task system is powered by [Docket](https://github.com/chrisguidry/docket), originally built by [Prefect](https://prefect.io) to power [Prefect Cloud](https://www.prefect.io/prefect/cloud)'s managed task scheduling and execution service, where it processes millions of concurrent tasks every day. Docket is now open-sourced for the community. + + + +## What Are MCP Background Tasks? + +In MCP, all component interactions are blocking by default. When a client calls a tool, reads a resource, or fetches a prompt, it sends a request and waits for the response. For operations that take seconds or minutes, this creates a poor user experience. + +The MCP background task protocol solves this by letting clients: +1. **Start** an operation and receive a task ID immediately +2. **Track** progress as the operation runs +3. **Retrieve** the result when ready + +FastMCP handles all of this for you. Add `task=True` to your decorator, and your function gains full background execution with progress reporting, distributed processing, and horizontal scaling. + +### MCP Background Tasks vs Python Concurrency + +You can always use Python's concurrency primitives (asyncio, threads, multiprocessing) or external task queues in your FastMCP servers. FastMCP is just Python—run code however you like. + +MCP background tasks are different: they're **protocol-native**. This means MCP clients that support the task protocol can start operations, receive progress updates, and retrieve results through the standard MCP interface. The coordination happens at the protocol level, not inside your application code. + +## Enabling Background Tasks + +Add `task=True` to any tool, resource, resource template, or prompt decorator. This marks the component as capable of background execution. + +```python {6} +import asyncio +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") + +@mcp.tool(task=True) +async def slow_computation(duration: int) -> str: + """A long-running operation.""" + for i in range(duration): + await asyncio.sleep(1) + return f"Completed in {duration} seconds" +``` + +When a client requests background execution, the call returns immediately with a task ID. The work executes in a background worker, and the client can poll for status or wait for the result. + + +Background tasks require async functions. Attempting to use `task=True` with a sync function raises a `ValueError` at registration time. + + +## Execution Modes + +For fine-grained control over task execution behavior, use `TaskConfig` instead of the boolean shorthand. The MCP task protocol defines three execution modes: + +| Mode | Client calls without task | Client calls with task | +|------|--------------------------|------------------------| +| `"forbidden"` | Executes synchronously | Error: task not supported | +| `"optional"` | Executes synchronously | Executes as background task | +| `"required"` | Error: task required | Executes as background task | + +```python +from fastmcp import FastMCP +from fastmcp.server.tasks import TaskConfig + +mcp = FastMCP("MyServer") + +# Supports both sync and background execution (default when task=True) +@mcp.tool(task=TaskConfig(mode="optional")) +async def flexible_task() -> str: + return "Works either way" + +# Requires background execution - errors if client doesn't request task +@mcp.tool(task=TaskConfig(mode="required")) +async def must_be_background() -> str: + return "Only runs as a background task" + +# No task support (default when task=False or omitted) +@mcp.tool(task=TaskConfig(mode="forbidden")) +async def sync_only() -> str: + return "Never runs as background task" +``` + +The boolean shortcuts map to these modes: +- `task=True` → `TaskConfig(mode="optional")` +- `task=False` → `TaskConfig(mode="forbidden")` + +### Server-Wide Default + +To enable background task support for all components by default, pass `tasks=True` to the constructor. Individual decorators can still override this with `task=False`. + +```python +mcp = FastMCP("MyServer", tasks=True) +``` + + +If your server defines any synchronous tools, resources, or prompts, you will need to explicitly set `task=False` on their decorators to avoid an error. + + +### Graceful Degradation + +When a client requests background execution but the component has `mode="forbidden"`, FastMCP executes synchronously and returns the result inline. This follows the SEP-1686 specification for graceful degradation—clients can always request background execution without worrying about server capabilities. + +Conversely, when a component has `mode="required"` but the client doesn't request background execution, FastMCP returns an error indicating that task execution is required. + +### Configuration + +| Environment Variable | Default | Description | +|---------------------|---------|-------------| +| `FASTMCP_DOCKET_URL` | `memory://` | Backend URL (`memory://` or `redis://host:port/db`) | + +## Backends + +FastMCP supports two backends for task execution, each with different tradeoffs. + +### In-Memory Backend (Default) + +The in-memory backend (`memory://`) requires zero configuration and works out of the box. + +**Advantages:** +- No external dependencies +- Simple single-process deployment + +**Disadvantages:** +- **Ephemeral**: If the server restarts, all pending tasks are lost +- **Higher latency**: ~250ms task pickup time vs single-digit milliseconds with Redis +- **No horizontal scaling**: Single process only—you cannot add additional workers + +### Redis Backend + +For production deployments, use Redis (or Valkey) as your backend by setting `FASTMCP_DOCKET_URL=redis://localhost:6379`. + +**Advantages:** +- **Persistent**: Tasks survive server restarts +- **Fast**: Single-digit millisecond task pickup latency +- **Scalable**: Add workers to distribute load across processes or machines + +## Workers + +Every FastMCP server with task-enabled components automatically starts an **embedded worker**. You do not need to start a separate worker process for tasks to execute. + +To scale horizontally, add more workers using the CLI: + +```bash +fastmcp tasks worker server.py +``` + +Each additional worker pulls tasks from the same queue, distributing load across processes. Configure worker concurrency via environment: + +```bash +export FASTMCP_DOCKET_CONCURRENCY=20 +fastmcp tasks worker server.py +``` + + +Additional workers only work with Redis/Valkey backends. The in-memory backend is single-process only. + + +## Progress Reporting + +The `Progress` dependency lets you report progress back to clients. Inject it as a parameter with a default value, and FastMCP will provide the active progress reporter. + +```python +from fastmcp import FastMCP +from fastmcp.dependencies import Progress + +mcp = FastMCP("MyServer") + +@mcp.tool(task=True) +async def process_files(files: list[str], progress: Progress = Progress()) -> str: + await progress.set_total(len(files)) + + for file in files: + await progress.set_message(f"Processing {file}") + # ... do work ... + await progress.increment() + + return f"Processed {len(files)} files" +``` + +The progress API: +- `await progress.set_total(n)` — Set the total number of steps +- `await progress.increment(amount=1)` — Increment progress +- `await progress.set_message(text)` — Update the status message + +Progress works in both immediate and background execution modes—you can use the same code regardless of how the client invokes your function. + +## Docket Dependencies + +FastMCP exposes Docket's full dependency injection system within your task-enabled functions. Beyond `Progress`, you can access the Docket instance, worker information, and use advanced features like retries and timeouts. + +```python +from docket import Docket, Worker +from fastmcp import FastMCP +from fastmcp.dependencies import Progress, CurrentDocket, CurrentWorker + +mcp = FastMCP("MyServer") + +@mcp.tool(task=True) +async def my_task( + progress: Progress = Progress(), + docket: Docket = CurrentDocket(), + worker: Worker = CurrentWorker(), +) -> str: + # Schedule additional background work + await docket.add(another_task, arg1, arg2) + + # Access worker metadata + worker_name = worker.name + + return "Done" +``` + +With `CurrentDocket()`, you can schedule additional background tasks, chain work together, and coordinate complex workflows. See the [Docket documentation](https://chrisguidry.github.io/docket/) for the complete API, including retry policies, timeouts, and custom dependencies. diff --git a/docs/v2/servers/tools.mdx b/docs/v2/servers/tools.mdx new file mode 100644 index 000000000..b443e8e77 --- /dev/null +++ b/docs/v2/servers/tools.mdx @@ -0,0 +1,986 @@ +--- +title: Tools +sidebarTitle: Tools +description: Expose functions as executable capabilities for your MCP client. +icon: wrench +--- + +import { VersionBadge } from '/snippets/version-badge.mdx' + +Tools are the core building blocks that allow your LLM to interact with external systems, execute code, and access data that isn't in its training data. In FastMCP, tools are Python functions exposed to LLMs through the MCP protocol. + +Tools in FastMCP transform regular Python functions into capabilities that LLMs can invoke during conversations. When an LLM decides to use a tool: + +1. It sends a request with parameters based on the tool's schema. +2. FastMCP validates these parameters against your function's signature. +3. Your function executes with the validated inputs. +4. The result is returned to the LLM, which can use it in its response. + +This allows LLMs to perform tasks like querying databases, calling APIs, making calculations, or accessing files—extending their capabilities beyond what's in their training data. + + +## The `@tool` Decorator + +Creating a tool is as simple as decorating a Python function with `@mcp.tool`: + +```python +from fastmcp import FastMCP + +mcp = FastMCP(name="CalculatorServer") + +@mcp.tool +def add(a: int, b: int) -> int: + """Adds two integer numbers together.""" + return a + b +``` + +When this tool is registered, FastMCP automatically: +- Uses the function name (`add`) as the tool name. +- Uses the function's docstring (`Adds two integer numbers...`) as the tool description. +- Generates an input schema based on the function's parameters and type annotations. +- Handles parameter validation and error reporting. + +The way you define your Python function dictates how the tool appears and behaves for the LLM client. + + +Functions with `*args` or `**kwargs` are not supported as tools. This restriction exists because FastMCP needs to generate a complete parameter schema for the MCP protocol, which isn't possible with variable argument lists. + + +### Decorator Arguments + +While FastMCP infers the name and description from your function, you can override these and add additional metadata using arguments to the `@mcp.tool` decorator: + +```python +@mcp.tool( + name="find_products", # Custom tool name for the LLM + description="Search the product catalog with optional category filtering.", # Custom description + tags={"catalog", "search"}, # Optional tags for organization/filtering + meta={"version": "1.2", "author": "product-team"} # Custom metadata +) +def search_products_implementation(query: str, category: str | None = None) -> list[dict]: + """Internal function description (ignored if description is provided above).""" + # Implementation... + print(f"Searching for '{query}' in category '{category}'") + return [{"id": 2, "name": "Another Product"}] +``` + + + + Sets the explicit tool name exposed via MCP. If not provided, uses the function name + + + + Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose + + + + A set of strings used to categorize the tool. These can be used by the server and, in some cases, by clients to filter or group available tools. + + + + A boolean to enable or disable the tool. See [Disabling Tools](#disabling-tools) for more information + + + + + + Optional list of icon representations for this tool. See [Icons](/servers/icons) for detailed examples + + + + An optional `ToolAnnotations` object or dictionary to add additional metadata about the tool. + + + A human-readable title for the tool. + + + If true, the tool does not modify its environment. + + + If true, the tool may perform destructive updates to its environment. + + + If true, calling the tool repeatedly with the same arguments will have no additional effect on the its environment. + + + If true, this tool may interact with an "open world" of external entities. If false, the tool's domain of interaction is closed. + + + + + + + + Optional meta information about the tool. This data is passed through to the MCP client as the `_meta` field of the client-side tool object and can be used for custom metadata, versioning, or other application-specific purposes. + + + + +### Async Support + +FastMCP is an async-first framework that seamlessly supports both asynchronous (`async def`) and synchronous (`def`) functions as tools. Async tools are preferred for I/O-bound operations to keep your server responsive. + +While synchronous tools work seamlessly in FastMCP, they can block the event loop during execution. For CPU-intensive or potentially blocking synchronous operations, consider alternative strategies. One approach is to use `anyio` (which FastMCP already uses internally) to wrap them as async functions, for example: + +```python {1, 13} +import anyio +from fastmcp import FastMCP + +mcp = FastMCP() + +def cpu_intensive_task(data: str) -> str: + # Some heavy computation that could block the event loop + return processed_data + +@mcp.tool +async def wrapped_cpu_task(data: str) -> str: + """CPU-intensive task wrapped to prevent blocking.""" + return await anyio.to_thread.run_sync(cpu_intensive_task, data) +``` + +Alternative approaches include using `asyncio.get_event_loop().run_in_executor()` or other threading techniques to manage blocking operations without impacting server responsiveness. For example, here's a recipe for using the `asyncer` library (not included in FastMCP) to create a decorator that wraps synchronous functions, courtesy of [@hsheth2](https://github.com/jlowin/fastmcp/issues/864#issuecomment-3103678258): + + +```python Decorator Recipe +import asyncer +import functools +from typing import Callable, ParamSpec, TypeVar, Awaitable + +_P = ParamSpec("_P") +_R = TypeVar("_R") + +def make_async_background(fn: Callable[_P, _R]) -> Callable[_P, Awaitable[_R]]: + @functools.wraps(fn) + async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R: + return await asyncer.asyncify(fn)(*args, **kwargs) + + return wrapper +``` + +```python Using the Decorator {6} +from fastmcp import FastMCP + +mcp = FastMCP() + +@mcp.tool() +@make_async_background +def my_tool() -> None: + time.sleep(5) +``` + + +## Arguments + +By default, FastMCP converts Python functions into MCP tools by inspecting the function's signature and type annotations. This allows you to use standard Python type annotations for your tools. In general, the framework strives to "just work": idiomatic Python behaviors like parameter defaults and type annotations are automatically translated into MCP schemas. However, there are a number of ways to customize the behavior of your tools. + + +FastMCP automatically dereferences `$ref` entries in tool schemas to ensure compatibility with MCP clients that don't fully support JSON Schema references (e.g., VS Code Copilot, Claude Desktop). This means complex Pydantic models with shared types are inlined in the schema rather than using `$defs` references. + + +### Type Annotations + +MCP tools have typed arguments, and FastMCP uses type annotations to determine those types. Therefore, you should use standard Python type annotations for tool arguments: + +```python +@mcp.tool +def analyze_text( + text: str, + max_tokens: int = 100, + language: str | None = None +) -> dict: + """Analyze the provided text.""" + # Implementation... +``` + +FastMCP supports a wide range of type annotations, including all Pydantic types: + +| Type Annotation | Example | Description | +| :---------------------- | :---------------------------- | :---------------------------------- | +| Basic types | `int`, `float`, `str`, `bool` | Simple scalar values | +| Binary data | `bytes` | Binary content (raw strings, not auto-decoded base64) | +| Date and Time | `datetime`, `date`, `timedelta` | Date and time objects (ISO format strings) | +| Collection types | `list[str]`, `dict[str, int]`, `set[int]` | Collections of items | +| Optional types | `float \| None`, `Optional[float]`| Parameters that may be null/omitted | +| Union types | `str \| int`, `Union[str, int]`| Parameters accepting multiple types | +| Constrained types | `Literal["A", "B"]`, `Enum` | Parameters with specific allowed values | +| Paths | `Path` | File system paths (auto-converted from strings) | +| UUIDs | `UUID` | Universally unique identifiers (auto-converted from strings) | +| Pydantic models | `UserData` | Complex structured data with validation | + +FastMCP supports all types that Pydantic supports as fields, including all Pydantic custom types. A few FastMCP-specific behaviors to note: + +**Binary Data**: `bytes` parameters accept raw strings without automatic base64 decoding. For base64 data, use `str` and decode manually with `base64.b64decode()`. + +**Enums**: Clients send enum values (`"red"`), not names (`"RED"`). Your function receives the Enum member (`Color.RED`). + +**Paths and UUIDs**: String inputs are automatically converted to `Path` and `UUID` objects. + +**Pydantic Models**: Must be provided as JSON objects (dicts), not stringified JSON. Even with flexible validation, `{"user": {"name": "Alice"}}` works, but `{"user": '{"name": "Alice"}'}` does not. + +### Optional Arguments + +FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional. + +```python +@mcp.tool +def search_products( + query: str, # Required - no default value + max_results: int = 10, # Optional - has default value + sort_by: str = "relevance", # Optional - has default value + category: str | None = None # Optional - can be None +) -> list[dict]: + """Search the product catalog.""" + # Implementation... +``` + +In this example, the LLM must provide a `query` parameter, while `max_results`, `sort_by`, and `category` will use their default values if not explicitly provided. + +### Validation Modes + + + +By default, FastMCP uses Pydantic's flexible validation that coerces compatible inputs to match your type annotations. This improves compatibility with LLM clients that may send string representations of values (like `"10"` for an integer parameter). + +If you need stricter validation that rejects any type mismatches, you can enable strict input validation. Strict mode uses the MCP SDK's built-in JSON Schema validation to validate inputs against the exact schema before passing them to your function: + +```python +# Enable strict validation for this server +mcp = FastMCP("StrictServer", strict_input_validation=True) + +@mcp.tool +def add_numbers(a: int, b: int) -> int: + """Add two numbers.""" + return a + b + +# With strict_input_validation=True, sending {"a": "10", "b": "20"} will fail +# With strict_input_validation=False (default), it will be coerced to integers +``` + +**Validation Behavior Comparison:** + +| Input Type | strict_input_validation=False (default) | strict_input_validation=True | +| :--------- | :-------------------------------------- | :--------------------------- | +| String integers (`"10"` for `int`) | ✅ Coerced to integer | ❌ Validation error | +| String floats (`"3.14"` for `float`) | ✅ Coerced to float | ❌ Validation error | +| String booleans (`"true"` for `bool`) | ✅ Coerced to boolean | ❌ Validation error | +| Lists with string elements (`["1", "2"]` for `list[int]`) | ✅ Elements coerced | ❌ Validation error | +| Pydantic model fields with type mismatches | ✅ Fields coerced | ❌ Validation error | +| Invalid values (`"abc"` for `int`) | ❌ Validation error | ❌ Validation error | + + +**Note on Pydantic Models:** Even with `strict_input_validation=False`, Pydantic model parameters must be provided as JSON objects (dicts), not as stringified JSON. For example, `{"user": {"name": "Alice"}}` works, but `{"user": '{"name": "Alice"}'}` does not. + + +The default flexible validation mode is recommended for most use cases as it handles common LLM client behaviors gracefully while still providing strong type safety through Pydantic's validation. + +### Parameter Metadata + +You can provide additional metadata about parameters in several ways: + +#### Simple String Descriptions + + + +For basic parameter descriptions, you can use a convenient shorthand with `Annotated`: + +```python +from typing import Annotated + +@mcp.tool +def process_image( + image_url: Annotated[str, "URL of the image to process"], + resize: Annotated[bool, "Whether to resize the image"] = False, + width: Annotated[int, "Target width in pixels"] = 800, + format: Annotated[str, "Output image format"] = "jpeg" +) -> dict: + """Process an image with optional resizing.""" + # Implementation... +``` + +This shorthand syntax is equivalent to using `Field(description=...)` but more concise for simple descriptions. + + +This shorthand syntax is only applied to `Annotated` types with a single string description. + + +#### Advanced Metadata with Field + +For validation constraints and advanced metadata, use Pydantic's `Field` class with `Annotated`: + +```python +from typing import Annotated +from pydantic import Field + +@mcp.tool +def process_image( + image_url: Annotated[str, Field(description="URL of the image to process")], + resize: Annotated[bool, Field(description="Whether to resize the image")] = False, + width: Annotated[int, Field(description="Target width in pixels", ge=1, le=2000)] = 800, + format: Annotated[ + Literal["jpeg", "png", "webp"], + Field(description="Output image format") + ] = "jpeg" +) -> dict: + """Process an image with optional resizing.""" + # Implementation... +``` + + +You can also use the Field as a default value, though the Annotated approach is preferred: + +```python +@mcp.tool +def search_database( + query: str = Field(description="Search query string"), + limit: int = Field(10, description="Maximum number of results", ge=1, le=100) +) -> list: + """Search the database with the provided query.""" + # Implementation... +``` + +Field provides several validation and documentation features: +- `description`: Human-readable explanation of the parameter (shown to LLMs) +- `ge`/`gt`/`le`/`lt`: Greater/less than (or equal) constraints +- `min_length`/`max_length`: String or collection length constraints +- `pattern`: Regex pattern for string validation +- `default`: Default value if parameter is omitted + +### Hiding Parameters from the LLM + + + +To inject values at runtime without exposing them to the LLM (such as `user_id`, credentials, or database connections), use dependency injection with `Depends()`. Parameters using `Depends()` are automatically excluded from the tool schema: + +```python +from fastmcp import FastMCP +from fastmcp.dependencies import Depends + +mcp = FastMCP() + +def get_user_id() -> str: + return "user_123" # Injected at runtime + +@mcp.tool +def get_user_details(user_id: str = Depends(get_user_id)) -> str: + # user_id is injected by the server, not provided by the LLM + return f"Details for {user_id}" +``` + +See [Custom Dependencies](/servers/context#custom-dependencies) for more details on dependency injection. + +## Return Values + + +FastMCP tools can return data in two complementary formats: **traditional content blocks** (like text and images) and **structured outputs** (machine-readable JSON). When you add return type annotations, FastMCP automatically generates **output schemas** to validate the structured data and enables clients to deserialize results back to Python objects. + +Understanding how these three concepts work together: + +- **Return Values**: What your Python function returns (determines both content blocks and structured data) +- **Structured Outputs**: JSON data sent alongside traditional content for machine processing +- **Output Schemas**: JSON Schema declarations that describe and validate the structured output format + +The following sections explain each concept in detail. + +### Content Blocks + +FastMCP automatically converts tool return values into appropriate MCP content blocks: + +- **`str`**: Sent as `TextContent` +- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (within an `EmbeddedResource`) +- **`fastmcp.utilities.types.Image`**: Sent as `ImageContent` +- **`fastmcp.utilities.types.Audio`**: Sent as `AudioContent` +- **`fastmcp.utilities.types.File`**: Sent as base64-encoded `EmbeddedResource` +- **MCP SDK content blocks**: Sent as-is +- **A list of any of the above**: Converts each item according to the above rules +- **`None`**: Results in an empty response + +#### Media Helper Classes + +FastMCP provides helper classes for returning images, audio, and files. When you return one of these classes, either directly or as part of a list, FastMCP automatically converts it to the appropriate MCP content block. For example, if you return a `fastmcp.utilities.types.Image` object, FastMCP will convert it to an MCP `ImageContent` block with the correct MIME type and base64 encoding. + +```python +from fastmcp.utilities.types import Image, Audio, File + +@mcp.tool +def get_chart() -> Image: + """Generate a chart image.""" + return Image(path="chart.png") + +@mcp.tool +def get_multiple_charts() -> list[Image]: + """Return multiple charts.""" + return [Image(path="chart1.png"), Image(path="chart2.png")] +``` + + +Helper classes are only automatically converted to MCP content blocks when returned **directly** or as part of a **list**. For more complex containers like dicts, you can manually convert them to MCP types: + +```python +# ✅ Automatic conversion +return Image(path="chart.png") +return [Image(path="chart1.png"), "text content"] + +# ❌ Will not be automatically converted +return {"image": Image(path="chart.png")} + +# ✅ Manual conversion for nested use +return {"image": Image(path="chart.png").to_image_content()} +``` + + +Each helper class accepts either `path=` or `data=` (mutually exclusive): +- **`path`**: File path (string or Path object) - MIME type detected from extension +- **`data`**: Raw bytes - requires `format=` parameter for MIME type +- **`format`**: Optional format override (e.g., "png", "wav", "pdf") +- **`name`**: Optional name for `File` when using `data=` +- **`annotations`**: Optional MCP annotations for the content + +### Structured Output + + + +The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content) structured content, which is a new way to return data from tools. Structured content is a JSON object that is sent alongside traditional content. FastMCP automatically creates structured outputs alongside traditional content when your tool returns data that has a JSON object representation. This provides machine-readable JSON data that clients can deserialize back to Python objects. + +**Automatic Structured Content Rules:** +- **Object-like results** (`dict`, Pydantic models, dataclasses) → Always become structured content (even without output schema) +- **Non-object results** (`int`, `str`, `list`) → Only become structured content if there's an output schema to validate/serialize them +- **All results** → Always become traditional content blocks for backward compatibility + + +This automatic behavior enables clients to receive machine-readable data alongside human-readable content without requiring explicit output schemas for object-like returns. + + +#### Dictionaries and Objects + +When your tool returns a dictionary, dataclass, or Pydantic model, FastMCP automatically creates structured content from it. The structured content contains the actual object data, making it easy for clients to deserialize back to native objects. + + +```python Tool Definition +@mcp.tool +def get_user_data(user_id: str) -> dict: + """Get user data.""" + return {"name": "Alice", "age": 30, "active": True} +``` + +```json MCP Result +{ + "content": [ + { + "type": "text", + "text": "{\n \"name\": \"Alice\",\n \"age\": 30,\n \"active\": true\n}" + } + ], + "structuredContent": { + "name": "Alice", + "age": 30, + "active": true + } +} +``` + + +#### Primitives and Collections + +When your tool returns a primitive type (int, str, bool) or a collection (list, set), FastMCP needs a return type annotation to generate structured content. The annotation tells FastMCP how to validate and serialize the result. + +Without a type annotation, the tool only produces `content`: + + +```python Tool Definition +@mcp.tool +def calculate_sum(a: int, b: int): + """Calculate sum without return annotation.""" + return a + b # Returns 8 +``` + +```json MCP Result +{ + "content": [ + { + "type": "text", + "text": "8" + } + ] +} +``` + + +When you add a return annotation, such as `-> int`, FastMCP generates `structuredContent` by wrapping the primitive value in a `{"result": ...}` object, since JSON schemas require object-type roots for structured output: + + +```python Tool Definition +@mcp.tool +def calculate_sum(a: int, b: int) -> int: + """Calculate sum with return annotation.""" + return a + b # Returns 8 +``` + +```json MCP Result +{ + "content": [ + { + "type": "text", + "text": "8" + } + ], + "structuredContent": { + "result": 8 + } +} +``` + + +#### Typed Models + +Return type annotations work with any type that can be converted to a JSON schema. Dataclasses and Pydantic models are particularly useful because FastMCP extracts their field definitions to create detailed schemas. + + +```python Tool Definition +from dataclasses import dataclass +from fastmcp import FastMCP + +mcp = FastMCP() + +@dataclass +class Person: + name: str + age: int + email: str + +@mcp.tool +def get_user_profile(user_id: str) -> Person: + """Get a user's profile information.""" + return Person( + name="Alice", + age=30, + email="alice@example.com", + ) +``` + +```json Generated Output Schema +{ + "properties": { + "name": {"title": "Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "email": {"title": "Email", "type": "string"} + }, + "required": ["name", "age", "email"], + "title": "Person", + "type": "object" +} +``` + +```json MCP Result +{ + "content": [ + { + "type": "text", + "text": "{\"name\": \"Alice\", \"age\": 30, \"email\": \"alice@example.com\"}" + } + ], + "structuredContent": { + "name": "Alice", + "age": 30, + "email": "alice@example.com" + } +} +``` + + +The `Person` dataclass becomes an output schema (second tab) that describes the expected format. When executed, clients receive the result (third tab) with both `content` and `structuredContent` fields. + +### Output Schemas + + + +The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema) output schemas, which are a new way to describe the expected output format of a tool. When an output schema is provided, the tool *must* return structured output that matches the schema. + +When you add return type annotations to your functions, FastMCP automatically generates JSON schemas that describe the expected output format. These schemas help MCP clients understand and validate the structured data they receive. + +#### Primitive Type Wrapping + +For primitive return types (like `int`, `str`, `bool`), FastMCP automatically wraps the result under a `"result"` key to create valid structured output: + + +```python Primitive Return Type +@mcp.tool +def calculate_sum(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b +``` + +```json Generated Schema (Wrapped) +{ + "type": "object", + "properties": { + "result": {"type": "integer"} + }, + "x-fastmcp-wrap-result": true +} +``` + +```json Structured Output +{ + "result": 8 +} +``` + + +#### Manual Schema Control + +You can override the automatically generated schema by providing a custom `output_schema`: + +```python +@mcp.tool(output_schema={ + "type": "object", + "properties": { + "data": {"type": "string"}, + "metadata": {"type": "object"} + } +}) +def custom_schema_tool() -> dict: + """Tool with custom output schema.""" + return {"data": "Hello", "metadata": {"version": "1.0"}} +``` + +Schema generation works for most common types including basic types, collections, union types, Pydantic models, TypedDict structures, and dataclasses. + + +**Important Constraints**: +- Output schemas must be object types (`"type": "object"`) +- If you provide an output schema, your tool **must** return structured output that matches it +- However, you can provide structured output without an output schema (using `ToolResult`) + + +### ToolResult and Metadata + +For complete control over tool responses, return a `ToolResult` object. This gives you explicit control over all aspects of the tool's output: traditional content, structured data, and metadata. + +```python +from fastmcp.tools.tool import ToolResult +from mcp.types import TextContent + +@mcp.tool +def advanced_tool() -> ToolResult: + """Tool with full control over output.""" + return ToolResult( + content=[TextContent(type="text", text="Human-readable summary")], + structured_content={"data": "value", "count": 42}, + meta={"execution_time_ms": 145} + ) +``` + +`ToolResult` accepts three fields: + +**`content`** - The traditional MCP content blocks that clients display to users. Can be a string (automatically converted to `TextContent`), a list of MCP content blocks, or any serializable value (converted to JSON string). At least one of `content` or `structured_content` must be provided. + +```python +# Simple string +ToolResult(content="Hello, world!") + +# List of content blocks +ToolResult(content=[ + TextContent(type="text", text="Result: 42"), + ImageContent(type="image", data="base64...", mimeType="image/png") +]) +``` + +**`structured_content`** - A dictionary containing structured data that matches your tool's output schema. This enables clients to programmatically process the results. If you provide `structured_content`, it must be a dictionary or `None`. If only `structured_content` is provided, it will also be used as `content` (converted to JSON string). + +```python +ToolResult( + content="Found 3 users", + structured_content={"users": [{"name": "Alice"}, {"name": "Bob"}]} +) +``` + +**`meta`** + +Runtime metadata about the tool execution. Use this for performance metrics, debugging information, or any client-specific data that doesn't belong in the content or structured output. + +```python +ToolResult( + content="Analysis complete", + structured_content={"result": "positive"}, + meta={ + "execution_time_ms": 145, + "model_version": "2.1", + "confidence": 0.95 + } +) +``` + + +The `meta` field in `ToolResult` is for runtime metadata about tool execution (e.g., execution time, performance metrics). This is separate from the `meta` parameter in `@mcp.tool(meta={...})`, which provides static metadata about the tool definition itself. + + +When returning `ToolResult`, you have full control - FastMCP won't automatically wrap or transform your data. `ToolResult` can be returned with or without an output schema. + +## Error Handling + + + +If your tool encounters an error, you can raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.) or a FastMCP `ToolError`. + +By default, all exceptions (including their details) are logged and converted into an MCP error response to be sent back to the client LLM. This helps the LLM understand failures and react appropriately. + +If you want to mask internal error details for security reasons, you can: + +1. Use the `mask_error_details=True` parameter when creating your `FastMCP` instance: +```python +mcp = FastMCP(name="SecureServer", mask_error_details=True) +``` + +2. Or use `ToolError` to explicitly control what error information is sent to clients: +```python +from fastmcp import FastMCP +from fastmcp.exceptions import ToolError + +@mcp.tool +def divide(a: float, b: float) -> float: + """Divide a by b.""" + + if b == 0: + # Error messages from ToolError are always sent to clients, + # regardless of mask_error_details setting + raise ToolError("Division by zero is not allowed.") + + # If mask_error_details=True, this message would be masked + if not isinstance(a, (int, float)) or not isinstance(b, (int, float)): + raise TypeError("Both arguments must be numbers.") + + return a / b +``` + +When `mask_error_details=True`, only error messages from `ToolError` will include details, other exceptions will be converted to a generic message. + +## Disabling Tools + + + +You can control the visibility and availability of tools by enabling or disabling them. This is useful for feature flagging, maintenance, or dynamically changing the toolset available to a client. Disabled tools will not appear in the list of available tools returned by `list_tools`, and attempting to call a disabled tool will result in an "Unknown tool" error, just as if the tool did not exist. + +By default, all tools are enabled. You can disable a tool upon creation using the `enabled` parameter in the decorator: + +```python +@mcp.tool(enabled=False) +def maintenance_tool(): + """This tool is currently under maintenance.""" + return "This tool is disabled." +``` + +You can also toggle a tool's state programmatically after it has been created: + +```python +@mcp.tool +def dynamic_tool(): + return "I am a dynamic tool." + +# Disable and re-enable the tool +dynamic_tool.disable() +dynamic_tool.enable() +``` +## MCP Annotations + + + +FastMCP allows you to add specialized metadata to your tools through annotations. These annotations communicate how tools behave to client applications without consuming token context in LLM prompts. + +Annotations serve several purposes in client applications: +- Adding user-friendly titles for display purposes +- Indicating whether tools modify data or systems +- Describing the safety profile of tools (destructive vs. non-destructive) +- Signaling if tools interact with external systems + +You can add annotations to a tool using the `annotations` parameter in the `@mcp.tool` decorator: + +```python +@mcp.tool( + annotations={ + "title": "Calculate Sum", + "readOnlyHint": True, + "openWorldHint": False + } +) +def calculate_sum(a: float, b: float) -> float: + """Add two numbers together.""" + return a + b +``` + +FastMCP supports these standard annotations: + +| Annotation | Type | Default | Purpose | +| :--------- | :--- | :------ | :------ | +| `title` | string | - | Display name for user interfaces | +| `readOnlyHint` | boolean | false | Indicates if the tool only reads without making changes | +| `destructiveHint` | boolean | true | For non-readonly tools, signals if changes are destructive | +| `idempotentHint` | boolean | false | Indicates if repeated identical calls have the same effect as a single call | +| `openWorldHint` | boolean | true | Specifies if the tool interacts with external systems | + +Remember that annotations help make better user experiences but should be treated as advisory hints. They help client applications present appropriate UI elements and safety controls, but won't enforce security boundaries on their own. Always focus on making your annotations accurately represent what your tool actually does. + +### Using Annotation Hints + +MCP clients like Claude and ChatGPT use annotation hints to determine when to skip confirmation prompts and how to present tools to users. The most commonly used hint is `readOnlyHint`, which signals that a tool only reads data without making changes. + +**Read-only tools** improve user experience by: +- Skipping confirmation prompts for safe operations +- Allowing broader access without security concerns +- Enabling more aggressive batching and caching + +Mark a tool as read-only when it retrieves data, performs calculations, or checks status without modifying state: + +```python +from fastmcp import FastMCP +from mcp.types import ToolAnnotations + +mcp = FastMCP("Data Server") + +@mcp.tool(annotations={"readOnlyHint": True}) +def get_user(user_id: str) -> dict: + """Retrieve user information by ID.""" + return {"id": user_id, "name": "Alice"} + +@mcp.tool( + annotations=ToolAnnotations( + readOnlyHint=True, + idempotentHint=True, # Same result for repeated calls + openWorldHint=False # Only internal data + ) +) +def search_products(query: str) -> list[dict]: + """Search the product catalog.""" + return [{"id": 1, "name": "Widget", "price": 29.99}] + +# Write operations - no readOnlyHint +@mcp.tool() +def update_user(user_id: str, name: str) -> dict: + """Update user information.""" + return {"id": user_id, "name": name, "updated": True} + +@mcp.tool(annotations={"destructiveHint": True}) +def delete_user(user_id: str) -> dict: + """Permanently delete a user account.""" + return {"deleted": user_id} +``` + +For tools that write to databases, send notifications, create/update/delete resources, or trigger workflows, omit `readOnlyHint` or set it to `False`. Use `destructiveHint=True` for operations that cannot be undone. + +Client-specific behavior: +- **ChatGPT**: Skips confirmation prompts for read-only tools in Chat mode (see [ChatGPT integration](/integrations/chatgpt)) +- **Claude**: Uses hints to understand tool safety profiles and make better execution decisions + +## Notifications + + + +FastMCP automatically sends `notifications/tools/list_changed` notifications to connected clients when tools are added, removed, enabled, or disabled. This allows clients to stay up-to-date with the current tool set without manually polling for changes. + +```python +@mcp.tool +def example_tool() -> str: + return "Hello!" + +# These operations trigger notifications: +mcp.add_tool(example_tool) # Sends tools/list_changed notification +example_tool.disable() # Sends tools/list_changed notification +example_tool.enable() # Sends tools/list_changed notification +mcp.remove_tool("example_tool") # Sends tools/list_changed notification +``` + +Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications. + +Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their tool lists or update their interfaces. + +## Accessing the MCP Context + +Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`. + +```python +from fastmcp import FastMCP, Context + +mcp = FastMCP(name="ContextDemo") + +@mcp.tool +async def process_data(data_uri: str, ctx: Context) -> dict: + """Process data from a resource with progress reporting.""" + await ctx.info(f"Processing data from {data_uri}") + + # Read a resource + resource = await ctx.read_resource(data_uri) + data = resource[0].content if resource else "" + + # Report progress + await ctx.report_progress(progress=50, total=100) + + # Example request to the client's LLM for help + summary = await ctx.sample(f"Summarize this in 10 words: {data[:200]}") + + await ctx.report_progress(progress=100, total=100) + return { + "length": len(data), + "summary": summary.text + } +``` + +The Context object provides access to: + +- **Logging**: `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()` +- **Progress Reporting**: `ctx.report_progress(progress, total)` +- **Resource Access**: `ctx.read_resource(uri)` +- **LLM Sampling**: `ctx.sample(...)` +- **Request Information**: `ctx.request_id`, `ctx.client_id` + +For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context). + +## Server Behavior + +### Duplicate Tools + + + +You can control how the FastMCP server behaves if you try to register multiple tools with the same name. This is configured using the `on_duplicate_tools` argument when creating the `FastMCP` instance. + +```python +from fastmcp import FastMCP + +mcp = FastMCP( + name="StrictServer", + # Configure behavior for duplicate tool names + on_duplicate_tools="error" +) + +@mcp.tool +def my_tool(): return "Version 1" + +# This will now raise a ValueError because 'my_tool' already exists +# and on_duplicate_tools is set to "error". +# @mcp.tool +# def my_tool(): return "Version 2" +``` + +The duplicate behavior options are: + +- `"warn"` (default): Logs a warning and the new tool replaces the old one. +- `"error"`: Raises a `ValueError`, preventing the duplicate registration. +- `"replace"`: Silently replaces the existing tool with the new one. +- `"ignore"`: Keeps the original tool and ignores the new registration attempt. + +### Removing Tools + + + +You can dynamically remove tools from a server using the `remove_tool` method: + +```python +from fastmcp import FastMCP + +mcp = FastMCP(name="DynamicToolServer") + +@mcp.tool +def calculate_sum(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b + +mcp.remove_tool("calculate_sum") +``` diff --git a/docs/v2/tutorials/create-mcp-server.mdx b/docs/v2/tutorials/create-mcp-server.mdx new file mode 100644 index 000000000..de1000703 --- /dev/null +++ b/docs/v2/tutorials/create-mcp-server.mdx @@ -0,0 +1,198 @@ +--- +title: "How to Create an MCP Server in Python" +sidebarTitle: "Creating an MCP Server" +description: "A step-by-step guide to building a Model Context Protocol (MCP) server using Python and FastMCP, from basic tools to dynamic resources." +icon: server +--- + +So you want to build a Model Context Protocol (MCP) server in Python. The goal is to create a service that can provide tools and data to AI models like Claude, Gemini, or others that support the protocol. While the [MCP specification](https://modelcontextprotocol.io/specification/) is powerful, implementing it from scratch involves a lot of boilerplate: handling JSON-RPC, managing session state, and correctly formatting requests and responses. + +This is where **FastMCP** comes in. It's a high-level framework that handles all the protocol complexities for you, letting you focus on what matters: writing the Python functions that power your server. + +This guide will walk you through creating a fully-featured MCP server from scratch using FastMCP. + + +Every code block in this tutorial is a complete, runnable example. You can copy and paste it into a file and run it, or paste it directly into a Python REPL like IPython to try it out. + + +### Prerequisites + +Make sure you have FastMCP installed. If not, follow the [installation guide](/getting-started/installation). + +```bash +pip install fastmcp +``` + + +## Step 1: Create the Basic Server + +Every FastMCP application starts with an instance of the `FastMCP` class. This object acts as the container for all your tools and resources. + +Create a new file called `my_mcp_server.py`: + +```python my_mcp_server.py +from fastmcp import FastMCP + +# Create a server instance with a descriptive name +mcp = FastMCP(name="My First MCP Server") +``` + +That's it! You have a valid (though empty) MCP server. Now, let's add some functionality. + +## Step 2: Add a Tool + +Tools are functions that an LLM can execute. Let's create a simple tool that adds two numbers. + +To do this, simply write a standard Python function and decorate it with `@mcp.tool`. + +```python my_mcp_server.py {5-8} +from fastmcp import FastMCP + +mcp = FastMCP(name="My First MCP Server") + +@mcp.tool +def add(a: int, b: int) -> int: + """Adds two integer numbers together.""" + return a + b +``` + +FastMCP automatically handles the rest: +- **Tool Name:** It uses the function name (`add`) as the tool's name. +- **Description:** It uses the function's docstring as the tool's description for the LLM. +- **Schema:** It inspects the type hints (`a: int`, `b: int`) to generate a JSON schema for the inputs. + +This is the core philosophy of FastMCP: **write Python, not protocol boilerplate.** + +## Step 3: Expose Data with Resources + +Resources provide read-only data to the LLM. You can define a resource by decorating a function with `@mcp.resource`, providing a unique URI. + +Let's expose a simple configuration dictionary as a resource. + +```python my_mcp_server.py {10-13} +from fastmcp import FastMCP + +mcp = FastMCP(name="My First MCP Server") + +@mcp.tool +def add(a: int, b: int) -> int: + """Adds two integer numbers together.""" + return a + b + +@mcp.resource("resource://config") +def get_config() -> dict: + """Provides the application's configuration.""" + return {"version": "1.0", "author": "MyTeam"} +``` + +When a client requests the URI `resource://config`, FastMCP will execute the `get_config` function and return its output (serialized as JSON) to the client. The function is only called when the resource is requested, enabling lazy-loading of data. + +## Step 4: Generate Dynamic Content with Resource Templates + +Sometimes, you need to generate resources based on parameters. This is what **Resource Templates** are for. You define them using the same `@mcp.resource` decorator but with placeholders in the URI. + +Let's create a template that provides a personalized greeting. + +```python my_mcp_server.py {15-17} +from fastmcp import FastMCP + +mcp = FastMCP(name="My First MCP Server") + +@mcp.tool +def add(a: int, b: int) -> int: + """Adds two integer numbers together.""" + return a + b + +@mcp.resource("resource://config") +def get_config() -> dict: + """Provides the application's configuration.""" + return {"version": "1.0", "author": "MyTeam"} + +@mcp.resource("greetings://{name}") +def personalized_greeting(name: str) -> str: + """Generates a personalized greeting for the given name.""" + return f"Hello, {name}! Welcome to the MCP server." +``` + +Now, clients can request dynamic URIs: +- `greetings://Ford` will call `personalized_greeting(name="Ford")`. +- `greetings://Marvin` will call `personalized_greeting(name="Marvin")`. + +FastMCP automatically maps the `{name}` placeholder in the URI to the `name` parameter in your function. + +## Step 5: Run the Server + +To make your server executable, add a `__main__` block to your script that calls `mcp.run()`. + +```python my_mcp_server.py {19-20} +from fastmcp import FastMCP + +mcp = FastMCP(name="My First MCP Server") + +@mcp.tool +def add(a: int, b: int) -> int: + """Adds two integer numbers together.""" + return a + b + +@mcp.resource("resource://config") +def get_config() -> dict: + """Provides the application's configuration.""" + return {"version": "1.0", "author": "MyTeam"} + +@mcp.resource("greetings://{name}") +def personalized_greeting(name: str) -> str: + """Generates a personalized greeting for the given name.""" + return f"Hello, {name}! Welcome to the MCP server." + +if __name__ == "__main__": + mcp.run() +``` + +Now you can run your server from the command line: +```bash +python my_mcp_server.py +``` +This starts the server using the default **STDIO transport**, which is how clients like Claude Desktop communicate with local servers. To learn about other transports, like HTTP, see the [Running Your Server](/deployment/running-server) guide. + +## The Complete Server + +Here is the full code for `my_mcp_server.py` (click to expand): + +```python my_mcp_server.py [expandable] +from fastmcp import FastMCP + +# 1. Create the server +mcp = FastMCP(name="My First MCP Server") + +# 2. Add a tool +@mcp.tool +def add(a: int, b: int) -> int: + """Adds two integer numbers together.""" + return a + b + +# 3. Add a static resource +@mcp.resource("resource://config") +def get_config() -> dict: + """Provides the application's configuration.""" + return {"version": "1.0", "author": "MyTeam"} + +# 4. Add a resource template for dynamic content +@mcp.resource("greetings://{name}") +def personalized_greeting(name: str) -> str: + """Generates a personalized greeting for the given name.""" + return f"Hello, {name}! Welcome to the MCP server." + +# 5. Make the server runnable +if __name__ == "__main__": + mcp.run() +``` + +## Next Steps + +You've successfully built an MCP server! From here, you can explore more advanced topics: + +- [**Tools in Depth**](/servers/tools): Learn about asynchronous tools, error handling, and custom return types. +- [**Resources & Templates**](/servers/resources): Discover different resource types, including files and HTTP endpoints. +- [**Prompts**](/servers/prompts): Create reusable prompt templates for your LLM. +- [**Running Your Server**](/deployment/running-server): Deploy your server with different transports like HTTP. + diff --git a/docs/v2/tutorials/mcp.mdx b/docs/v2/tutorials/mcp.mdx new file mode 100644 index 000000000..fd3995fff --- /dev/null +++ b/docs/v2/tutorials/mcp.mdx @@ -0,0 +1,120 @@ +--- +title: "What is the Model Context Protocol (MCP)?" +sidebarTitle: "What is MCP?" +description: "An introduction to the core concepts of the Model Context Protocol (MCP), explaining what it is, why it's useful, and how it works." +icon: "diagram-project" +--- + +The Model Context Protocol (MCP) is an open standard designed to solve a fundamental problem in AI development: how can Large Language Models (LLMs) reliably and securely interact with external tools, data, and services? + +It's the **bridge between the probabilistic, non-deterministic world of AI and the deterministic, reliable world of your code and data.** + +While you could build a custom REST API for your LLM, MCP provides a specialized, standardized "port" for AI-native communication. Think of it as **USB-C for AI**: a single, well-defined interface for connecting any compliant LLM to any compliant tool or data source. + +This guide provides a high-level overview of the protocol itself. We'll use **FastMCP**, the leading Python framework for MCP, to illustrate the concepts with simple code examples. + +## Why Do We Need a Protocol? + +With countless APIs already in existence, the most common question is: "Why do we need another one?" + +The answer lies in **standardization**. The AI ecosystem is fragmented. Every model provider has its own way of defining and calling tools. MCP's goal is to create a common language that offers several key advantages: + +1. **Interoperability:** Build one MCP server, and it can be used by any MCP-compliant client (Claude, Gemini, OpenAI, custom agents, etc.) without custom integration code. This is the protocol's most important promise. +2. **Discoverability:** Clients can dynamically ask a server what it's capable of at runtime. They receive a structured, machine-readable "menu" of tools and resources. +3. **Security & Safety:** MCP provides a clear, sandboxed boundary. An LLM can't execute arbitrary code on your server; it can only *request* to run the specific, typed, and validated functions you explicitly expose. +4. **Composability:** You can build small, specialized MCP servers and combine them to create powerful, complex applications. + +## Core MCP Components + +An MCP server exposes its capabilities through three primary components: Tools, Resources, and Prompts. + +### Tools: Executable Actions + +Tools are functions that the LLM can ask the server to execute. They are the action-oriented part of MCP. + +In the spirit of a REST API, you can think of **Tools as being like `POST` requests.** They are used to *perform an action*, *change state*, or *trigger a side effect*, like sending an email, adding a user to a database, or making a calculation. + +With FastMCP, creating a tool is as simple as decorating a Python function. + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +# This function is now an MCP tool named "get_weather" +@mcp.tool +def get_weather(city: str) -> dict: + """Gets the current weather for a specific city.""" + # In a real app, this would call a weather API + return {"city": city, "temperature": "72F", "forecast": "Sunny"} +``` + +[**Learn more about Tools →**](/servers/tools) + +### Resources: Read-Only Data + +Resources are data sources that the LLM can read. They are used to load information into the LLM's context, providing it with knowledge it doesn't have from its training data. + +Following the REST API analogy, **Resources are like `GET` requests.** Their purpose is to *retrieve information* idempotently, ideally without causing side effects. A resource can be anything from a static text file to a dynamic piece of data from a database. Each resource is identified by a unique URI. + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +# This function provides a resource at the URI "system://status" +@mcp.resource("system://status") +def get_system_status() -> dict: + """Returns the current operational status of the service.""" + return {"status": "all systems normal"} +``` + +#### Resource Templates + +You can also create **Resource Templates** for dynamic data. A client could request `users://42/profile` to get the profile for a specific user. + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +# This template provides user data for any given user ID +@mcp.resource("users://{user_id}/profile") +def get_user_profile(user_id: str) -> dict: + """Returns the profile for a specific user.""" + # Fetch user from a database... + return {"id": user_id, "name": "Zaphod Beeblebrox"} +``` + +[**Learn more about Resources & Templates →**](/servers/resources) + +### Prompts: Reusable Instructions + +Prompts are reusable, parameterized message templates. They provide a way to define consistent, structured instructions that a client can request to guide the LLM's behavior for a specific task. + +```python +from fastmcp import FastMCP + +mcp = FastMCP() + +@mcp.prompt +def summarize_text(text_to_summarize: str) -> str: + """Creates a prompt asking the LLM to summarize a piece of text.""" + return f""" + Please provide a concise, one-paragraph summary of the following text: + + {text_to_summarize} + """ +``` + +[**Learn more about Prompts →**](/servers/prompts) + +## Advanced Capabilities + +Beyond the core components, MCP also supports more advanced interaction patterns, such as a server requesting that the *client's* LLM generate a completion (known as **sampling**), or a server sending asynchronous **notifications** to a client. These features enable more complex, bidirectional workflows and are fully supported by FastMCP. + +## Next Steps + +Now that you understand the core concepts of the Model Context Protocol, you're ready to start building. The best place to begin is our step-by-step tutorial. + +[**Tutorial: How to Create an MCP Server in Python →**](/tutorials/create-mcp-server) diff --git a/docs/v2/tutorials/rest-api.mdx b/docs/v2/tutorials/rest-api.mdx new file mode 100644 index 000000000..a0857aca9 --- /dev/null +++ b/docs/v2/tutorials/rest-api.mdx @@ -0,0 +1,203 @@ +--- +title: "How to Connect an LLM to a REST API" +sidebarTitle: "Connect LLMs to REST APIs" +description: "A step-by-step guide to making any REST API with an OpenAPI spec available to LLMs using FastMCP." +icon: "plug" +--- + +You've built a powerful REST API, and now you want your LLM to be able to use it. Manually writing a wrapper function for every single endpoint is tedious, error-prone, and hard to maintain. + +This is where **FastMCP** shines. If your API has an OpenAPI (or Swagger) specification, FastMCP can automatically convert your entire API into a fully-featured MCP server, making every endpoint available as a secure, typed tool for your AI model. + +This guide will walk you through converting a public REST API into an MCP server in just a few lines of code. + + +Every code block in this tutorial is a complete, runnable example. You can copy and paste it into a file and run it, or paste it directly into a Python REPL like IPython to try it out. + + +### Prerequisites + +Make sure you have FastMCP installed. If not, follow the [installation guide](/getting-started/installation). + +```bash +pip install fastmcp +``` + +## Step 1: Choose a Target API + +For this tutorial, we'll use the [JSONPlaceholder API](https://jsonplaceholder.typicode.com/), a free, fake online REST API for testing and prototyping. It's perfect because it's simple and has a public OpenAPI specification. + +- **API Base URL:** `https://jsonplaceholder.typicode.com` +- **OpenAPI Spec URL:** We'll use a community-provided spec for it. + +## Step 2: Create the MCP Server + +Now for the magic. We'll use `FastMCP.from_openapi`. This method takes an `httpx.AsyncClient` configured for your API and its OpenAPI specification, and automatically converts **every endpoint** into a callable MCP `Tool`. + + +Learn more about working with OpenAPI specs in the [OpenAPI integration docs](/integrations/openapi). + + + +For this tutorial, we'll use a simplified OpenAPI spec directly in the code. In a real project, you would typically load the spec from a URL or local file. + + +Create a file named `api_server.py`: + +```python api_server.py {31-35} +import httpx +from fastmcp import FastMCP + +# Create an HTTP client for the target API +client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com") + +# Define a simplified OpenAPI spec for JSONPlaceholder +openapi_spec = { + "openapi": "3.0.0", + "info": {"title": "JSONPlaceholder API", "version": "1.0"}, + "paths": { + "/users": { + "get": { + "summary": "Get all users", + "operationId": "get_users", + "responses": {"200": {"description": "A list of users."}} + } + }, + "/users/{id}": { + "get": { + "summary": "Get a user by ID", + "operationId": "get_user_by_id", + "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], + "responses": {"200": {"description": "A single user."}} + } + } + } +} + +# Create the MCP server from the OpenAPI spec +mcp = FastMCP.from_openapi( + openapi_spec=openapi_spec, + client=client, + name="JSONPlaceholder MCP Server" +) + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` + +And that's it! With just a few lines of code, you've created an MCP server that exposes the entire JSONPlaceholder API as a collection of tools. + +## Step 3: Test the Generated Server + +Let's verify that our new MCP server works. We can use the `fastmcp.Client` to connect to it and inspect its tools. + + +Learn more about the FastMCP client in the [client docs](/clients/client). + + +Create a separate file, `api_client.py`: + +```python api_client.py {2, 6, 9, 16} +import asyncio +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: + + # List the tools that were automatically generated + tools = await client.list_tools() + print("Generated Tools:") + for tool in tools: + print(f"- {tool.name}") + + # Call one of the generated tools + print("\n\nCalling tool 'get_user_by_id'...") + user = await client.call_tool("get_user_by_id", {"id": 1}) + print(f"Result:\n{user.data}") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +First, run your server: +```bash +python api_server.py +``` + +Then, in another terminal, run the client: +```bash +python api_client.py +``` + +You should see a list of generated tools (`get_users`, `get_user_by_id`) and the result of calling the `get_user_by_id` tool, which fetches data from the live JSONPlaceholder API. + +![](/assets/images/tutorial-rest-api-result.png) + + +## Step 4: Customizing Route Maps + +By default, FastMCP converts every API endpoint into an MCP `Tool`. This ensures maximum compatibility with contemporary LLM clients, many of which **only support the `tools` part of the MCP specification.** + +However, for clients that support the full MCP spec, representing `GET` requests as `Resources` can be more semantically correct and efficient. + +FastMCP allows users to customize this behavior using the concept of "route maps". A `RouteMap` is a mapping of an API route to an MCP type. FastMCP checks each API route against your custom maps in order. If a route matches a map, it's converted to the specified `mcp_type`. Any route that doesn't match your custom maps will fall back to the default behavior (becoming a `Tool`). + + +Learn more about route maps in the [OpenAPI integration docs](/integrations/openapi#route-mapping). + + +Here’s how you can add custom route maps to turn `GET` requests into `Resources` and `ResourceTemplates` (if they have path parameters): + +```python api_server_with_resources.py {3, 37-42} +import httpx +from fastmcp import FastMCP +from fastmcp.server.openapi import RouteMap, MCPType + + +# Create an HTTP client for the target API +client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com") + +# Define a simplified OpenAPI spec for JSONPlaceholder +openapi_spec = { + "openapi": "3.0.0", + "info": {"title": "JSONPlaceholder API", "version": "1.0"}, + "paths": { + "/users": { + "get": { + "summary": "Get all users", + "operationId": "get_users", + "responses": {"200": {"description": "A list of users."}} + } + }, + "/users/{id}": { + "get": { + "summary": "Get a user by ID", + "operationId": "get_user_by_id", + "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], + "responses": {"200": {"description": "A single user."}} + } + } + } +} + +# Create the MCP server with custom route mapping +mcp = FastMCP.from_openapi( + openapi_spec=openapi_spec, + client=client, + name="JSONPlaceholder MCP Server", + route_maps=[ + # Map GET requests with path parameters (e.g., /users/{id}) to ResourceTemplate + RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE), + # Map all other GET requests to Resource + RouteMap(methods=["GET"], mcp_type=MCPType.RESOURCE), + ] +) + +if __name__ == "__main__": + mcp.run(transport="http", port=8000) +``` +With this configuration: +- `GET /users/{id}` becomes a `ResourceTemplate`. +- `GET /users` becomes a `Resource`. +- Any `POST`, `PUT`, etc. endpoints would still become `Tools` by default. \ No newline at end of file diff --git a/docs/v2/updates.mdx b/docs/v2/updates.mdx new file mode 100644 index 000000000..021ac2357 --- /dev/null +++ b/docs/v2/updates.mdx @@ -0,0 +1,405 @@ +--- +title: "FastMCP Updates" +sidebarTitle: "Updates" +icon: "sparkles" +tag: NEW +--- + + + +Sometimes five seconds just isn't enough. This release fixes an HTTP transport bug that was cutting connections short, along with OAuth and Redis fixes, better ASGI support, and CLI update notifications so you never miss a beat. + + + + + +FastMCP 2.14.2 brings a wave of community contributions safely into the 2.x line. A variety of important fixes backported from 3.0 work improve OpenAPI 3.1 compatibility, MCP spec compliance for output schemas and elicitation, and correct a subtle base_url fallback issue. The CLI now gently reminds you that FastMCP 3.0 is on the horizon. + + + + + +FastMCP 2.14.1 introduces sampling with tools (SEP-1577), enabling servers to pass tools to `ctx.sample()` for agentic workflows where the LLM can automatically execute tool calls in a loop. + +🤖 **Sampling with tools** lets servers leverage client LLM capabilities for multi-step agentic workflows. The new `ctx.sample_step()` method provides single LLM calls with tool inspection, while `result_type` enables structured outputs via validated Pydantic models. + +🔧 **AnthropicSamplingHandler** joins the existing OpenAI handler, and both are now promoted from experimental to production-ready status with a unified API. + + + + + +FastMCP 2.14 begins adopting the MCP 2025-11-25 specification, introducing protocol-native background tasks that enable long-running operations to report progress without blocking clients. + +⏳ **Background Tasks (SEP-1686)** let you add `task=True` to any async tool decorator. Powered by [Docket](https://github.com/chrisguidry/docket) for enterprise task scheduling—in-memory backends work out-of-the-box, Redis enables persistence and horizontal scaling. + +🔧 **OpenAPI Parser Promoted** from experimental to standard with improved performance through single-pass schema processing. + +📋 **MCP Spec Updates** including SSE polling (SEP-1699), multi-select elicitation (SEP-1330), and tool name validation (SEP-986). Also removes deprecated APIs accumulated across 2.x. + + + + + +Pins `mcp<1.23` as a precaution due to MCP SDK changes related to the 11/25/25 protocol update that break certain FastMCP patches and workarounds. FastMCP 2.14 introduces proper support for the updated protocol. + + + + + +Polishes the authentication stack with improvements to token refresh, scope handling, and multi-instance deployments. + +🎮 **Discord OAuth provider** added as a built-in authentication option. + +🔄 **Token refresh fixes** for Azure and Google providers, plus OAuth proxy improvements for multi-instance deployments. + +🎨 **Icon support** added to proxy classes for richer UX. + + + + + +Introduces meta parameter support for `ToolResult`, enabling tools to return supplementary metadata alongside results for patterns like OpenAI's Apps SDK. + +🏷️ **Meta parameters** let tools return supplementary metadata alongside results. + +🔐 **New auth providers** for OCI and Supabase, plus custom token verifiers with DebugTokenVerifier for development. + +🔒 **Security fixes** for CVE-2025-61920 and safer Cursor deeplink URL validation on Windows. + + + + + +FastMCP 2.13 "Cache Me If You Can" represents a fundamental maturation of the framework. After months of community feedback on authentication and state management, this release delivers the infrastructure FastMCP needs to handle production workloads: persistent storage, response caching, and pragmatic OAuth improvements that reflect real-world deployment challenges. + +💾 **Pluggable storage backends** bring persistent state to FastMCP servers. Built on [py-key-value-aio](https://github.com/strawgate/py-key-value), a new library from FastMCP maintainer Bill Easton ([@strawgate](https://github.com/strawgate)), the storage layer provides encrypted disk storage by default, platform-aware token management, and a simple key-value interface for application state. We're excited to bring this elegantly designed library into the FastMCP ecosystem - it's both powerful and remarkably easy to use, including wrappers to add encryption, TTLs, caching, and more to backends ranging from Elasticsearch, Redis, DynamoDB, filesystem, in-memory, and more! + +🔐 **OAuth maturity** brings months of production learnings into the framework. The new consent screen prevents confused deputy and authorization bypass attacks discovered in earlier versions, while the OAuth proxy now issues its own tokens with automatic key derivation. RFC 7662 token introspection support enables enterprise auth flows, and path prefix mounting enables OAuth-protected servers to integrate into existing web applications. FastMCP now supports out-of-the-box authentication with [WorkOS](https://gofastmcp.com/integrations/workos) and [AuthKit](https://gofastmcp.com/integrations/authkit), [GitHub](https://gofastmcp.com/integrations/github), [Google](https://gofastmcp.com/integrations/google), [Azure](https://gofastmcp.com/integrations/azure) (Entra ID), [AWS Cognito](https://gofastmcp.com/integrations/aws-cognito), [Auth0](https://gofastmcp.com/integrations/auth0), [Descope](https://gofastmcp.com/integrations/descope), [Scalekit](https://gofastmcp.com/integrations/scalekit), [JWTs](https://gofastmcp.com/servers/auth/token-verification#jwt-token-verification), and [RFC 7662 token introspection](https://gofastmcp.com/servers/auth/token-verification#token-introspection-protocol). + +⚡ **Response Caching Middleware** dramatically improves performance for expensive operations, while **Server lifespans** provide proper initialization and cleanup hooks that run once per server instance instead of per client session. + +✨ **Developer experience improvements** include Pydantic input validation, icon support, RFC 6570 query parameters for resource templates, improved Context API methods, and async file/directory resources. + + + + + +Pins MCP SDK version below 1.17 to ensure the `.well-known` payload appears in the expected location when using FastMCP auth providers with composite applications. + + + + + +FastMCP 2.12.4 adds comprehensive OIDC support and expands authentication options with AWS Cognito and Descope providers. The release also includes improvements to logging middleware, URL handling for nested resources, persistent OAuth client registration storage, and various fixes to the experimental OpenAPI parser. + +🔐 **OIDC Configuration** brings native support for OpenID Connect, enabling seamless integration with enterprise identity providers. + +🏢 **Enterprise Authentication** expands with AWS Cognito and Descope providers, broadening the authentication ecosystem. + +🛠️ **Improved Reliability** through enhanced URL handling, persistent OAuth storage, and numerous parser fixes based on community feedback. + + + + + +FastMCP 2.12.3 focuses on performance and developer experience improvements. This release includes optimized auth provider imports that reduce server startup time, enhanced OIDC authentication flows, and automatic inline snapshot creation for testing. + + + + + +Hotfix for streamable-http transport validation in fastmcp.json configuration files, resolving a parsing error when CLI arguments were merged against the configuration spec. + + + + + +FastMCP 2.12.1 strengthens OAuth proxy implementation with improved client storage reliability, PKCE forwarding, configurable token endpoint authentication methods, and expanded scope handling based on extensive community testing. + + + + + +FastMCP 2.12 represents one of our most significant releases to date. After extensive testing and iteration with the community, we're shipping major improvements to authentication, configuration, and MCP feature adoption. + +🔐 **OAuth Proxy** bridges the gap for providers that don't support Dynamic Client Registration, enabling authentication with GitHub, Google, WorkOS, and Azure through minimal configuration. + +📋 **Declarative JSON Configuration** introduces `fastmcp.json` as the single source of truth for server settings, making MCP servers as portable and shareable as container images. + +🧠 **Sampling API Fallback** tackles adoption challenges by letting servers generate completions server-side when clients don't support the feature, encouraging innovation while maintaining compatibility. + + + + + +FastMCP 2.11 brings enterprise-ready authentication and dramatic performance improvements. + +🔒 **Comprehensive OAuth 2.1 Support** with WorkOS AuthKit integration, Dynamic Client Registration, and support for separate resource and authorization servers. + +⚡ **Experimental OpenAPI Parser** delivers dramatic performance gains through single-pass schema processing and optimized memory usage (enable with environment variable). + +💾 **Enhanced State Management** provides persistent state across tool calls with a simple dictionary interface, improving context handling and type annotations. + +This release emphasizes speed and simplicity while setting the foundation for future enterprise features. + + + + + +FastMCP 2.10 achieves full compliance with the 6/18/2025 MCP specification update, introducing powerful new communication patterns. + +💬 **Elicitation Support** enables dynamic server-client communication and "human-in-the-loop" workflows, allowing servers to request additional information during execution. + +📊 **Output Schemas** provide structured outputs for tools, making results more predictable and easier to parse programmatically. + +🛠️ **Enhanced HTTP Routing** with OpenAPI extensions support and configurable algorithms for more flexible API integration. + +This release includes a breaking change to `client.call_tool()` return signatures but significantly expands the interaction capabilities of MCP servers. + + + + + +FastMCP 2.9 is a major release that, among other things, introduces two important features that push beyond the basic MCP protocol. + +🤝 *MCP Middleware* brings a flexible middleware system for intercepting and controlling server operations - think authentication, logging, rate limiting, and custom business logic without touching core protocol code. + +✨ *Server-side type conversion* for prompts solves a major developer pain point: while MCP requires string arguments, your functions can now work with native Python types like lists and dictionaries, with automatic conversion handling the complexity. + +These features transform FastMCP from a simple protocol implementation into a powerful framework for building sophisticated MCP applications. Combined with the new `File` utility for binary data and improvements to authentication and serialization, this release makes FastMCP significantly more flexible and developer-friendly while maintaining full protocol compliance. + + + + + +FastMCP 2.8 is here, and it's all about taking control of your tools. + +This release is packed with new features for curating the perfect LLM experience: + +🛠️ Tool Transformation + +The headline feature lets you wrap any tool—from your own code, a third-party library, or an OpenAPI spec—to create an enhanced, LLM-friendly version. You can rename arguments, rewrite descriptions, and hide parameters without touching the original code. + +This feature was developed in close partnership with Bill Easton. As Bill brilliantly [put it](https://www.linkedin.com/posts/williamseaston_huge-thanks-to-william-easton-for-providing-activity-7338011349525983232-Mw6T?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAAd6d0B3uL9zpCsq9eYWKi3HIvb8eN_r_Q), "Tool transformation flips Prompt Engineering on its head: stop writing tool-friendly LLM prompts and start providing LLM-friendly tools." + +🏷️ Component Control + +Now that you're transforming tools, you need a way to hide the old ones! In FastMCP 2.8 you can programmatically enable/disable any component, and for everyone who's been asking what FastMCP's tags are for—they finally have a purpose! You can now use tags to declaratively filter which components are exposed to your clients. + +🚀 Pragmatic by Default + +Lastly, to ensure maximum compatibility with the ecosystem, we've made the pragmatic decision to default all OpenAPI routes to Tools, making your entire API immediately accessible to any tool-using agent. When the industry catches up and supports resources, we'll restore the old default -- but no reason you should do extra work before OpenAI, Anthropic, or Google! + + + + + + +FastMCP 2.7 has been released! + +Most notably, it introduces the highly requested (and Pythonic) "naked" decorator usage: + +```python {3} +mcp = FastMCP() + +@mcp.tool +def add(a: int, b: int) -> int: + return a + b +``` + +In addition, decorators now return the objects they create, instead of the decorated function. This is an important usability enhancement. + +The bulk of the update is focused on improving the FastMCP internals, including a few breaking internal changes to private APIs. A number of functions that have clung on since 1.0 are now deprecated. + + + + + + + +FastMCP 2.6 is here! + +This release introduces first-class authentication for MCP servers and clients, including pragmatic Bearer token support and seamless OAuth 2.1 integration. This release aligns with how major AI platforms are adopting MCP today, making it easier than ever to securely connect your tools to real-world AI models. Dive into the update and secure your stack with minimal friction. + + + + + + +Your tests are bad and you should feel bad. + +Stop vibe-testing your MCP server through LLM guesswork. FastMCP 2.0 introduces in-memory testing for fast, deterministic, and fully Pythonic validation of your MCP logic—no network, no subprocesses, no vibes. + + + + + + + + +In just six weeks since its relaunch, FastMCP has surpassed 10,000 GitHub stars—becoming the fastest-growing OSS project in our orbit. What started as a personal itch has become the backbone of Python-based MCP servers, powering a rapidly expanding ecosystem. While the protocol itself evolves, FastMCP continues to lead with clarity, developer experience, and opinionated tooling. Here’s to what’s next. + + + + + + + +FastMCP 2.3 introduces full support for Streamable HTTP, a modern alternative to SSE that simplifies MCP deployments over the web. It’s efficient, reliable, and now the default HTTP transport. Just run your server with transport="http" and connect clients via a standard URL—FastMCP handles the rest. No special setup required. This release makes deploying MCP servers easier and more portable than ever. + + + + + + + +Even AI needs a good travel adapter 🔌 + + +FastMCP now supports proxying arbitrary MCP servers, letting you run a local FastMCP instance that transparently forwards requests to any remote or third-party server—regardless of transport. This enables transport bridging (e.g., stdio ⇄ SSE), simplified client configuration, and powerful gateway patterns. Proxies are fully composable with other FastMCP servers, letting you mount or import them just like local servers. Use `FastMCP.from_client()` to wrap any backend in a clean, Pythonic proxy. + + + + + + +This major release reimagines FastMCP as a full ecosystem platform, with powerful new features for composition, integration, and client interaction. You can now compose local and remote servers, proxy arbitrary MCP servers (with transport translation), and generate MCP servers from OpenAPI or FastAPI apps. A new client infrastructure supports advanced workflows like LLM sampling. + +FastMCP 2.0 builds on the success of v1 with a cleaner, more flexible foundation—try it out today! + + + + + + + +FastMCP 1.0 will become part of the official MCP Python SDK! + + + + + + + +Because life's too short for boilerplate. + +This is where it all started. FastMCP’s launch post introduced a clean, Pythonic way to build MCP servers without the protocol overhead. Just write functions; FastMCP handles the rest. What began as a weekend project quickly became the foundation of a growing ecosystem. + + + diff --git a/plans/03-health-endpoint.md b/plans/03-health-endpoint.md deleted file mode 100644 index d7bbdc86c..000000000 --- a/plans/03-health-endpoint.md +++ /dev/null @@ -1,387 +0,0 @@ -# Health Endpoint - -## Problem - -Production deployments need health checks for: -- Load balancers (AWS ALB, nginx, etc.) -- Kubernetes liveness/readiness probes -- Monitoring systems (Datadog, Prometheus, etc.) -- Uptime monitoring services - -Currently users must implement health endpoints themselves or rely on generic HTTP probes that don't understand server state. - -## Solution - -Built-in `/health` endpoint that returns server status and optional custom health checks. - -## API - -### Basic Usage - -```python -from fastmcp import FastMCP - -# Enable health endpoint (defaults to /health) -mcp = FastMCP("server", health_endpoint=True) -``` - -### With Configuration - -```python -from fastmcp.server.health import HealthConfig, HealthCheck - -async def check_database() -> tuple[str, bool]: - """Custom health check.""" - try: - await db.execute("SELECT 1") - return "database", True - except Exception: - return "database", False - -async def check_cache() -> tuple[str, bool]: - try: - await cache.ping() - return "cache", True - except Exception: - return "cache", False - -mcp = FastMCP( - "server", - health_endpoint=HealthConfig( - path="/health", - checks=[check_database, check_cache], - include_version=True, - include_uptime=True, - ) -) -``` - -### Readiness Endpoint - -```python -# Separate liveness (is server running?) and readiness (can it serve traffic?) -mcp = FastMCP( - "server", - health_endpoint=HealthConfig( - liveness_path="/health", - readiness_path="/ready", - readiness_checks=[check_database, check_cache], - ) -) -``` - -## Response Format - -### Healthy Response (200 OK) - -```json -{ - "status": "healthy", - "version": "3.0.0", - "uptime_seconds": 3600.5, - "timestamp": "2025-01-19T12:00:00Z", - "checks": { - "database": "ok", - "cache": "ok" - } -} -``` - -### Unhealthy Response (503 Service Unavailable) - -```json -{ - "status": "unhealthy", - "version": "3.0.0", - "uptime_seconds": 3600.5, - "timestamp": "2025-01-19T12:00:00Z", - "checks": { - "database": "failed", - "cache": "ok" - } -} -``` - -### Minimal Response - -If `checks=[]`, `include_version=False`, `include_uptime=False`: - -```json -{ - "status": "healthy" -} -``` - -## Implementation - -### Location - -- `src/fastmcp/server/health.py` - Core implementation -- `src/fastmcp/server/server.py` - Integration with FastMCP - -### HealthConfig - -```python -from dataclasses import dataclass -from collections.abc import Callable, Awaitable - -HealthCheckFn = Callable[[], Awaitable[tuple[str, bool]]] - -@dataclass -class HealthConfig: - """Configuration for health endpoints.""" - - # Liveness endpoint (is server alive?) - liveness_path: str = "/health" - - # Readiness endpoint (can server handle requests?) - readiness_path: str | None = None - - # Health checks (name, passed/failed) - checks: list[HealthCheckFn] = field(default_factory=list) - - # Checks only for readiness (not liveness) - readiness_checks: list[HealthCheckFn] = field(default_factory=list) - - # Include server version in response - include_version: bool = True - - # Include uptime in response - include_uptime: bool = True - - # Timeout for each check - check_timeout: float = 5.0 -``` - -### Health Endpoint Handler - -```python -class HealthHandler: - def __init__(self, config: HealthConfig, server: FastMCP): - self.config = config - self.server = server - self.start_time = time.time() - - async def liveness(self, request: Request) -> Response: - """Liveness check - is the server running?""" - result = { - "status": "healthy", - } - - if self.config.include_version: - result["version"] = fastmcp.__version__ - - if self.config.include_uptime: - result["uptime_seconds"] = time.time() - self.start_time - - if self.config.checks: - check_results = await self._run_checks(self.config.checks) - result["checks"] = check_results - - if any(status == "failed" for status in check_results.values()): - result["status"] = "unhealthy" - return JSONResponse(result, status_code=503) - - result["timestamp"] = datetime.now(timezone.utc).isoformat() - return JSONResponse(result) - - async def readiness(self, request: Request) -> Response: - """Readiness check - can the server handle requests?""" - # Liveness checks - liveness_checks = await self._run_checks(self.config.checks) - - # Readiness-specific checks - readiness_checks = await self._run_checks(self.config.readiness_checks) - - all_checks = {**liveness_checks, **readiness_checks} - - result = { - "status": "ready" if all(s == "ok" for s in all_checks.values()) else "not_ready", - "checks": all_checks, - } - - if self.config.include_version: - result["version"] = fastmcp.__version__ - - if self.config.include_uptime: - result["uptime_seconds"] = time.time() - self.start_time - - result["timestamp"] = datetime.now(timezone.utc).isoformat() - - status_code = 200 if result["status"] == "ready" else 503 - return JSONResponse(result, status_code=status_code) - - async def _run_checks(self, checks: list[HealthCheckFn]) -> dict[str, str]: - """Run health checks with timeout.""" - results = {} - - for check in checks: - try: - async with asyncio.timeout(self.config.check_timeout): - name, passed = await check() - results[name] = "ok" if passed else "failed" - except asyncio.TimeoutError: - results[name] = "timeout" - except Exception: - results[name] = "error" - - return results -``` - -### Integration with FastMCP - -In `server.py`: - -```python -class FastMCP: - def __init__( - self, - name: str, - *, - health_endpoint: bool | HealthConfig = False, - **kwargs, - ): - self._health_handler: HealthHandler | None = None - - if health_endpoint: - config = health_endpoint if isinstance(health_endpoint, HealthConfig) else HealthConfig() - self._health_handler = HealthHandler(config, self) - - async def handle_http_request(self, request: Request) -> Response: - """Route HTTP requests.""" - path = request.url.path - - # Health endpoints - if self._health_handler: - if path == self._health_handler.config.liveness_path: - return await self._health_handler.liveness(request) - - if self._health_handler.config.readiness_path and path == self._health_handler.config.readiness_path: - return await self._health_handler.readiness(request) - - # Normal MCP request handling - return await self._handle_mcp_request(request) -``` - -## Edge Cases - -1. **stdio transport** - Health endpoints only work for HTTP transports. Ignore for stdio. - -2. **Startup time** - Readiness checks might fail during startup (DB connecting, etc.). This is correct behavior. - -3. **Check timeouts** - Individual checks should timeout independently. Don't let one slow check block others. - -4. **Check errors** - Exceptions in checks should be caught and reported as "error" status. - -5. **No checks** - If no checks configured, health endpoint just returns `{"status": "healthy"}`. - -6. **Concurrent requests** - Health checks might be called concurrently. Ensure checks are safe for concurrent execution. - -## Testing - -Add `tests/server/test_health.py`: - -```python -async def test_basic_health_endpoint(): - mcp = FastMCP("test", health_endpoint=True) - - async with httpx.AsyncClient(app=mcp.get_asgi_app()) as client: - response = await client.get("/health") - assert response.status_code == 200 - data = response.json() - assert data["status"] == "healthy" - -async def test_health_checks(): - async def good_check(): - return "good", True - - async def bad_check(): - return "bad", False - - mcp = FastMCP( - "test", - health_endpoint=HealthConfig(checks=[good_check, bad_check]) - ) - - async with httpx.AsyncClient(app=mcp.get_asgi_app()) as client: - response = await client.get("/health") - assert response.status_code == 503 - data = response.json() - assert data["status"] == "unhealthy" - assert data["checks"]["good"] == "ok" - assert data["checks"]["bad"] == "failed" - -async def test_readiness_endpoint(): - async def db_check(): - return "database", True - - mcp = FastMCP( - "test", - health_endpoint=HealthConfig( - readiness_path="/ready", - readiness_checks=[db_check] - ) - ) - - async with httpx.AsyncClient(app=mcp.get_asgi_app()) as client: - # Liveness should pass with no checks - response = await client.get("/health") - assert response.status_code == 200 - - # Readiness should pass with passing checks - response = await client.get("/ready") - assert response.status_code == 200 - assert response.json()["status"] == "ready" -``` - -## Documentation - -Add to `docs/servers/health.mdx`: - -- Why health endpoints matter -- Basic usage -- Custom health checks -- Liveness vs readiness -- Kubernetes integration examples -- AWS ALB integration examples -- Best practices for health checks - -## Examples - -### Kubernetes Integration - -```yaml -apiVersion: v1 -kind: Pod -metadata: - name: fastmcp-server -spec: - containers: - - name: server - image: my-fastmcp-server - ports: - - containerPort: 8000 - livenessProbe: - httpGet: - path: /health - port: 8000 - initialDelaySeconds: 10 - periodSeconds: 30 - readinessProbe: - httpGet: - path: /ready - port: 8000 - initialDelaySeconds: 5 - periodSeconds: 10 -``` - -### AWS ALB Target Group - -Health check configuration: -- Protocol: HTTP -- Path: `/health` -- Success codes: 200 -- Interval: 30 seconds -- Timeout: 5 seconds -- Healthy threshold: 2 -- Unhealthy threshold: 3 diff --git a/plans/04-resource-subscriptions.md b/plans/04-resource-subscriptions.md deleted file mode 100644 index a82cb8ad5..000000000 --- a/plans/04-resource-subscriptions.md +++ /dev/null @@ -1,484 +0,0 @@ -# Resource Subscriptions - -## Problem - -The MCP spec supports resource subscriptions where: -1. Client subscribes to a resource URI -2. Server notifies client when that resource changes -3. Client can then re-read the resource - -This enables real-time updates without polling. FastMCP doesn't currently implement this. - -## Solution - -Full implementation of MCP resource subscription protocol. - -## API - -### Server-side: Declaring Subscribable Resources - -```python -from fastmcp import FastMCP, Context - -mcp = FastMCP("server") - -# Option 1: Decorator parameter -@mcp.resource("data://metrics", subscribe=True) -async def get_metrics() -> dict: - return {"cpu": 45.2, "memory": 60.1} - -# Option 2: ResourceConfig -from fastmcp.resources import ResourceConfig - -@mcp.resource( - "data://metrics", - config=ResourceConfig(subscribable=True) -) -async def get_metrics() -> dict: - return {"cpu": 45.2, "memory": 60.1} -``` - -### Server-side: Notifying Subscribers - -```python -@mcp.tool -async def update_metrics(ctx: Context, cpu: float, memory: float) -> str: - global current_metrics - current_metrics = {"cpu": cpu, "memory": memory} - - # Notify all subscribers that this resource changed - await ctx.notify_resource_changed("data://metrics") - - return "Metrics updated" - -# Or notify from anywhere with access to server -async def background_updater(server: FastMCP): - while True: - await asyncio.sleep(5) - # Update data... - await server.notify_resource_changed("data://metrics") -``` - -### Client-side: Subscribing - -```python -from fastmcp import Client - -async with Client("http://server:8000/mcp") as client: - # Subscribe to a resource - await client.subscribe_resource("data://metrics") - - # Handle notifications - async for notification in client.resource_notifications(): - print(f"Resource changed: {notification.uri}") - - # Re-read the resource - updated_data = await client.read_resource(notification.uri) - print(f"New data: {updated_data}") -``` - -### Client-side: Unsubscribing - -```python -async with Client("http://server:8000/mcp") as client: - # Subscribe - await client.subscribe_resource("data://metrics") - - # Later, unsubscribe - await client.unsubscribe_resource("data://metrics") -``` - -### Client-side: List Subscriptions - -```python -async with Client("http://server:8000/mcp") as client: - await client.subscribe_resource("data://metrics") - await client.subscribe_resource("data://logs") - - # Get all active subscriptions - subs = client.list_subscriptions() - assert "data://metrics" in subs - assert "data://logs" in subs -``` - -## MCP Protocol - -### Subscribe Request - -```json -{ - "method": "resources/subscribe", - "params": { - "uri": "data://metrics" - } -} -``` - -### Subscribe Response - -```json -{ - "result": {} -} -``` - -### Unsubscribe Request - -```json -{ - "method": "resources/unsubscribe", - "params": { - "uri": "data://metrics" - } -} -``` - -### Resource Changed Notification - -Server → Client notification: - -```json -{ - "method": "notifications/resources/updated", - "params": { - "uri": "data://metrics" - } -} -``` - -## Implementation - -### Location - -- `src/fastmcp/server/subscriptions.py` - Server-side subscription tracking -- `src/fastmcp/server/context.py` - Add `notify_resource_changed()` method -- `src/fastmcp/server/server.py` - Wire up subscription handlers -- `src/fastmcp/client/client.py` - Client subscription API -- `src/fastmcp/resources/resource.py` - Add `subscribe` parameter - -### Server-side: Subscription Manager - -```python -from collections import defaultdict -from weakref import WeakSet - -class SubscriptionManager: - """Tracks resource subscriptions per session.""" - - def __init__(self): - # uri -> set of session IDs - self._subscriptions: dict[str, set[str]] = defaultdict(set) - - # session_id -> set of URIs (for cleanup) - self._session_subscriptions: dict[str, set[str]] = defaultdict(set) - - def subscribe(self, uri: str, session_id: str) -> None: - """Subscribe a session to a resource URI.""" - self._subscriptions[uri].add(session_id) - self._session_subscriptions[session_id].add(uri) - - def unsubscribe(self, uri: str, session_id: str) -> None: - """Unsubscribe a session from a resource URI.""" - self._subscriptions[uri].discard(session_id) - self._session_subscriptions[session_id].discard(uri) - - def get_subscribers(self, uri: str) -> set[str]: - """Get all session IDs subscribed to a URI.""" - return self._subscriptions.get(uri, set()).copy() - - def cleanup_session(self, session_id: str) -> None: - """Remove all subscriptions for a session.""" - for uri in self._session_subscriptions.get(session_id, set()): - self._subscriptions[uri].discard(session_id) - del self._session_subscriptions[session_id] - - def list_subscriptions(self, session_id: str) -> list[str]: - """List all URIs a session is subscribed to.""" - return list(self._session_subscriptions.get(session_id, set())) -``` - -### Server-side: Context Method - -```python -class Context: - async def notify_resource_changed(self, uri: str) -> None: - """Notify all subscribers that a resource has changed.""" - manager = self._server._subscription_manager - session_ids = manager.get_subscribers(uri) - - for session_id in session_ids: - session = self._server._get_session(session_id) - if session: - await session.send_notification( - "notifications/resources/updated", - {"uri": uri} - ) -``` - -### Server-side: Session Notification - -```python -class Session: - async def send_notification(self, method: str, params: dict) -> None: - """Send a notification to the client.""" - message = { - "jsonrpc": "2.0", - "method": method, - "params": params, - } - - if self.transport == "stdio": - await self._stdio_write(message) - elif self.transport in ("sse", "streamable-http"): - await self._http_write_notification(message) -``` - -### Server-side: Handlers - -In `server.py`: - -```python -async def handle_subscribe(self, uri: str, ctx: Context) -> dict: - """Handle resources/subscribe request.""" - # Check if resource exists and is subscribable - resource = await self.get_resource(uri) - if not resource: - raise McpError(f"Resource not found: {uri}", code=-32602) - - if not resource.subscribable: - raise McpError(f"Resource not subscribable: {uri}", code=-32602) - - # Add subscription - self._subscription_manager.subscribe(uri, ctx.session_id) - - return {} - -async def handle_unsubscribe(self, uri: str, ctx: Context) -> dict: - """Handle resources/unsubscribe request.""" - self._subscription_manager.unsubscribe(uri, ctx.session_id) - return {} -``` - -### Client-side: Subscription API - -```python -class Client: - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._subscriptions: set[str] = set() - self._notification_queue: asyncio.Queue = asyncio.Queue() - - async def subscribe_resource(self, uri: str) -> None: - """Subscribe to resource change notifications.""" - result = await self.request( - "resources/subscribe", - {"uri": uri} - ) - self._subscriptions.add(uri) - - async def unsubscribe_resource(self, uri: str) -> None: - """Unsubscribe from resource change notifications.""" - await self.request( - "resources/unsubscribe", - {"uri": uri} - ) - self._subscriptions.discard(uri) - - def list_subscriptions(self) -> list[str]: - """List all active subscriptions.""" - return list(self._subscriptions) - - async def resource_notifications(self) -> AsyncIterator[ResourceNotification]: - """Iterate over resource change notifications.""" - while True: - notification = await self._notification_queue.get() - if notification.method == "notifications/resources/updated": - yield ResourceNotification(uri=notification.params["uri"]) - - async def _handle_notification(self, notification: dict) -> None: - """Handle incoming notifications from server.""" - await self._notification_queue.put(notification) -``` - -### Resource Configuration - -In `resources/resource.py`: - -```python -@dataclass -class ResourceConfig: - subscribable: bool = False - # ... other config - -class Resource: - def __init__( - self, - uri: str, - *, - subscribe: bool = False, - config: ResourceConfig | None = None, - **kwargs - ): - if config is None: - config = ResourceConfig() - - if subscribe: - config.subscribable = True - - self.subscribable = config.subscribable -``` - -## Edge Cases - -1. **Session cleanup** - When a session ends, automatically unsubscribe all its subscriptions. - -2. **Non-subscribable resources** - Return error if client tries to subscribe to a resource that doesn't support it. - -3. **Resource doesn't exist** - Return error if subscribing to non-existent resource. - -4. **Duplicate subscriptions** - Allow same session to subscribe multiple times (idempotent). - -5. **stdio transport** - Notifications work over stdio using JSON-RPC 2.0 notification format. - -6. **HTTP transport** - For SSE, notifications are sent as events. For streamable HTTP, they're sent in the response stream. - -7. **Notification delivery failure** - If session is gone when notification fires, silently skip (already cleaned up). - -8. **Resource URI patterns** - Template URIs can be subscribable. Notify on exact URI match only. - -## Testing - -Add `tests/server/test_subscriptions.py`: - -```python -async def test_subscribe_resource(): - mcp = FastMCP("test") - - @mcp.resource("data://metrics", subscribe=True) - def metrics(): - return {"value": 42} - - async with Client(mcp) as client: - await client.subscribe_resource("data://metrics") - assert "data://metrics" in client.list_subscriptions() - -async def test_notification_delivery(): - mcp = FastMCP("test") - - @mcp.resource("data://metrics", subscribe=True) - def metrics(): - return {"value": current_value} - - @mcp.tool - async def update(ctx: Context, value: int): - global current_value - current_value = value - await ctx.notify_resource_changed("data://metrics") - - async with Client(mcp) as client: - await client.subscribe_resource("data://metrics") - - # Trigger notification - await client.call_tool("update", {"value": 100}) - - # Receive notification - notification = await asyncio.wait_for( - client.resource_notifications().__anext__(), - timeout=1.0 - ) - - assert notification.uri == "data://metrics" - -async def test_non_subscribable_resource(): - mcp = FastMCP("test") - - @mcp.resource("data://config") # No subscribe=True - def config(): - return {"key": "value"} - - async with Client(mcp) as client: - with pytest.raises(McpError, match="not subscribable"): - await client.subscribe_resource("data://config") - -async def test_session_cleanup(): - mcp = FastMCP("test") - - @mcp.resource("data://metrics", subscribe=True) - def metrics(): - return {} - - async with Client(mcp) as client1: - await client1.subscribe_resource("data://metrics") - - # Session ended, subscriptions should be cleaned up - assert len(mcp._subscription_manager._subscriptions["data://metrics"]) == 0 -``` - -## Documentation - -Add to `docs/servers/resources.mdx`: - -- Resource subscriptions overview -- Declaring subscribable resources -- Notifying subscribers -- Client subscription API -- Real-time updates pattern -- Comparison with polling - -Add example in `docs/examples/`: - -```python -# examples/subscriptions/server.py -"""Real-time metrics server with subscriptions.""" - -import asyncio -from fastmcp import FastMCP, Context - -mcp = FastMCP("Metrics Server") - -current_metrics = {"cpu": 0.0, "memory": 0.0} - -@mcp.resource("data://metrics", subscribe=True) -def get_metrics() -> dict: - return current_metrics - -async def update_metrics_loop(server: FastMCP): - """Background task that updates metrics every 5 seconds.""" - while True: - await asyncio.sleep(5) - - # Simulate metrics update - current_metrics["cpu"] = random.uniform(0, 100) - current_metrics["memory"] = random.uniform(0, 100) - - # Notify all subscribers - await server.notify_resource_changed("data://metrics") - -@mcp.lifespan -async def lifespan(server): - task = asyncio.create_task(update_metrics_loop(server)) - yield - task.cancel() -``` - -```python -# examples/subscriptions/client.py -"""Client that subscribes to real-time metrics.""" - -import asyncio -from fastmcp import Client - -async def main(): - async with Client("http://localhost:8000/mcp") as client: - # Subscribe - await client.subscribe_resource("data://metrics") - print("Subscribed to metrics") - - # Listen for updates - async for notification in client.resource_notifications(): - data = await client.read_resource(notification.uri) - print(f"Metrics updated: {data}") - -if __name__ == "__main__": - asyncio.run(main()) -``` diff --git a/plans/05-curator-provider.md b/plans/05-curator-provider.md deleted file mode 100644 index 6054db8cf..000000000 --- a/plans/05-curator-provider.md +++ /dev/null @@ -1,542 +0,0 @@ -# CuratorProvider (Search Tools) - -## Problem - -Servers with many tools overwhelm agent context windows: -- A server with 100 tools uses ~50KB+ just listing them -- Agents waste tokens on irrelevant tools -- Response quality degrades with too many options -- Users can't easily scale their servers - -Current workarounds: -- Manual tool grouping with visibility/namespacing (requires upfront design) -- Client-side filtering (not all clients support this) -- Multiple specialized servers (operational complexity) - -## Solution - -A meta-provider that indexes tools and exposes search/discovery functionality. Instead of showing 100 tools, show 1 search tool that returns the relevant subset based on natural language or keywords. - -## Modes - -### 1. Search Mode (Keyword/Semantic) - -Add a `find_tools` tool that searches over your server's tools. - -**Keyword mode**: TF-IDF or simple word matching on tool names and descriptions. - -**Semantic mode**: Embeddings-based search (requires `sentence-transformers` or OpenAI). - -### 2. Hidden Mode (Future) - -Hide the underlying tools entirely - clients only see the search tool. This drastically reduces context usage but requires clients to always search first. - -## API - -### Basic Usage (Keyword Search) - -```python -from fastmcp import FastMCP -from fastmcp.server.providers import CuratorProvider - -mcp = FastMCP("server") - -# Register many tools -@mcp.tool -def search_documents(query: str) -> list[dict]: ... - -@mcp.tool -def list_files(directory: str) -> list[str]: ... - -@mcp.tool -def delete_file(path: str) -> bool: ... - -# ... 97 more tools ... - -# Add curator - indexes all tools, adds search -mcp.add_provider(CuratorProvider( - source=mcp._provider, - mode="keyword", -)) -``` - -Now clients see 101 tools: the original 100 + `find_tools`. - -### Semantic Search Mode - -```python -from fastmcp.server.providers import CuratorProvider - -mcp.add_provider(CuratorProvider( - source=mcp._provider, - mode="semantic", - embedding_model="sentence-transformers/all-MiniLM-L6-v2", -)) -``` - -Requires `pip install sentence-transformers` (optional dependency). - -### OpenAI Embeddings - -```python -mcp.add_provider(CuratorProvider( - source=mcp._provider, - mode="semantic", - embedding_model="openai", - openai_api_key=os.environ["OPENAI_API_KEY"], -)) -``` - -### Hidden Tools Mode (Future) - -```python -mcp.add_provider(CuratorProvider( - source=mcp._provider, - mode="semantic", - hide_tools=True, # Only expose find_tools, hide originals -)) -``` - -Clients only see `find_tools`. Must search before calling anything. - -## Generated Tool - -### find_tools - -```python -def find_tools( - query: str, - limit: int = 10, -) -> list[ToolMatch]: - """Search for tools relevant to your query. - - Args: - query: Natural language description of what you want to do - limit: Maximum number of results to return - - Returns: - List of matching tools with relevance scores - """ -``` - -**Example call**: -```python -result = await client.call_tool("find_tools", { - "query": "I need to clean up old files and free disk space", - "limit": 5 -}) -``` - -**Example result**: -```json -[ - { - "name": "list_files", - "description": "List files in a directory with filters", - "relevance": 0.92, - "reasoning": "Useful for finding files to evaluate" - }, - { - "name": "get_file_age", - "description": "Get the last modified time of a file", - "relevance": 0.87, - "reasoning": "Helps identify old files" - }, - { - "name": "delete_files", - "description": "Delete files matching a pattern", - "relevance": 0.85, - "reasoning": "Performs the actual cleanup" - }, - { - "name": "get_disk_usage", - "description": "Get disk usage statistics", - "relevance": 0.78, - "reasoning": "Verify space was freed" - } -] -``` - -## Implementation - -### Location - -- `src/fastmcp/server/providers/curator.py` - Main implementation -- `src/fastmcp/server/providers/curator_keyword.py` - Keyword search -- `src/fastmcp/server/providers/curator_semantic.py` - Semantic search (optional) - -### CuratorProvider - -```python -from fastmcp.server.providers import Provider -from fastmcp.tools import Tool - -class CuratorProvider(Provider): - """Meta-provider that adds tool search capabilities.""" - - def __init__( - self, - source: Provider, - mode: Literal["keyword", "semantic"] = "keyword", - hide_tools: bool = False, - embedding_model: str | None = None, - openai_api_key: str | None = None, - ): - self.source = source - self.mode = mode - self.hide_tools = hide_tools - - # Build search index - if mode == "keyword": - self.searcher = KeywordSearcher() - else: - self.searcher = SemanticSearcher( - model=embedding_model, - openai_api_key=openai_api_key, - ) - - self._index_built = False - - async def list_tools(self) -> Sequence[Tool]: - """Return source tools + find_tools.""" - source_tools = await self.source.list_tools() - - if not self._index_built: - await self._build_index(source_tools) - self._index_built = True - - # Add find_tools - find_tool = self._create_find_tools_tool() - - if self.hide_tools: - return [find_tool] - else: - return [*source_tools, find_tool] - - async def get_tool(self, name: str) -> Tool | None: - if name == "find_tools": - return self._create_find_tools_tool() - - if self.hide_tools: - return None - - return await self.source.get_tool(name) - - async def _build_index(self, tools: Sequence[Tool]) -> None: - """Build search index from tools.""" - for tool in tools: - self.searcher.add( - name=tool.name, - description=tool.description or "", - tags=" ".join(tool.tags) if tool.tags else "", - ) - - def _create_find_tools_tool(self) -> Tool: - """Create the find_tools tool.""" - - async def find_tools_handler(query: str, limit: int = 10) -> list[dict]: - results = await self.searcher.search(query, limit=limit) - - # Fetch full tool info for each result - tools = [] - for match in results: - tool = await self.source.get_tool(match.name) - if tool: - tools.append({ - "name": tool.name, - "description": tool.description, - "relevance": match.score, - "reasoning": match.reasoning, - }) - - return tools - - return Tool.from_function( - find_tools_handler, - name="find_tools", - description="Search for tools relevant to your query", - ) -``` - -### KeywordSearcher - -```python -from collections import Counter -import math - -class KeywordSearcher: - """TF-IDF based keyword search.""" - - def __init__(self): - self.documents: dict[str, str] = {} # name -> text - self.vocab: set[str] = set() - self.idf: dict[str, float] = {} - - def add(self, name: str, description: str, tags: str = "") -> None: - """Add a tool to the index.""" - text = f"{name} {description} {tags}".lower() - self.documents[name] = text - self.vocab.update(self._tokenize(text)) - - async def search(self, query: str, limit: int = 10) -> list[ToolMatch]: - """Search for relevant tools.""" - # Build IDF if not built - if not self.idf: - self._build_idf() - - query_tokens = self._tokenize(query.lower()) - scores = {} - - for name, doc_text in self.documents.items(): - doc_tokens = self._tokenize(doc_text) - score = self._compute_score(query_tokens, doc_tokens) - scores[name] = score - - # Sort by score descending - ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) - - return [ - ToolMatch( - name=name, - score=score, - reasoning=self._generate_reasoning(name, query), - ) - for name, score in ranked[:limit] - if score > 0 - ] - - def _tokenize(self, text: str) -> list[str]: - """Simple tokenization.""" - return text.split() - - def _build_idf(self) -> None: - """Build IDF scores for vocabulary.""" - n_docs = len(self.documents) - - for word in self.vocab: - df = sum(1 for doc in self.documents.values() if word in doc) - self.idf[word] = math.log(n_docs / (1 + df)) - - def _compute_score(self, query_tokens: list[str], doc_tokens: list[str]) -> float: - """Compute TF-IDF similarity.""" - doc_counts = Counter(doc_tokens) - score = 0.0 - - for token in query_tokens: - if token in doc_counts: - tf = doc_counts[token] / len(doc_tokens) - idf = self.idf.get(token, 0) - score += tf * idf - - return score - - def _generate_reasoning(self, name: str, query: str) -> str: - """Generate simple reasoning for why tool matched.""" - # Find common words - query_words = set(self._tokenize(query.lower())) - doc_words = set(self._tokenize(self.documents[name])) - common = query_words & doc_words - - if common: - return f"Matches keywords: {', '.join(sorted(common)[:3])}" - return "Relevant based on semantic similarity" -``` - -### SemanticSearcher - -```python -from sentence_transformers import SentenceTransformer -import numpy as np - -class SemanticSearcher: - """Embeddings-based semantic search.""" - - def __init__( - self, - model: str = "sentence-transformers/all-MiniLM-L6-v2", - openai_api_key: str | None = None, - ): - if model == "openai": - self.use_openai = True - self.openai_api_key = openai_api_key - else: - self.use_openai = False - self.model = SentenceTransformer(model) - - self.documents: dict[str, str] = {} - self.embeddings: dict[str, np.ndarray] = {} - - def add(self, name: str, description: str, tags: str = "") -> None: - """Add a tool to the index.""" - text = f"{name}. {description}" - self.documents[name] = text - - # Embed immediately (could defer to first search) - if self.use_openai: - embedding = self._embed_openai(text) - else: - embedding = self.model.encode(text) - - self.embeddings[name] = embedding - - async def search(self, query: str, limit: int = 10) -> list[ToolMatch]: - """Search for relevant tools using embeddings.""" - # Embed query - if self.use_openai: - query_embedding = self._embed_openai(query) - else: - query_embedding = self.model.encode(query) - - # Compute cosine similarity - scores = {} - for name, doc_embedding in self.embeddings.items(): - similarity = np.dot(query_embedding, doc_embedding) / ( - np.linalg.norm(query_embedding) * np.linalg.norm(doc_embedding) - ) - scores[name] = float(similarity) - - # Sort by similarity - ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) - - return [ - ToolMatch( - name=name, - score=score, - reasoning=self._generate_reasoning(name, query, score), - ) - for name, score in ranked[:limit] - if score > 0.3 # Threshold - ] - - def _embed_openai(self, text: str) -> np.ndarray: - """Get embedding from OpenAI.""" - import openai - - client = openai.OpenAI(api_key=self.openai_api_key) - response = client.embeddings.create( - input=text, - model="text-embedding-3-small" - ) - return np.array(response.data[0].embedding) - - def _generate_reasoning(self, name: str, query: str, score: float) -> str: - if score > 0.8: - return "Highly relevant to your request" - elif score > 0.6: - return "Likely useful for this task" - else: - return "May be relevant" -``` - -### ToolMatch - -```python -from dataclasses import dataclass - -@dataclass -class ToolMatch: - name: str - score: float - reasoning: str -``` - -## Edge Cases - -1. **Empty index** - If source has no tools, find_tools returns empty list. - -2. **Index rebuild** - If tools change (hot reload), should we rebuild index? For now, no - index is static after first build. - -3. **Large servers** - Embedding 1000 tools at startup could be slow. Consider lazy indexing. - -4. **Query quality** - Vague queries ("do something") will return poor results. Document best practices. - -5. **Hide tools mode** - If tools are hidden, agents MUST use find_tools first. Calling a tool by name directly fails. - -6. **Concurrent searches** - Index is read-only after building, safe for concurrent access. - -## Testing - -Add `tests/server/providers/test_curator.py`: - -```python -async def test_curator_keyword_search(): - provider = LocalProvider() - - @provider.tool - def search_documents(query: str) -> list: - """Search through documents.""" - return [] - - @provider.tool - def delete_file(path: str) -> bool: - """Delete a file.""" - return True - - curator = CuratorProvider(provider, mode="keyword") - - tools = await curator.list_tools() - assert len(tools) == 3 # original 2 + find_tools - - # Call find_tools - find_tool = await curator.get_tool("find_tools") - result = await find_tool.fn(query="search for documents", limit=5) - - assert len(result) > 0 - assert result[0]["name"] == "search_documents" - assert result[0]["relevance"] > 0 - -async def test_curator_semantic_search(): - pytest.importorskip("sentence_transformers") - - provider = LocalProvider() - - @provider.tool - def find_files(pattern: str) -> list: - """Locate files matching a pattern.""" - return [] - - @provider.tool - def delete_data(id: str) -> bool: - """Remove data by ID.""" - return True - - curator = CuratorProvider(provider, mode="semantic") - - find_tool = await curator.get_tool("find_tools") - result = await find_tool.fn(query="search for files", limit=5) - - # Semantic search should match "find_files" even though words differ - assert any(r["name"] == "find_files" for r in result) -``` - -## Documentation - -Add to `docs/servers/providers/curator.mdx`: - -- Why search over tools matters -- Keyword vs semantic modes -- Setting up embeddings -- Best practices for queries -- Performance considerations -- Comparison with manual grouping - -## Dependencies - -Make `sentence-transformers` an optional dependency: - -```toml -[project.optional-dependencies] -semantic-search = [ - "sentence-transformers>=2.0.0", -] -``` - -For OpenAI mode, require `openai` (already a dependency). - -## Future Enhancements - -1. **Agent mode** - Full conversational agent instead of just search -2. **Caching** - Cache query results -3. **Multi-modal search** - Search across tools + resources + prompts -4. **Relevance feedback** - Learn from which tools users actually call -5. **Custom scoring** - Allow users to provide scoring functions diff --git a/plans/06-dynamic-resources.md b/plans/06-dynamic-resources.md deleted file mode 100644 index 391187333..000000000 --- a/plans/06-dynamic-resources.md +++ /dev/null @@ -1,628 +0,0 @@ -# Dynamic Resources - -## Problem - -Tools that return large results bloat agent context: -- Search results: 10MB of JSON -- Generated files: large CSV, PDF, images -- Log files: thousands of lines -- Database dumps: extensive data - -Current workarounds: -- Return truncated data (loses information) -- Return everything (wastes tokens) -- Write to filesystem manually (not portable, cleanup issues) - -The GitHub MCP server pioneered a pattern: write large results to a file and return a reference. Clients can then read/search the file as needed. - -## Solution - -Allow tools to create ephemeral resources at runtime that clients can read, search, and navigate without bloating context. - -## API - -### Basic Usage - -```python -from fastmcp import FastMCP, Context - -mcp = FastMCP("server") - -@mcp.tool -async def search(query: str, ctx: Context) -> str: - # Perform search - returns 10MB of data - results = do_search(query) - - # Create ephemeral resource instead of returning everything - uri = await ctx.create_resource( - f"results://search/{ctx.request_id}", - content=results, - ttl=3600, # Auto-cleanup after 1 hour - ) - - return f"Found {len(results)} results. Access at {uri}" -``` - -Client can then: -```python -result = await client.call_tool("search", {"query": "python async"}) -# "Found 1000 results. Access at results://search/abc123" - -# Read the full results -data = await client.read_resource("results://search/abc123") - -# Or use resource tools (if ResourceToolsProvider is enabled) -preview = await client.call_tool("read_resource", { - "uri": "results://search/abc123", - "limit": 10 -}) -``` - -### With Metadata - -```python -uri = await ctx.create_resource( - "results://analysis/output.json", - content={"data": [...], "summary": "..."}, - name="Analysis Results", - description="Detailed analysis output", - mime_type="application/json", - ttl=7200, # 2 hours -) -``` - -### Binary Content - -```python -@mcp.tool -async def generate_pdf(ctx: Context) -> str: - pdf_bytes = create_pdf() - - uri = await ctx.create_resource( - f"output://pdf/{ctx.request_id}.pdf", - content=pdf_bytes, - mime_type="application/pdf", - ttl=1800, - ) - - return f"PDF generated: {uri}" -``` - -### Update Resource - -```python -@mcp.tool -async def update_results(ctx: Context, uri: str, new_data: dict) -> str: - # Update existing resource - await ctx.update_resource(uri, content=new_data) - return f"Updated {uri}" -``` - -### Delete Resource - -```python -@mcp.tool -async def cleanup(ctx: Context, uri: str) -> str: - await ctx.delete_resource(uri) - return f"Deleted {uri}" -``` - -## Storage Backends - -### In-Memory (Default) - -```python -mcp = FastMCP("server") -# Uses in-memory storage - lost on restart -``` - -### Filesystem - -```python -from fastmcp.server.dynamic_resources import FilesystemStorage - -mcp = FastMCP( - "server", - dynamic_resource_storage=FilesystemStorage( - directory="/tmp/mcp-resources" - ) -) -``` - -### Redis - -```python -from fastmcp.server.dynamic_resources import RedisStorage - -mcp = FastMCP( - "server", - dynamic_resource_storage=RedisStorage( - url="redis://localhost:6379" - ) -) -``` - -### S3 - -```python -from fastmcp.server.dynamic_resources import S3Storage - -mcp = FastMCP( - "server", - dynamic_resource_storage=S3Storage( - bucket="my-mcp-resources", - prefix="dynamic/", - ) -) -``` - -## Implementation - -### Location - -- `src/fastmcp/server/dynamic_resources.py` - Core implementation -- `src/fastmcp/server/context.py` - Add context methods -- `src/fastmcp/server/server.py` - Integrate with server - -### Storage Interface - -```python -from abc import ABC, abstractmethod -from typing import Protocol - -class DynamicResourceStorage(Protocol): - """Storage backend for dynamic resources.""" - - async def write( - self, - uri: str, - content: str | bytes | dict, - *, - metadata: dict | None = None, - ttl: int | None = None, - ) -> None: - """Write a resource.""" - ... - - async def read(self, uri: str) -> tuple[bytes, dict]: - """Read a resource. Returns (content, metadata).""" - ... - - async def delete(self, uri: str) -> None: - """Delete a resource.""" - ... - - async def exists(self, uri: str) -> bool: - """Check if resource exists.""" - ... - - async def list(self, prefix: str | None = None) -> list[str]: - """List all URIs, optionally filtered by prefix.""" - ... -``` - -### In-Memory Storage - -```python -import asyncio -from datetime import datetime, timedelta - -class InMemoryStorage: - """In-memory storage with TTL support.""" - - def __init__(self): - self._data: dict[str, tuple[bytes, dict, datetime | None]] = {} - self._cleanup_task: asyncio.Task | None = None - - async def write( - self, - uri: str, - content: str | bytes | dict, - *, - metadata: dict | None = None, - ttl: int | None = None, - ) -> None: - # Serialize content - if isinstance(content, dict): - content_bytes = json.dumps(content).encode() - elif isinstance(content, str): - content_bytes = content.encode() - else: - content_bytes = content - - # Calculate expiry - expiry = None - if ttl: - expiry = datetime.now() + timedelta(seconds=ttl) - - self._data[uri] = (content_bytes, metadata or {}, expiry) - - # Start cleanup task if not running - if not self._cleanup_task: - self._cleanup_task = asyncio.create_task(self._cleanup_loop()) - - async def read(self, uri: str) -> tuple[bytes, dict]: - if uri not in self._data: - raise KeyError(f"Resource not found: {uri}") - - content, metadata, expiry = self._data[uri] - - # Check expiry - if expiry and datetime.now() > expiry: - del self._data[uri] - raise KeyError(f"Resource expired: {uri}") - - return content, metadata - - async def delete(self, uri: str) -> None: - self._data.pop(uri, None) - - async def exists(self, uri: str) -> bool: - return uri in self._data - - async def list(self, prefix: str | None = None) -> list[str]: - if prefix: - return [uri for uri in self._data if uri.startswith(prefix)] - return list(self._data.keys()) - - async def _cleanup_loop(self) -> None: - """Periodically remove expired resources.""" - while True: - await asyncio.sleep(60) # Check every minute - - now = datetime.now() - expired = [ - uri for uri, (_, _, expiry) in self._data.items() - if expiry and now > expiry - ] - - for uri in expired: - del self._data[uri] - - if not self._data: - break # Stop cleanup if empty -``` - -### Filesystem Storage - -```python -import aiofiles -import os -from pathlib import Path - -class FilesystemStorage: - """Filesystem-backed storage.""" - - def __init__(self, directory: str | Path): - self.directory = Path(directory) - self.directory.mkdir(parents=True, exist_ok=True) - - async def write( - self, - uri: str, - content: str | bytes | dict, - *, - metadata: dict | None = None, - ttl: int | None = None, - ) -> None: - # Create path from URI - path = self._uri_to_path(uri) - path.parent.mkdir(parents=True, exist_ok=True) - - # Serialize - if isinstance(content, dict): - content_bytes = json.dumps(content).encode() - elif isinstance(content, str): - content_bytes = content.encode() - else: - content_bytes = content - - # Write content - async with aiofiles.open(path, "wb") as f: - await f.write(content_bytes) - - # Write metadata - meta_path = path.with_suffix(path.suffix + ".meta") - async with aiofiles.open(meta_path, "w") as f: - meta = metadata or {} - if ttl: - meta["expires_at"] = (datetime.now() + timedelta(seconds=ttl)).isoformat() - await f.write(json.dumps(meta)) - - async def read(self, uri: str) -> tuple[bytes, dict]: - path = self._uri_to_path(uri) - - if not path.exists(): - raise KeyError(f"Resource not found: {uri}") - - # Check expiry - meta_path = path.with_suffix(path.suffix + ".meta") - if meta_path.exists(): - async with aiofiles.open(meta_path, "r") as f: - metadata = json.loads(await f.read()) - - if "expires_at" in metadata: - expiry = datetime.fromisoformat(metadata["expires_at"]) - if datetime.now() > expiry: - # Delete expired - path.unlink() - meta_path.unlink() - raise KeyError(f"Resource expired: {uri}") - else: - metadata = {} - - # Read content - async with aiofiles.open(path, "rb") as f: - content = await f.read() - - return content, metadata - - async def delete(self, uri: str) -> None: - path = self._uri_to_path(uri) - path.unlink(missing_ok=True) - path.with_suffix(path.suffix + ".meta").unlink(missing_ok=True) - - async def exists(self, uri: str) -> bool: - return self._uri_to_path(uri).exists() - - async def list(self, prefix: str | None = None) -> list[str]: - uris = [] - for path in self.directory.rglob("*"): - if path.is_file() and not path.name.endswith(".meta"): - uri = self._path_to_uri(path) - if not prefix or uri.startswith(prefix): - uris.append(uri) - return uris - - def _uri_to_path(self, uri: str) -> Path: - """Convert URI to filesystem path.""" - # Strip scheme - path_part = uri.split("://", 1)[1] if "://" in uri else uri - return self.directory / path_part - - def _path_to_uri(self, path: Path) -> str: - """Convert filesystem path to URI.""" - rel = path.relative_to(self.directory) - return f"file://{rel}" -``` - -### Context Methods - -In `context.py`: - -```python -class Context: - async def create_resource( - self, - uri: str, - content: str | bytes | dict, - *, - name: str | None = None, - description: str | None = None, - mime_type: str | None = None, - ttl: int | None = None, - ) -> str: - """Create a dynamic resource. - - Args: - uri: Resource URI - content: Resource content (string, bytes, or dict) - name: Human-readable name - description: Resource description - mime_type: MIME type - ttl: Time-to-live in seconds (default: 86400 = 1 day) - - Returns: - The URI of the created resource - """ - metadata = {} - if name: - metadata["name"] = name - if description: - metadata["description"] = description - if mime_type: - metadata["mime_type"] = mime_type - - ttl = ttl or 86400 # Default 1 day - - await self._server._dynamic_resource_storage.write( - uri, content, metadata=metadata, ttl=ttl - ) - - return uri - - async def update_resource( - self, - uri: str, - content: str | bytes | dict, - ) -> None: - """Update an existing dynamic resource.""" - # Read existing metadata - _, metadata = await self._server._dynamic_resource_storage.read(uri) - - # Write with updated content, preserve metadata - await self._server._dynamic_resource_storage.write( - uri, content, metadata=metadata - ) - - async def delete_resource(self, uri: str) -> None: - """Delete a dynamic resource.""" - await self._server._dynamic_resource_storage.delete(uri) -``` - -### Server Integration - -In `server.py`: - -```python -class FastMCP: - def __init__( - self, - name: str, - *, - dynamic_resource_storage: DynamicResourceStorage | None = None, - **kwargs - ): - self._dynamic_resource_storage = ( - dynamic_resource_storage or InMemoryStorage() - ) - - async def get_resource(self, uri: str) -> Resource | None: - """Get a resource - check dynamic storage first.""" - # Check dynamic resources - if await self._dynamic_resource_storage.exists(uri): - content, metadata = await self._dynamic_resource_storage.read(uri) - - # Create a dynamic resource wrapper - return DynamicResource( - uri=uri, - content=content, - name=metadata.get("name"), - description=metadata.get("description"), - mime_type=metadata.get("mime_type"), - ) - - # Fall back to provider resources - return await super().get_resource(uri) - - async def list_resources(self) -> list[Resource]: - """List resources - include dynamic ones.""" - # Get provider resources - provider_resources = await super().list_resources() - - # Get dynamic resources - dynamic_uris = await self._dynamic_resource_storage.list() - dynamic_resources = [] - - for uri in dynamic_uris: - _, metadata = await self._dynamic_resource_storage.read(uri) - dynamic_resources.append( - DynamicResource( - uri=uri, - name=metadata.get("name"), - description=metadata.get("description"), - ) - ) - - return [*provider_resources, *dynamic_resources] -``` - -## Edge Cases - -1. **URI collisions** - Dynamic resource URI conflicts with provider resource. Dynamic takes precedence. - -2. **TTL expiry** - Resource expires while being read. Return error, client should handle. - -3. **Large content** - 100MB+ files in memory. Use filesystem or S3 storage. - -4. **Session cleanup** - When session ends, should we delete its resources? Optional, controlled by TTL. - -5. **Concurrent writes** - Two tools write to same URI. Last write wins (no locking). - -6. **URI schemes** - Any scheme is allowed. Convention: `results://`, `output://`, `temp://`. - -7. **Storage backend failure** - If Redis is down, operations fail with clear error. - -## Testing - -Add `tests/server/test_dynamic_resources.py`: - -```python -async def test_create_resource(): - mcp = FastMCP("test") - - @mcp.tool - async def create(ctx: Context) -> str: - uri = await ctx.create_resource( - "results://test", - content={"data": [1, 2, 3]}, - ttl=60 - ) - return uri - - async with Client(mcp) as client: - uri = await client.call_tool("create", {}) - assert uri == "results://test" - - # Read it back - resource = await client.read_resource(uri) - assert resource.contents[0].text == '{"data": [1, 2, 3]}' - -async def test_resource_ttl(): - storage = InMemoryStorage() - - await storage.write("test://resource", "data", ttl=1) - - # Should exist immediately - assert await storage.exists("test://resource") - - # Wait for expiry - await asyncio.sleep(1.5) - - # Should be gone - with pytest.raises(KeyError): - await storage.read("test://resource") - -async def test_filesystem_storage(tmp_path): - storage = FilesystemStorage(tmp_path) - - await storage.write( - "output://file.json", - {"key": "value"}, - metadata={"name": "Test File"} - ) - - content, metadata = await storage.read("output://file.json") - assert json.loads(content) == {"key": "value"} - assert metadata["name"] == "Test File" - - # Check file exists - assert (tmp_path / "output" / "file.json").exists() -``` - -## Documentation - -Add to `docs/servers/dynamic-resources.mdx`: - -- Why dynamic resources matter -- Creating resources from tools -- Storage backends (memory, filesystem, Redis, S3) -- TTL and cleanup -- Best practices (when to use vs returning data) -- Integration with ResourceToolsProvider - -Add example in `docs/examples/`: - -```python -# examples/dynamic_resources/search_server.py -"""Search server that returns large results as resources.""" - -from fastmcp import FastMCP, Context - -mcp = FastMCP("Search Server") - -@mcp.tool -async def search_logs(query: str, ctx: Context) -> str: - """Search through millions of log lines.""" - results = search_engine.search(query) # Returns 10MB - - # Instead of returning all 10MB... - uri = await ctx.create_resource( - f"results://search/{ctx.request_id}", - content=results, - name=f"Search Results: {query}", - description=f"Found {len(results)} matches for '{query}'", - mime_type="application/json", - ttl=3600, - ) - - return f"Search complete. Found {len(results)} results. Access at {uri}" -``` - -## Future Enhancements - -1. **Resource pagination** - Auto-paginate large dynamic resources -2. **Compression** - Compress content before storing -3. **Access control** - Per-resource auth checks -4. **Versioning** - Keep multiple versions of same URI -5. **Search** - Full-text search over dynamic resources