diff --git a/docs/servers/tools.mdx b/docs/servers/tools.mdx index 7ba5535a4..fda4d24af 100644 --- a/docs/servers/tools.mdx +++ b/docs/servers/tools.mdx @@ -108,6 +108,61 @@ def search_products_implementation(query: str, category: str | None = None) -> l + + +### 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): + + +```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) +``` + + ### 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