Description
Describe the bug
When running an MCP server using mcp-sdk
(both FastMCP
and the low-level Server
with mcp.server.stdio.stdio_server
) on macOS with Python 3.12, the server process hangs indefinitely immediately after the DEBUG - Using selector: KqueueSelector
log message appears. This happens during the server.run()
or mcp.run()
call.
The hang occurs regardless of whether the STDIO transport (transport="stdio"
) or the default web/SSE transport is used. It also occurs even with a minimal server configuration with no custom tools, resources, or plugins registered.
Environment:
- OS: macOS 15 Sequoia (Darwin 24.5.0)
- Python: 3.12.9 (via Homebrew - Please update if incorrect)
- mcp-sdk Version: 1.6.1.dev14+babb477 (Installed via
pip install -e .
from Git main branch usingmcp @ git+https://github.com/modelcontextprotocol/python-sdk.git
inpyproject.toml
withhatchling
andallow-direct-references = true
) - Key Dependencies:
- uvicorn: 0.30.3
- starlette: 0.37.2
- anyio: 4.9.0
To Reproduce
Steps to reproduce the behavior:
- Ensure Python 3.12.9 and
mcp-sdk
(tested with version1.6.1.dev14+babb477
installed from Git main branch) are installed in your environment on macOS. - Save the following minimal STDIO server example (based on the SDK documentation) as
mcp_stdio_test.py
:# From https://github.com/modelcontextprotocol/python-sdk README (Low-Level Server example) import mcp.server.stdio import mcp.types as types from mcp.server.lowlevel import NotificationOptions, Server from mcp.server.models import InitializationOptions import logging # Add logging import asyncio # Add asyncio # Configure logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Create a server instance logger.debug("Creating Server instance...") server = Server("example-server") logger.debug("Server instance created.") @server.list_prompts() async def handle_list_prompts() -> list[types.Prompt]: logger.debug("Handling list_prompts request...") return [ types.Prompt( name="example-prompt", description="An example prompt template", arguments=[ types.PromptArgument( name="arg1", description="Example argument", required=True ) ], ) ] logger.debug("list_prompts handler defined.") @server.get_prompt() async def handle_get_prompt( name: str, arguments: dict[str, str] | None ) -> types.GetPromptResult: logger.debug(f"Handling get_prompt request for: {name}") if name != "example-prompt": raise ValueError(f"Unknown prompt: {name}") return types.GetPromptResult( description="Example prompt", messages=[ types.PromptMessage( role="user", content=types.TextContent(type="text", text="Example prompt text"), ) ], ) logger.debug("get_prompt handler defined.") async def run(): logger.debug("run() function started.") async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): logger.debug("stdio_server context entered.") init_options = InitializationOptions( server_name="example", server_version="0.1.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ) logger.debug(f"Initialization options created: {init_options}") logger.debug("Attempting server.run()...") # Note: Using low-level server.run, not FastMCP's run await server.run( read_stream, write_stream, init_options ) logger.debug("server.run() finished.") # This likely won't be reached in normal operation if __name__ == "__main__": logger.info("Starting asyncio event loop...") try: asyncio.run(run()) except KeyboardInterrupt: logger.info("KeyboardInterrupt received, exiting.") except Exception as e: logger.critical(f"Unhandled exception in main loop: {e}", exc_info=True) finally: logger.info("asyncio event loop finished.")
- Run the script from the terminal:
python mcp_stdio_test.py
- See error: Observe the log output. The script hangs indefinitely after the
Attempting server.run()...
log message.
Expected behavior
The server should successfully start, remain running, and be ready to accept MCP connections via STDIO (or SSE if run without transport="stdio"
). It should not hang during initialization.
Screenshots
N/A - The issue is a process hang, the relevant output is text logs shown below.
Desktop:
- OS: macOS 15 Sequoia (Darwin 24.5.0)
- Python Version: 3.12.9 (via Homebrew - Please update if incorrect)
- mcp-sdk Version: 1.6.1.dev14+babb477 (Installed from Git main branch)
- Key Dependencies:
- uvicorn: 0.30.3
- starlette: 0.37.2
- anyio: 4.9.0
Smartphone:
N/A
Additional context
- The issue occurs with both
FastMCP().run(...)
and the low-levelServer().run(...)
implementation. - The hang happens even when all custom tools, resources, plugins, and prompts defined in the application code are commented out.
- The issue persists whether using
mcp
installed from PyPI or directly from the Git main branch. - Pinning
uvicorn[standard]
to0.30.3
(a version used in a previously working commit) did not resolve the issue. - Adding a
time.sleep(10)
before therun()
call (which seemed to help in older commits) no longer prevents the hang. - Issue MCP Server: Inconsistent Exception Handling in @app.call_tool and Client Undetected Server Termination via Stdio #396 seems potentially related as it also involves problems with the STDIO transport, although it describes the client not detecting server termination rather than the server hanging on startup.
This strongly suggests an underlying issue within mcp-sdk
's core run logic or its interaction with asyncio
/KqueueSelector
on macOS with recent Python versions or recent mcp-sdk
versions. The hang point after KqueueSelector
seems critical.
Log Output Showing Hang:
2025-04-21 02:17:29,965 - DEBUG - Creating Server instance...
2025-04-21 02:17:29,966 - DEBUG - Initializing server 'example-server'
2025-04-21 02:17:29,966 - DEBUG - Server instance created.
2025-04-21 02:17:29,966 - DEBUG - Registering handler for PromptListRequest
2025-04-21 02:17:29,966 - DEBUG - list_prompts handler defined.
2025-04-21 02:17:29,966 - DEBUG - Registering handler for GetPromptRequest
2025-04-21 02:17:29,966 - DEBUG - get_prompt handler defined.
2025-04-21 02:17:29,966 - INFO - Starting asyncio event loop...
2025-04-21 02:17:29,966 - DEBUG - Using selector: KqueueSelector
2025-04-21 02:17:29,971 - DEBUG - run() function started.
2025-04-21 02:17:29,973 - DEBUG - stdio_server context entered.
2025-04-21 02:17:29,973 - DEBUG - Initialization options created: server_name='example' server_version='0.1.0' capabilities=ServerCapabilities(experimental={}, logging=None, prompts=PromptsCapability(listChanged=False), resources=None, tools=None) instructions=None
2025-04-21 02:17:29,973 - DEBUG - Attempting server.run()...
<-- HANGS HERE INDEFINITELY -->