modelcontextprotocol / modelcontextprotocol/python-sdk

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

未关闭
#1,333 1 条评论 1 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

bug P2 ready for work
主要语言
Python
星标
24.3k
派生
4k
平均合并
1 天 1 小时
30 天内合并 PR
31

描述

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: https://github.com/modelcontextprotocol/python-sdk/blob/543961968c0634e93d919d509cce23a1d6a56c21/src/mcp/server/stdio.py#L57-L58

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

Example Code
  import anyio
  import pytest


  @pytest.mark.anyio
  async def test_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)

      async def stdin_reader():
          # First message gets through
          await send_stream.send("request_1")

          # Second message (like a ping) blocks until first is fully processed
          await send_stream.send("ping")  # This will block for entire processing time!

      async def message_processor():
          # Process first message
          msg = await receive_stream.receive()

          # Simulate slow processing (database query, API call, etc.)
          await anyio.sleep(0.1)  # 100ms processing time

          # During this time, stdin_reader is completely blocked
          # No new messages (including pings) can be read!

          ping = await receive_stream.receive()  # Finally unblocks stdin_reader

      async with anyio.create_task_group() as tg:
          tg.start_soon(message_processor)
          await anyio.sleep(0.01)  # Let processor start waiting
          tg.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.

async def stdio_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.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 src/mcp/server/stdio.py 中引用的 memory-stream 设置开始,检查 max_buffer_size 如何传递给两个流。按照描述使缓冲区大小可配置,同时保留当前默认值,并使用提供的异步示例验证正值能够在处理速度较慢时对输入进行缓冲。

由索引模型根据 Issue 内容生成。

评估

技术栈
python
领域
backend
Issue 类型
缺陷
难度
2/5
预计耗时
1-3 小时
活跃度
停滞
描述清晰度
描述清楚
新手友好度
52/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。