Update docs to reflect sync tools (#1234)

This commit is contained in:
Jeremiah Lowin 2025-07-22 19:04:38 -04:00 committed by GitHub
commit f4c0beca27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -108,6 +108,61 @@ def search_products_implementation(query: str, category: str | None = None) -> l
</Expandable>
</ParamField>
</Card>
### Async and Synchronous Tools
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):
<CodeGroup>
```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)
```
</CodeGroup>
### Tool Parameters
#### Type Annotations
@ -259,33 +314,6 @@ dynamic_tool.disable()
dynamic_tool.enable()
```
### Async Tools
FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as tools.
```python
# Synchronous tool (suitable for CPU-bound or quick tasks)
@mcp.tool
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate the distance between two coordinates."""
# Implementation...
return 42.5
# Asynchronous tool (ideal for I/O-bound operations)
@mcp.tool
async def fetch_weather(city: str) -> dict:
"""Retrieve current weather conditions for a city."""
# Use 'async def' for operations involving network calls, file I/O, etc.
# This prevents blocking the server while waiting for external operations.
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.example.com/weather/{city}") as response:
# Check response status before returning
response.raise_for_status()
return await response.json()
```
Use `async def` when your tool needs to perform operations that might wait for external systems (network requests, database queries, file access) to keep your server responsive.
### Return Values