Repository navigation
MCP Tool execution hangs indefinitely in stdio mode when calling external Python scripts #671
Description
Activity
@ycycycl
take a look here: dev-kit-mcp-server
it works smoothly.Met the exact same issue here. The function mentioned by @DanielAvdar solved it perfectly. Thanks a lot!
Reacted by Daniel Avdar@Hellwz
Hello, could you provide me with an example to have a look?
The code I wrote by referring to that function still has problems and reports the following error.the code:
async def create_sub_proccess(cmd: str) -> asyncio.subprocess.Process: """Create a subprocess to execute a shell command. Args: cmd: The shell command to execute Returns: A subprocess object with stdout and stderr pipes """ process_get = await asyncio.create_subprocess_shell( cmd, cwd=field(init=False, repr=False).as_posix(), stdin=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) return process_get @mcp.tool() async def plot_by_script(script: str, path: str) -> str: """ Draw through a python script in the form of command-line parameters, input the python script path and file path, draw the image and convert it to base64 format to return. :param script: python scripts can pass in a parameter to specify the data path :param path: file path :return: image base64 """ cmd = f"python {script} {path}" process = await create_sub_proccess(cmd) stdout, stderr = await process.communicate() res = { "stdout": stdout.decode(errors="replace"), "stderr": stderr.decode(errors="replace"), "exitcode": process.returncode, "cwd": field(init=False, repr=False).as_posix(), } return res["stdout"]the error:
⚠️ error: 'coroutine' object has no attribute 'content' /project/mcp/client.py:151: RuntimeWarning: coroutine 'ClientSession.call_tool' was never awaited print(f"\n⚠️ error: {str(e)}") RuntimeWarning: Enable tracemalloc to get the object allocation tracebackHi @atongmuuu,
I made simple modifications based on your script and used it to call a very simple Python script. It works just fine. Hope it helps.async def create_sub_process(cmd: str, cwd: str) -> asyncio.subprocess.Process: """Create a subprocess to execute a shell command.""" process_get = await asyncio.create_subprocess_shell( cmd, cwd=cwd, stdin=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) return process_get @mcp.tool() async def call_script(script_path: str) -> str: """Call an external script.""" current_dir = os.path.dirname(os.path.abspath(__file__)) cmd = f"{sys.executable} {script_path}" process = await create_sub_process(cmd, current_dir) stdout, stderr = await process.communicate() res = { "stdout": stdout.decode(errors="replace"), "stderr": stderr.decode(errors="replace"), "exitcode": process.returncode, "cwd": current_dir, } return res["stdout"]@Hellwz
Thank you. I changed the code later and the script has been successfully called.Reacted by Zhaowen WangI can:
- Reproduce the issue.
- Confirm it is a Windows-only issue.
- Confirm
stdin=DEVNULLworks as a workaround.
Reacted by Konstantin Lopuhin- addedbugSomething isn't workingSomething isn't workingready for workEnough information for someone to start working onEnough information for someone to start working onP3Nice to haves, rare edge casesNice to haves, rare edge cases
on Oct 3, 2025 - added a commit that references this issue
on Dec 19, 2025 - added a commit that references this issue
on Feb 18, 2026 - added a commit that references this issue
on Feb 18, 2026 - added a commit that references this issue
on Feb 20, 2026 - added a commit that references this issue
on Apr 14, 2026 I would like to work on this. I’ll first reproduce the Windows stdio behavior on current main, then keep any PR scoped to the smallest fix or docs/tests needed.
Disclosure: I may use AI assistance while working, but I will review and own the final change.
- added 4 commits that reference this issue
on Jul 16, 2026 - added a commit that references this issue
on Jul 24, 2026 Fixed on
mainin #3117 (merge commit 629ca29), shipping with v2.The root cause here was that a tool-spawned child inherited the server's stdin - the protocol pipe - and, on Windows, hung in interpreter startup reading it (CPython gh-78961).
stdio_server()now moves the wire onto private descriptors while serving and points fd 0 at an end-of-file source and fd 1 at stderr, so a child spawned from a handler reads EOF instead of hanging, and stray output can't corrupt the stream either. There's a regression test for this exact scenario that fails on the pre-fix code and passes onmain.If anyone still hits this on v2, please open a fresh issue with the repro.
- added a commit that references this issue
on Jul 27, 2026
Describe the bug
When using FastMCP in stdio transport mode, any tool that attempts to execute an external Python script will hang indefinitely until timeout, without returning any result or error. The same code works perfectly when using SSE transport mode.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The tool should execute the external Python script, capture its output, and return the results promptly, regardless of the transport mode used (stdio or SSE).
Screenshots

When using stdio, the tool will hang indefinitely until timeout.
However, correct results can be obtained by using SSE mode.

Desktop (please complete the following information):
Additional context
· The issue persists regardless of whether using asyncio.create_subprocess_exec() or subprocess.run().
· I tried printing something in an external py script and found that when calling the tool in STDIO mode, it would be unresponsive, but after timeout, the external py script would be executed and something would be printed out. This is like the running of a py script being blocked until the tool call times out before it can run