Skip to content

stdio_server uses unbuffered memory streams which can cause server to block and become unresponsive #1333

Description

@peteski22

Initial Checks

Description

The MCP Python SDK's stdio_server becomes unresponsive during slow message processing operations, causing ping and/or new requests to timeout.

Observed Behaviour

  • Server appears to 'freeze' and becomes unresponsive after running for extended periods
  • Ping requests timeout during slow operations
  • New requests cannot be processed while the server is handling long-running operations
  • After 67 minutes of operation with requests every 10 seconds, the server became completely unresponsive

Expected Behaviour

  • Server should remain responsive to new requests (like pings) even while processing slow operations
  • Ping requests should not timeout due to message processing delays
  • The server should handle concurrent requests without blocking

Suspected Root Cause

The stdio_server uses max_buffer_size=0 (supplied to anyio.create_memory_object_stream) by default, which creates synchronous handoff between the stdin reader and message processor.

See:

read_stream_writer, read_stream=anyio.create_memory_object_stream(0)
write_stream, write_stream_reader=anyio.create_memory_object_stream(0)

When the message processor is slow or blocked, the stdin reader cannot read new messages from stdin, making the server appear unresponsive.

Example Code

importanyioimportpytest@pytest.mark.anyioasyncdeftest_server_becomes_unresponsive_with_slow_processor():
"""Demonstrates how server becomes unresponsive during slow processing."""# Simulates stdio_server with default max_buffer_size=0 (synchronous handoff)send_stream, receive_stream=anyio.create_memory_object_stream(0)
asyncdefstdin_reader():
# First message gets throughawaitsend_stream.send("request_1")
# Second message (like a ping) blocks until first is fully processedawaitsend_stream.send("ping") # This will block for entire processing time!asyncdefmessage_processor():
# Process first messagemsg=awaitreceive_stream.receive()
# Simulate slow processing (database query, API call, etc.)awaitanyio.sleep(0.1) # 100ms processing time# During this time, stdin_reader is completely blocked# No new messages (including pings) can be read!ping=awaitreceive_stream.receive() # Finally unblocks stdin_readerasyncwithanyio.create_task_group() astg:
tg.start_soon(message_processor)
awaitanyio.sleep(0.01) # Let processor start waitingtg.start_soon(stdin_reader)
send_stream.close()
receive_stream.close()

Proposed Solution

Allow users to configure max_buffer_size > 0 to enable buffering, with a default value (0) that preserves the current behaviour.

asyncdefstdio_server(
stdin: anyio.AsyncFile[str] |None=None,
stdout: anyio.AsyncFile[str] |None=None,
max_buffer_size: int=0, ):
# ...read_stream_writer, read_stream=anyio.create_memory_object_stream(max_buffer_size)
write_stream, write_stream_reader=anyio.create_memory_object_stream(max_buffer_size)

Python & MCP Python SDK

  • Python: 3.12
  • MCP Python SDK: 1.12.4
  • OS: macOS

Additional Context

The issue manifests in long-running servers where message processing can occasionally be slow (database queries, file operations, API calls).

The fix should make max_buffer_size configurable so users can add buffering to prevent the stdin reader from blocking during slow operations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions