Skip to content

mecatui/mecated startup blocks up to 30s on a single unreachable MCP server #1695

Description

@jhrozek

Summary

mecatui/mecated startup (app.Build) blocks synchronously on connecting
every MCP server before the embedded server (and therefore the TUI) becomes
usable. If any single MCP server is unreachable — a network blip, a wrong URL,
a crashed/corrupted local ToolHive workload — startup stalls for the full,
hardcoded defaultConnectTimeout (30s), even when every other server connects
in milliseconds. There is no way to configure this timeout down, no
degrade-and-continue path, and no visible progress during the wait, so from
the outside a single dead MCP server is indistinguishable from a hung/broken
build.

Reproduction (what we hit)

Root-caused via a private --diagnostics-log capture. Timestamped evidence:

time=2026-09-18T10:41:59.302+02:00  ← last line before the gap
time=2026-09-18T10:42:29.319+02:00  level=WARN msg="MCP server unreachable; skipping" name=github reason=unavailable
time=2026-09-18T10:42:29.320+02:00  level=WARN msg="MCP server unreachable; skipping" name=toolhive-doc-mcp-remote reason=unavailable
time=2026-09-18T10:42:29.320+02:00  level=INFO msg="MCP servers connected" servers=6 tools=39

That's a 30.017s gap, matching defaultConnectTimeout = 30 * time.Second in
internal/adapter/mcp/mcp.go:57 to the millisecond. Everything else in the
build (session store, permission config, agent defs, LLM provider, skills,
memory, Team/Parallel/Schedule tools, scheduler) completed in under 300ms
total — this one stalled connection is the entire startup delay.

In our case the underlying cause was a corrupted Docker overlay2 layer for
the ToolHive-managed github MCP server container (thv status github /
docker rm both failed with input/output error / read-only file system
against the Docker Desktop VM disk) — but the mecatl-side behavior is the
real issue: this class of failure (one unreachable MCP server) will always
cost every user a 30s freeze on mecatui/mecated startup, for any reason a
server can be down.

Why this matters

  • internal/adapter/mcp/mcp.go NewManager connects servers concurrently
    under a bounded semaphore, but Build() still calls wg.Wait() and does
    not return until every server has connected or timed out
    (internal/adapter/mcp/mcp.go ~L1258). One dead server holds back the
    other N that connected in milliseconds.
  • defaultConnectTimeout (30s) is a hardcoded constant with no CLI flag or
    settings.yaml knob — not even for local/loopback ToolHive servers where
    30s is enormous relative to a real connect.
  • There's no degrade-and-continue path: MCP tool mounting happens once during
    buildCatalog (Phase A of catalog construction), so there's no way to
    bring up the core tool set immediately and attach straggler MCP servers'
    tools later if/when they connect.
  • Nothing is emitted to the user during the wait to explain why startup is
    frozen — from the outside, a hung MCP server and a genuinely broken build
    look identical, which is what made this hard to diagnose in the first
    place.

Suggested direction (not prescriptive — open to design review)

  1. Make the per-server (and/or overall) MCP connect timeout configurable
    (CLI flag + settings.yaml), so an operator with known-flaky or
    loopback-only servers can tighten it.
  2. Consider connecting MCP servers in the background after the embedded
    server/TUI is otherwise ready, surfacing tools as each server comes
    online instead of gating the whole Build() on the slowest one. This is
    a bigger architectural change (catalog assembly currently assumes a
    build-once snapshot) and would need its own design pass.
  3. At minimum, emit a visible "connecting to MCP server(s): X, Y, Z..."
    diagnostic/status line the moment connects start, so a long wait is
    attributable in real time instead of only after the fact via diag.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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