stdio_client fails with BrokenResourceError during initialization
Initial Checks
- [x] I confirm that I'm using the latest version of MCP Python SDK
- [x] I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
I'm trying to use stdio_client to spawn and communicate with a FastMCP server, but it consistently fails during the initialization handshake. The client times out after about 10 seconds with a BrokenResourceError.
The subprocess starts fine and I can see the server is running, but when the client tries to initialize the session, it hangs and eventually times out. Looking at the traceback, there's a BrokenResourceError happening in the stdout_reader coroutine at line 162 of mcp/client/stdio/init.py.
Expected behavior: The client should successfully spawn the server, complete the initialize handshake, and be ready to call tools.
Actual behavior: The client hangs during initialization and then times out with:
ExceptionGroup: unhandled errors in a TaskGroup (2 sub-exceptions) BrokenResourceError at mcp/client/stdio/init.py:162 in stdout_reader TimeoutError after waiting for initialize response
Additional cpontext: Interesting finding: When I test the server directly by piping JSON to it, it works perfectly:
$ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | python test_server.py
The server immediately returns a valid response. So the server itself is working correctly - it can read from stdin and write to stdout just fine. The issue seems to be specifically with how stdio_client is handling the subprocess communication.
I've tried various things to fix it but nothing works:
- Running python with -u flag for unbuffered output
- Setting sys.stdout.reconfigure(line_buffering=True) in the server
- Using absolute paths
- Increasing timeout values
- Setting PYTHONUNBUFFERED=1
Note: HTTP/SSE transport works fine as an alternative.
Example Code
- Create a simple FastMCP server (test_server.py):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("TestServer")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
if __name__ == "__main__":
mcp.run()
- Create a client that uses stdio_client (test_client.py):
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def test():
params = StdioServerParameters(
command="python",
args=["test_server.py"]
)
async with stdio_client(params) as (stdio, write):
session = ClientSession(stdio, write)
await session.initialize() # Hangs here
result = await session.list_tools()
print(f"Tools: {result.tools}")
asyncio.run(test())
- Run: python test_client.py
- Observe: hangs for about 10 seconds then fails with BrokenResourceError
Python & MCP Python SDK
- MCP SDK Version: 1.20.0
- Python Version: 3.12.7
- Operating System: macOS
- Installation method: pip install mcp==1.20.01
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def test():
params = StdioServerParameters(command="python", args=["main.py"])
async with stdio_client(params) as (stdio, write):
async with ClientSession(stdio, write) as session:
await session.initialize()
result = await session.list_tools()
print(f"Tools: {result.tools}")
asyncio.run(test())
can you try this @ParthibanRajasekaran
Yeah, without first asynchronously entering the ClientSession context manager it doesn't look like it can be used. Without doing a deeper analysis, I have a couple ideas for fixes:
- Have a flag in the
ClientSessionflag which tracks if it was entered as part of a context manager. The upside is that this would be a backwards-compatible change, the downside is that the interface doesn't make it clear which functions can and can't be called outside of a context manager. - Split the classes: have a class which is only a context manager (say
BuildClientSessionor some hopefully better name), which only exposes the enter and exit functions. When entering that object, it returns the actualClientSession, and all its methods are immediately available for use.
Any maintainers want to weigh in with other options or preferences?