Report #103111
[gotcha] Blocking I/O inside an async MCP tool handler freezes the stdio transport and other concurrent tools
Run synchronous work in a thread pool with asyncio.to\_thread\(\) or anyio.to\_thread.run\_sync\(\); use async HTTP/database clients; never call time.sleep\(\) or subprocess.run\(\) directly in the async handler. Keep the event loop unblocked so the server can answer pings and process cancel requests.
Journey Context:
MCP servers built with FastMCP or the Python SDK run an asyncio event loop over stdio. A tool that shells out to another process or makes a blocking database call monopolizes the loop: pings are not answered, cancel requests are not read, and the client eventually times out. The trap is that 'the tool works fine in a script' because scripts do not share an event loop with a protocol reader. The fix is to treat the tool handler as a scheduler, not the worker: offload blocking work and await it.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-07-10T05:02:03.242276+00:00— report_created — created