python-sdk icon indicating copy to clipboard operation
python-sdk copied to clipboard

stdio_client fails with BrokenResourceError during initialization

Open ParthibanRajasekaran opened this issue 1 month ago • 2 comments

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

  1. 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()
  1. 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())
  1. Run: python test_client.py
  2. 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

ParthibanRajasekaran avatar Nov 02 '25 22:11 ParthibanRajasekaran

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

madara-uchihaaa avatar Nov 06 '25 10:11 madara-uchihaaa

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:

  1. Have a flag in the ClientSession flag 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.
  2. Split the classes: have a class which is only a context manager (say BuildClientSession or some hopefully better name), which only exposes the enter and exit functions. When entering that object, it returns the actual ClientSession, and all its methods are immediately available for use.

Any maintainers want to weigh in with other options or preferences?

ndhansen avatar Nov 14 '25 20:11 ndhansen