Skip to content

fix: catch KeyboardInterrupt in run() for clean Ctrl+C exit#2943

Open
kaXianc2-gom wants to merge 1 commit into
modelcontextprotocol:mainfrom
kaXianc2-gom:fix/ctrl-c-clean-exit
Open

fix: catch KeyboardInterrupt in run() for clean Ctrl+C exit#2943
kaXianc2-gom wants to merge 1 commit into
modelcontextprotocol:mainfrom
kaXianc2-gom:fix/ctrl-c-clean-exit

Conversation

@kaXianc2-gom

Copy link
Copy Markdown

AI Assistance Disclosure

This contribution was developed with AI assistance (Claude / Anthropic Claude Code).

Summary

Fix #2663: When running a stdio server from the terminal and pressing Ctrl+C, the server previously produced a noisy multi-frame traceback through anyio.run()asyncio.runnersanyio.streams.memory.receive. This change wraps the anyio.run() call in MCPServer.run() with a try/except KeyboardInterrupt so the server exits cleanly.

Changes

src/mcp/server/mcpserver/server.py: +7 lines (try/except + comment), −5 lines (indent shift)
tests/server/test_stdio.py: +14 lines (1 new test)

Verification Process

  1. Forked → cloned to local sandbox
  2. Applied fix: try/except KeyboardInterrupt in run()
  3. Wrote test mocking anyio.run to raise KeyboardInterrupt
  4. Ran stdio test suite: 5/5 passed (4 existing + 1 new)
  5. Ruff format + lint: all checks passed
  6. Zero new pragma/type:ignore/noqa annotations

Test Results

tests/server/test_stdio.py::test_stdio_server_round_trips_messages_over_injected_streams PASSED
tests/server/test_stdio.py::test_stdio_server_invalid_utf8 PASSED
tests/server/test_stdio.py::test_mcpserver_run_stdio_serves_until_stdin_closes PASSED
tests/server/test_stdio.py::test_mcpserver_run_stdio_runs_lifespan_cleanup_after_stdin_closes PASSED
tests/server/test_stdio.py::test_mcpserver_run_catches_keyboard_interrupt PASSED [NEW]

Cross-Validation

Scenario Before After
Ctrl+C during stdio run ❌ noisy traceback ✅ clean exit
Normal shutdown (EOF)
Unknown transport error ✅ raised normally ✅ raised normally

Closes #2663

When running a stdio server from the terminal, Ctrl+C previously produced
a noisy multi-frame traceback through anyio.run() → asyncio.runners →
anyio.streams.memory.receive. This change catches KeyboardInterrupt at
the top-level run() method so the server exits quietly, matching user
expectations for a terminal application.

Closes modelcontextprotocol#2663

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FastMCP.run(transport="stdio") produces noisy traceback on KeyboardInterrupt instead of clean exit

1 participant