快速结论:在 Windows 系统上使用 FastMCP 的 stdio 传输模式时,当 MCP 工具调用外部 Python 脚本,会因子进程继承了服务端的 stdin(协议管道)而挂起无限等待。优先排查子进程创建时是否设置了 stdin=asyncio.subprocess.DEVNULL。
适用环境:Windows 11 24H2 26100.3775,Python 3.10.0,mcp 1.7.1,asyncio 3.4.3(评论区确认是 Windows 专用问题,且 mcp v1.x 中存在此 Bug,v2 已修复)。
最快修复方案:在调用 asyncio.create_subprocess_exec 或 asyncio.create_subprocess_shell 时,增加参数 stdin=asyncio.subprocess.DEVNULL,使子进程的 stdin 指向空设备,避免它读取父进程的协议管道。示例代码片段:process = await asyncio.create_subprocess_exec(*cmd, stdin=asyncio.subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)。
注意事项:该方案为已验证的工作区(workaround),并非官方根本修复。官方已在 #3117 中修复,确认 mcp v2 起不再需要此修改。若你正在使用 v1.x,请仅将其视为临时绕过方法,并计划升级到 v2。
问题场景
用户使用 FastMCP 在 stdio 传输模式下运行 MCP 服务器,并定义了一个工具(tool)通过 asyncio.create_subprocess_exec 或 subprocess.run 调用外部 Python 脚本(例如 minimal_script.py)。当通过 MCP Inspector 调用该工具时,请求挂起直至超时,且不返回任何输出或错误。相同的代码在 SSE 传输模式下工作正常。
报错原文
MCP Tool execution hangs indefinitely in stdio mode when calling external Python scripts
实际运行时用户观察不到任何错误输出,工具调用一直卡住直到达到客户端超时。
原因分析
可能原因:在 Windows 系统上,通过 asyncio.create_subprocess_exec 或 subprocess.run 启动的子进程会继承父进程的 stdin 文件句柄。由于 FastMCP 的 stdio 传输模式下,父进程的 stdin 实际上是 MCP 协议的通信管道。子进程(外部 Python 脚本)在启动时尝试读取 stdin 来初始化(CPython 存在一个已知问题 gh-78961,会导致子进程在 Windows 上读取 stdin 时挂起),造成了死锁:父进程等待子进程输出,子进程等待从 stdin 中读到数据,而 stdin 却被 MCP 协议占用。
官方在 #3117 (PR) 中确认了根本原因:stdio_server() 在建立连接后并未将协议管道移到私有描述符上,导致子进程继承了服务端 stdin 句柄。v2 修复中通过将 fd 0 指向 EOF 解决。
环境排查
- 操作系统是否 Windows(Issue 确认仅 Windows 重现)
- Python 版本(Issue 测试于 Python 3.10.0,但可能影响其他版本)
- mcp Python SDK 版本(v1.7.1 及之前版本都可能受影响)
- 确认子进程创建方式:
asyncio.create_subprocess_exec、asyncio.create_subprocess_shell或subprocess.run均会触发 - 确认在 SSE 传输模式下能否正常运行(可作为对比)
解决步骤
- 临时解决方案(已验证):在创建子进程时显式设置
stdin=asyncio.subprocess.DEVNULL。修改工具函数中的调用代码,例如:process = await asyncio.create_subprocess_exec( *cmd, stdin=asyncio.subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE )或使用 shell 方式时一样处理。完整示例见 Issue 中 @DanielAvdar 提供的代码。
- 替代方案:暂时切换为 SSE 传输模式运行服务器(
mcp.run(transport="sse")),但需注意 SSE 模式可能需要额外配置。 - 最终修复:升级 mcp Python SDK 至 v2 或更高版本(已在
main分支修复,合并 commit 629ca29,随 v2 发布)。v2 中stdio_server()已将协议管道移到私有描述符,子进程读取 stdin 会直接获得 EOF,不会挂起。
验证方法
完成修改后,重新以 stdio 模式启动 MCP 服务器,并通过 MCP Inspector 调用之前挂起的工具。正常情况下应当立即返回外部脚本的输出(如 “Successfully Call!”),不再超时。也可同时使用 SSE 模式对比确保结果一致。
参考来源
modelcontextprotocol/python-sdk #671
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[BUG]: when scrolling up, scrolling jumps](https://www.chat-gpts.plus/wp-content/uploads/2026/07/5846-1433eaaa-768x403.jpg)

