fix(server): exit cleanly on KeyboardInterrupt in MCPServer.run - #3027
fix(server): exit cleanly on KeyboardInterrupt in MCPServer.run#3027devansh-dek wants to merge 1 commit into
Conversation
Catch KeyboardInterrupt at the sync run() boundary so Ctrl-C during stdio server execution does not print a noisy anyio/asyncio cancellation traceback. Fixes modelcontextprotocol#2663
8235287 to
ac3f477
Compare
ErenAta16
left a comment
There was a problem hiding this comment.
Fix is correct, and this is the tightest of the three open PRs for #2663 in terms of diff: one test file, no unrelated edits.
Worth knowing that the source change here is byte-identical to #2745 (Jun 1) and #2778 (Jun 4). All three wrap the match transport: block in try/except KeyboardInterrupt: return, same lines, same position. Yours is the most recent of the three, so #2745 has priority by date, though yours has the cleanest diff. Full comparison on #2663.
Exit code. except KeyboardInterrupt: return makes run() return normally, so the process exits 0. Measured both shapes:
except KeyboardInterrupt: return -> exit code 0
raise SystemExit(130) -> exit code 130
130 is 128 + SIGINT, which is what an uncaught KeyboardInterrupt produces today and what a shell or supervisor expects from a Ctrl-C. After this change a script doing mcp-server || handle_failure reads an interrupted run as a clean success.
If the goal is only to drop the traceback, raise SystemExit(130) gets that without changing the signal semantics. If exiting 0 is deliberate (say, because a supervisor is meant to treat Ctrl-C as a normal stop) that's a defensible choice, it just deserves a comment, otherwise someone will "fix" it later assuming it was an oversight.
Your two tests (suppress KeyboardInterrupt, re-raise RuntimeError) are the right pair: the second is what stops a future change from widening the except into something that swallows real startup failures. If the exit-code point above is adopted, the first would want updating to assert SystemExit with code 130 rather than a plain None return.
Summary
KeyboardInterruptat theMCPServer.run()sync boundary so Ctrl-C during stdio server execution exits cleanly instead of printing a noisy anyio/asyncio cancellation traceback.KeyboardInterruptis suppressed while other exceptions still propagate.Test plan
uv run pytest tests/server/mcpserver/test_server.py::TestServer::test_run_suppresses_keyboard_interrupt tests/server/mcpserver/test_server.py::TestServer::test_run_reraises_other_exceptions -vuv run ruff check src/mcp/server/mcpserver/server.py tests/server/mcpserver/test_server.pyFixes #2663