stdio server ignores the first two Ctrl+C presses at a terminal (blocked stdin worker thread)
まだ誰も着手していません。
評価
調査の方向性
Reproduce the issue with the provided server.py and pty script, then read MCPServer.run("stdio"), stdio_server(), and the _claim_fd comment. Compare the possible cancellation and signal-handling approaches across the stated environments. Done means terminal Ctrl+C exits without waiting for the stdin worker or producing a threading._shutdown traceback, while client EOF still ends the read.
索引モデルが issue の本文から書いたものです。
説明
Description
Running an MCPServer over stdio from a terminal, Ctrl+C does nothing the first time, nothing visible the second time, and the third ends the process with a traceback out of threading._shutdown. This is related to #2663 but is not the same problem, and the fix proposed there (catching KeyboardInterrupt around anyio.run, #2745) does not address it: on a TTY the first Ctrl+C never raises KeyboardInterrupt.
A client that closes stdin never sees this, because EOF ends the read. It only affects people running a server by hand, which is what you do when testing one.
Reproduction
server.py:
from mcp.server.mcpserver import MCPServer
MCPServer("repro").run("stdio")
Run python server.py in a terminal and press Ctrl+C three times. Or, scripted, with a pty as stdin:
import pty, signal, subprocess, sys, time
master, slave = pty.openpty()
proc = subprocess.Popen([sys.executable, "server.py"], stdin=slave, stderr=subprocess.PIPE)
time.sleep(2) # let it reach the stdin read
for n in range(1, 6):
proc.send_signal(signal.SIGINT)
try:
proc.wait(1)
break
except subprocess.TimeoutExpired:
print(f"still running 1s after SIGINT #{n}")
print(f"exit code {proc.returncode} after {n} SIGINT(s)")
print(proc.stderr.read().decode()[-400:])
Output:
still running 1s after SIGINT #1
still running 1s after SIGINT #2
exit code -2 after 3 SIGINT(s)
...
Exception ignored while joining a thread in _thread._shutdown():
Traceback (most recent call last):
File "/usr/lib64/python3.14/threading.py", line 1583, in _shutdown
_thread_shutdown()
KeyboardInterrupt:
Cause
stdio_server() reads stdin through anyio.wrap_file, so each readline() runs in an AnyIO worker thread. A thread blocked in readline() on a terminal cannot be cancelled.
- First SIGINT: asyncio's handler cancels the main task. The task cannot finish cancelling, because
stdin_readeris awaiting the worker thread. Afaulthandlerdump taken at this point shows the main thread still inrun_forever→select, and theAnyIO worker threadinside the blocking call. - Second SIGINT: asyncio raises
KeyboardInterruptout of the loop. Interpreter shutdown then joins the same worker thread, which is still blocked. - Third SIGINT: interrupts that join, producing the
_thread._shutdowntraceback.
The comment in _claim_fd ("a worker thread can still block on this descriptor after the transport exits") suggests this is known for the descriptor's lifetime; this is the same thread seen from the user's side.
Possible fixes
- In
MCPServer.run("stdio"), install a SIGINT handler for the duration of the run that ends the process without waiting for the reader thread. We useos._exit(130)in our server, which is only appropriate if nothing needs unwinding, so probably not as a library default. - Read stdin without a thread where the platform allows it (on POSIX, wait for fd readability on the event loop and read non-blocking), so cancellation can actually complete.
- Run the blocking read with
abandon_on_cancel=Truein a daemon thread, so neither task cancellation nor interpreter shutdown waits on it.
Environment
MCP Python SDK 2.2.0, anyio 4.15.1, Python 3.14.7, Linux (Fedora 44). Not tested on v1.x, macOS or Windows.
- 主要言語
- Python
- スター
- 24.3k
- フォーク
- 4k
- 平均マージ
- 1日 19分
- マージ済み PR(30日)
- 29
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
modelcontextprotocol/python-sdk のほかの issue
-
v1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
modelcontextprotocol/python-sdk#3546 · コメント 5 件 ·
-
v1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
modelcontextprotocol/python-sdk#3545 · コメント 1 件 ·
-
v1 v2
難易度 1/5 1時間未満 初心者へのやさしさ 91/100
modelcontextprotocol/python-sdk#3508 · コメント 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
modelcontextprotocol/python-sdk#3504 ·
-
v1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
modelcontextprotocol/python-sdk#3492 · コメント 1 件 ·
modelcontextprotocol/python-sdk の issue をすべて見る
似ている issue
-
Add: hunch オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
AbdelStark/awesome-typesafe#104 ·
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
DiamondLightSource/dodal#2211 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
openml/openml-python#1749 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
sipyourdrink-ltd/bernstein#6191 ·