-
Notifications
You must be signed in to change notification settings - Fork 1.3k
stdio_client
hangs during session.initialize()
due to failed message transfer via internal anyio
memory stream
#382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This might be the same issue as #201, which is fixed on main, but has not been released. Do you mind trying this with the new version from main? |
I've tried with the latest version, error still.
|
@Stark-X I believe this is the root cause of the timeout. You created a ![]() |
Yes, I think @Stark-X is right here. I think this is a matter of mixing lowlevel primitives and FastCMP |
Labels:
bug
,transport:stdio
,client
,server
,anyio
Body:
Environment:
modelcontextprotocol
SDK Version: [v1.5.0]anyio
Version: [v4.9.0]Description:
When using the documented
mcp.client.stdio.stdio_client
to connect to amcp.server.fastmcp.FastMCP
server running via the stdio transport (await mcp.run_stdio_async()
), the client consistently hangs during theawait session.initialize()
call, eventually timing out.Extensive debugging using monkeypatching revealed the following sequence:
stdio_client
.initialize
request.mcp.server.stdio.stdio_server
successfully reads theinitialize
request from the process's stdin (usinganyio.wrap_file(TextIOWrapper(...))
).JSONRPCMessage
onto theanyio
memory stream (read_stream_writer
) intended for the server's main processing loop.mcp.shared.session.BaseSession._receive_loop
, awaits messages on the receiving end of that sameanyio
memory stream (async for message in self._read_stream:
).async for
loop inBaseSession._receive_loop
never yields the message that was sent to the memory stream. It remains blocked.initialize
message is never received by theBaseSession
loop, no response is generated.initialize
response.This indicates a failure in message passing across the
anyio
memory stream used internally by the stdio transport implementation, specifically between the task group managing stdio bridging and the task group managing session message processing, when running under theasyncio
backend in this configuration.A separate test confirmed that replacing the internal
anyio
memory streams with standardasyncio.Queue
s does allow the message to be transferred successfully between these task contexts, allowing initialization and subsequent communication to proceed. This strongly suggests the issue lies within theanyio
memory stream implementation or its usage in this specific cross-task-group stdio scenario.Steps to Reproduce:
Save the following server code as
mcp_file_server.py
:(Use the original, unpatched version that calls
await mcp.run_stdio_async()
)Save the following client code as
minimal_client.py
:(Use the version corrected for Python 3.10 timeouts and list_tools processing)
Install dependencies:
pip install modelcontextprotocol pandas
(or usinguv
)Run the client:
python minimal_client.py
Expected Behavior:
The client connects, initializes successfully, lists tools, and exits cleanly.
Actual Behavior:
The client connects but hangs at the
Initializing...
step. After the 30-second timeout expires forsession.initialize()
, it logs the timeout error and exits. Server logs confirm thatmcp.run_stdio_async()
was awaited but never processed the incoming message until after the client disconnected.Logs:
(Logs showing the client timeout and the server hanging after
>>> About to await mcp.run_stdio_async()
)Additional Context:
mcp.server.stdio.stdio_server
does successfully read theinitialize
request from stdin and sends it to the internalanyio
memory stream.async for
loop withinmcp.shared.session.BaseSession._receive_loop
(which reads from that memory stream) never yields the message.anyio
memory streams with standardasyncio.Queue
s allowed the communication to succeed, isolating the problem to theanyio
memory stream communication between the stdio bridging task group and the session processing task group.This appears to be a bug in the stdio transport implementation related to
anyio
memory streams and task group interaction under theasyncio
backend.The patched working version with asyncio.Queue attached in
[working_code.zip](https://github.com/user-attachments/files/19485125/working_code.zip)
Run vis
uv run minimal_client.py
The text was updated successfully, but these errors were encountered: