Skip to content

Stdio transport requires multiple Ctrl-C to terminate #2837

@jlowin

Description

@jlowin

When running a FastMCP server with stdio transport directly via python server.py, terminating with Ctrl-C requires multiple presses and produces messy tracebacks.

Reproduction

from fastmcp import FastMCP

mcp = FastMCP("Test Server")

@mcp.tool
def hello(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()

Run with python server.py, then press Ctrl-C.

Expected behavior

Single Ctrl-C cleanly terminates the server.

Actual behavior

Requires 2-3 Ctrl-C presses and produces cascading KeyboardInterrupt tracebacks:

^C^CTraceback (most recent call last):
  File "..._asyncio.py", line 2365, in run
    return runner.run(wrapper())
  ...
  File ".../asyncio/runners.py", line 157, in _on_sigint
    raise KeyboardInterrupt()
KeyboardInterrupt

During handling of the above exception, another exception occurred:
...

Root cause

The stdio transport blocks on selector.select() waiting for stdin. When SIGINT arrives:

  1. The blocking call is interrupted
  2. Async cleanup begins but gets interrupted by subsequent Ctrl-C
  3. Threading shutdown also gets interrupted

Notes

  • HTTP transport handles Ctrl-C cleanly (uvicorn has proper signal handling)
  • Stdio is designed to be controlled by a parent MCP client process, so interactive use is an edge case
  • However, python server.py is a common way to test servers during development

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.serverRelated to FastMCP server implementation or server-side functionality.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions