modelcontextprotocol / modelcontextprotocol/python-sdk

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

オープン
#1,333 コメント 1 件 リアクション 1 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

bug P2 ready for work
主要言語
Python
スター
24.3k
フォーク
4k
平均マージ
1日 1時間
マージ済み PR(30日)
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. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

src/mcp/server/stdio.py の参照されている memory-stream の設定から始め、max_buffer_size が両方のストリームにどのように渡されているかを確認します。説明されているとおりにバッファサイズを設定可能にし、現在のデフォルト値を維持したうえで、提供されている非同期の例を使って、正の値によって処理が遅い場合に入力をバッファリングできることを検証します。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
backend
issue の種類
バグ
難易度
2/5
見積もり時間
1〜3時間
活発さ
停滞
明瞭さ
明確に書かれている
初心者へのやさしさ
52/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。