Jeremiah Lowin commited on
Commit
b8f381e
·
unverified ·
1 Parent(s): 7fec089

Update docs to reflect sync tools (#1234)

Browse files
Files changed (1) hide show
  1. docs/servers/tools.mdx +55 -27
docs/servers/tools.mdx CHANGED
@@ -108,6 +108,61 @@ def search_products_implementation(query: str, category: str | None = None) -> l
108
  </Expandable>
109
  </ParamField>
110
  </Card>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  ### Tool Parameters
112
 
113
  #### Type Annotations
@@ -259,33 +314,6 @@ dynamic_tool.disable()
259
  dynamic_tool.enable()
260
  ```
261
 
262
- ### Async Tools
263
-
264
- FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as tools.
265
-
266
- ```python
267
- # Synchronous tool (suitable for CPU-bound or quick tasks)
268
- @mcp.tool
269
- def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
270
- """Calculate the distance between two coordinates."""
271
- # Implementation...
272
- return 42.5
273
-
274
- # Asynchronous tool (ideal for I/O-bound operations)
275
- @mcp.tool
276
- async def fetch_weather(city: str) -> dict:
277
- """Retrieve current weather conditions for a city."""
278
- # Use 'async def' for operations involving network calls, file I/O, etc.
279
- # This prevents blocking the server while waiting for external operations.
280
- async with aiohttp.ClientSession() as session:
281
- async with session.get(f"https://api.example.com/weather/{city}") as response:
282
- # Check response status before returning
283
- response.raise_for_status()
284
- return await response.json()
285
- ```
286
-
287
- 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.
288
-
289
  ### Return Values
290
 
291
 
 
108
  </Expandable>
109
  </ParamField>
110
  </Card>
111
+
112
+
113
+ ### Async and Synchronous Tools
114
+
115
+ 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.
116
+
117
+ 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:
118
+
119
+ ```python {1, 13}
120
+ import anyio
121
+ from fastmcp import FastMCP
122
+
123
+ mcp = FastMCP()
124
+
125
+ def cpu_intensive_task(data: str) -> str:
126
+ # Some heavy computation that could block the event loop
127
+ return processed_data
128
+
129
+ @mcp.tool
130
+ async def wrapped_cpu_task(data: str) -> str:
131
+ """CPU-intensive task wrapped to prevent blocking."""
132
+ return await anyio.to_thread.run_sync(cpu_intensive_task, data)
133
+ ```
134
+
135
+ 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):
136
+
137
+ <CodeGroup>
138
+ ```python Decorator Recipe
139
+ import asyncer
140
+ import functools
141
+ from typing import Callable, ParamSpec, TypeVar, Awaitable
142
+
143
+ _P = ParamSpec("_P")
144
+ _R = TypeVar("_R")
145
+
146
+ def make_async_background(fn: Callable[_P, _R]) -> Callable[_P, Awaitable[_R]]:
147
+ @functools.wraps(fn)
148
+ async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
149
+ return await asyncer.asyncify(fn)(*args, **kwargs)
150
+
151
+ return wrapper
152
+ ```
153
+
154
+ ```python Using the Decorator {6}
155
+ from fastmcp import FastMCP
156
+
157
+ mcp = FastMCP()
158
+
159
+ @mcp.tool()
160
+ @make_async_background
161
+ def my_tool() -> None:
162
+ time.sleep(5)
163
+ ```
164
+ </CodeGroup>
165
+
166
  ### Tool Parameters
167
 
168
  #### Type Annotations
 
314
  dynamic_tool.enable()
315
  ```
316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  ### Return Values
318
 
319