Skip to content

MCP Server Hangs Indefinitely After KqueueSelector Log on macOS (Python 3.12, Both STDIO & SSE) #547

Description

@jlcases

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 using mcp @ git+https://github.com/modelcontextprotocol/python-sdk.git in pyproject.toml with hatchling and allow-direct-references = true)
  • Key Dependencies:
    • uvicorn: 0.30.3
    • starlette: 0.37.2
    • anyio: 4.9.0

To Reproduce

Steps to reproduce the behavior:

  1. Ensure Python 3.12.9 and mcp-sdk (tested with version 1.6.1.dev14+babb477 installed from Git main branch) are installed in your environment on macOS.
  2. 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)importmcp.server.stdioimportmcp.typesastypesfrommcp.server.lowlevelimportNotificationOptions, Serverfrommcp.server.modelsimportInitializationOptionsimportlogging# Add loggingimportasyncio# Add asyncio# Configure logginglogging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
    logger=logging.getLogger(__name__)
    # Create a server instancelogger.debug("Creating Server instance...")
    server=Server("example-server")
    logger.debug("Server instance created.")
    @server.list_prompts()asyncdefhandle_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()asyncdefhandle_get_prompt(
    name: str, arguments: dict[str, str] |None
    ) ->types.GetPromptResult:
    logger.debug(f"Handling get_prompt request for: {name}")
    ifname!="example-prompt":
    raiseValueError(f"Unknown prompt: {name}")
    returntypes.GetPromptResult(
    description="Example prompt",
    messages=[
    types.PromptMessage(
    role="user",
    content=types.TextContent(type="text", text="Example prompt text"),
    )
    ],
    )
    logger.debug("get_prompt handler defined.")
    asyncdefrun():
    logger.debug("run() function started.")
    asyncwithmcp.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 runawaitserver.run(
    read_stream,
    write_stream,
    init_options
    )
    logger.debug("server.run() finished.") # This likely won't be reached in normal operationif__name__=="__main__":
    logger.info("Starting asyncio event loop...")
    try:
    asyncio.run(run())
    exceptKeyboardInterrupt:
    logger.info("KeyboardInterrupt received, exiting.")
    exceptExceptionase:
    logger.critical(f"Unhandled exception in main loop: {e}", exc_info=True)
    finally:
    logger.info("asyncio event loop finished.")
  3. Run the script from the terminal: python mcp_stdio_test.py
  4. 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-level Server().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] to 0.30.3 (a version used in a previously working commit) did not resolve the issue.
  • Adding a time.sleep(10) before the run() 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 -->

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds confirmationNeeds confirmation that the PR is actually required or needed.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions