Skip to content

MCP startup hangs: synchronous HTTP call in osv_check blocks asyncio event loop #29184

@mmahao

Description

@mmahao

Summary

When the TUI gateway starts, discover_mcp_tools() calls _run_stdio() for each MCP server using stdio transport. Inside _run_stdio() (an async def method), check_package_for_malware() makes a synchronous blocking urllib.request.urlopen() call to api.osv.dev. When the OSV API SSL handshake hangs (intermittent network issue), the entire asyncio event loop is frozen — blocking MCP discovery for up to 120s.

Meanwhile, the TUI's startup timeout is only 15s, so the user sees "gateway startup timeout" / "error: gateway exited" and the TUI becomes unusable.

Root Cause

File: tools/mcp_tool.py, line 1272, inside async def _run_stdio():

malware_error = check_package_for_malware(command, args)

check_package_for_malware_query_osvurllib.request.urlopen() is synchronous blocking I/O running inside the asyncio event loop thread. No await, no thread pool — it blocks the loop directly.

File: tools/osv_check.py, line 150:

with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:

Python's urllib timeout doesn't always cover the SSL handshake phase — the crash log confirms the thread is stuck in ssl.py:do_handshake().

Stack Trace (from ~/.hermes/logs/tui_gateway_crash.log)

=== SIGTERM received · 2026-05-20 14:55:12 ===
main-thread stack:
  File "tui_gateway/entry.py", line 215, in main
    discover_mcp_tools()
  File "tools/mcp_tool.py", line 3312, in discover_mcp_tools
    tool_names = register_mcp_servers(servers)
  File "tools/mcp_tool.py", line 3262, in register_mcp_servers
    _run_on_mcp_loop(_discover_all, timeout=120)
  File "tools/mcp_tool.py", line 2208, in _run_on_mcp_loop
    return future.result(timeout=wait_timeout)
  (blocked waiting on future)

--- thread mcp-event-loop ---
  File "tools/mcp_tool.py", line 1272, in _run_stdio
    malware_error = check_package_for_malware(command, args)
  File "tools/osv_check.py", line 47, in check_package_for_malware
    malware = _query_osv(package, ecosystem, version)
  File "tools/osv_check.py", line 150, in _query_osv
    with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
  File "urllib/request.py", line 1348, in do_open
    h.request(req.get_method(), req.selector, req.data, headers, ...)
  File "http/client.py", line 1458, in connect
    self.sock = self._context.wrap_socket(self.sock, ...)
  File "ssl.py", line 1346, in do_handshake
    self._sslobj.do_handshake()

Reproduction

  1. Configure multiple MCP servers with stdio transport (uvx/npx commands)
  2. Run hermes --tui
  3. When api.osv.dev has intermittent connectivity or SSL issues, the gateway hangs during startup
  4. After 15s, TUI shows "gateway startup timeout"
  5. After up to 120s, discovery times out → but TUI is already in broken state

The malware check itself is fast when the API is reachable (~0.00s measured). The issue is the intermittent SSL handshake hang with no async fallback.

Environment

  • Hermes version: v2026.5.16-587-g340d2b6de
  • macOS 14.8.7, Python 3.11.4
  • 5 MCP servers configured (fetch/uvx, filesystem/npx, github/npx, intellij/sse, time/uvx)

Suggested Fix

Use asyncio.to_thread() to run the malware check off the event loop:

# tools/mcp_tool.py, line 1272
malware_error = await asyncio.to_thread(check_package_for_malware, command, args)

Or alternatively, use an async HTTP client (aiohttp, httpx) inside _query_osv so the call is non-blocking.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High — major feature broken, no workaroundcomp/toolsTool registry, model_tools, toolsetstool/mcpMCP client and OAuthtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions