Skip to content

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

Open
@jlcases

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)
    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.")
  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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions