Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

stdio server ignores the first two Ctrl+C presses at a terminal (blocked stdin worker thread)

未關閉
#3,551 1 則留言 1 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
5/5
預估耗時
一週以上
新手友好度
35/100
Issue 類型
缺陷
描述清晰度
需要釐清
活躍度
活躍
技術堆疊
python
領域
backend

研究方向

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 內容生成。

描述

v1 v2
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.

  1. First SIGINT: asyncio's handler cancels the main task. The task cannot finish cancelling, because stdin_reader is awaiting the worker thread. A faulthandler dump taken at this point shows the main thread still in run_foreverselect, and the AnyIO worker thread inside the blocking call.
  2. Second SIGINT: asyncio raises KeyboardInterrupt out of the loop. Interpreter shutdown then joins the same worker thread, which is still blocked.
  3. Third SIGINT: interrupts that join, producing the _thread._shutdown traceback.

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 use os._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=True in 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 天 11 小時
30 天內合併 PR
30

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

modelcontextprotocol/python-sdk 的其他 Issue

查看 modelcontextprotocol/python-sdk 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。