MCP Server Hangs Indefinitely After KqueueSelector Log on macOS (Python 3.12, Both STDIO & SSE)
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.gitinpyproject.tomlwithhatchlingandallow-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+babb477installed 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
mcpinstalled 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 #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 -->
Same issue with Python 3.10.17 and 3.12.3; although it hangs even earlier at this stage
...
DEBUG Requirement already installed: mdurl==0.1.2
Audited 28 packages in 0.08ms
DEBUG Using Python 3.10.17 interpreter at: .../.venv/bin/python3
DEBUG Running `python weather.py`
DEBUG Spawned child 23168 in process group 23167
<-- HANGS -->
Using fastmcp but I think the problem is coming from deeper. With Python 3.13, fastmcp==2.5.1 running this simple example:
from fastmcp import Client
from src.config.config_manager import get_config_manager
import asyncio
import sys
import os
async def get_tools():
servers = get_config_manager().get_mcp_config().list_servers()
print("servers: {servers}".format(servers=servers))
server = servers[1]
print("Picking: {server}".format(server=server))
config = get_config_manager().get_mcp_config().get_server(server)
final_config = {
"mcpServers": {
server: config.to_dict()
}
}
print("Final generated config: {config}".format(config=final_config))
'''
Output: Final generated config: {'mcpServers': {'everything': {'command': 'npx', 'args': ['-y', '@modelcontextprotocol/server-everything']}}}
'''
async with Client(final_config, timeout=10) as client:
print("Blocked here, pid:{pid}".format(pid=os.getpid()))
tools = await client.list_tools()
print("Tools output: {tools}".format(tools=tools))
print("Starting the process")
print("Current process pid: {pid}".format(pid=os.getpid()))
asyncio.run(get_tools())
This gets stuck in the stdio prompt
Hi thanks for this report, is this still an issue for you? Checking as it's been a while (apologies for the time it took to get back to this)