stacklok / stacklok/mecatl

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

Open
#1,695 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
152
Forks
16
Avg merge
14h 48m
Merged PRs (30d)
536

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in internal/adapter/mcp/mcp.go, reading defaultConnectTimeout, NewManager, Build, and the buildCatalog Phase A path to understand where MCP connections gate startup. Done should be defined by design review, but the issue calls for a configurable timeout, visible connection status, or startup that no longer waits for one unreachable server.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.