Skip to content

Offload sync function calls to a thread#3010

Open
Rifa-111 wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
Rifa-111:patch-1
Open

Offload sync function calls to a thread#3010
Rifa-111 wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
Rifa-111:patch-1

Conversation

@Rifa-111

Copy link
Copy Markdown

Summary

Sync tool functions registered with FastMCP were called directly on the
asyncio event loop, blocking it for the duration of the call. Any sync
tool performing I/O (e.g. using requests, time.sleep, file reads)
would stall all other concurrent tasks on the same server.

Change

In call_fn_with_arg_validation (src/mcp/server/fastmcp/utilities/func_metadata.py),
wrap sync calls with anyio.to_thread.run_sync() instead of calling
them directly:

Before:

if fn_is_async:
    return await fn(**arguments_parsed_dict)
else:
    return fn(**arguments_parsed_dict)

After:

if fn_is_async:
    return await fn(**arguments_parsed_dict)
else:
    return await anyio.to_thread.run_sync(lambda: fn(**arguments_parsed_dict))

This is consistent with how FastAPI handles sync route handlers, and how
FileResource and DirectoryResource already handle blocking reads
within this codebase.

anyio.to_thread.run_sync() uses copy_context() internally so
contextvars propagate correctly to the worker thread. The MCP Context
object is unaffected as it is passed explicitly via
arguments_to_pass_directly.

Testing

Added a test that registers a sync tool using time.sleep and asserts
that two concurrent calls complete in roughly the time of one sleep,
confirming they run in parallel without blocking the event loop.

Closes #1839

Offload synchronous function calls to a thread to prevent blocking the event loop. This change ensures that context is correctly propagated using anyio's run_sync.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/mcp/server/fastmcp/utilities/func_metadata.py">

<violation number="1" location="src/mcp/server/fastmcp/utilities/func_metadata.py:96">
P1: Add the missing `anyio` import before using `anyio.to_thread.run_sync`; otherwise every synchronous tool call fails at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

return await fn(**arguments_parsed_dict)
else:
return fn(**arguments_parsed_dict)
return await anyio.to_thread.run_sync(lambda: fn(**arguments_parsed_dict))

@cubic-dev-ai cubic-dev-ai Bot Jun 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Add the missing anyio import before using anyio.to_thread.run_sync; otherwise every synchronous tool call fails at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp/server/fastmcp/utilities/func_metadata.py, line 96:

<comment>Add the missing `anyio` import before using `anyio.to_thread.run_sync`; otherwise every synchronous tool call fails at runtime.</comment>

<file context>
@@ -93,7 +93,11 @@ async def call_fn_with_arg_validation(
             return await fn(**arguments_parsed_dict)
         else:
-            return fn(**arguments_parsed_dict)
+            return await anyio.to_thread.run_sync(lambda: fn(**arguments_parsed_dict))
+            # Sync functions are offloaded to a thread to avoid blocking the event loop.
+            # anyio.to_thread.run_sync() uses copy_context() internally, so contextvars
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant