# Recovering from `client is closing: EOF` on a Gitea MCP namespace (#543) ## Symptom A tool call through a Gitea MCP namespace — `gitea-author`, `gitea-reviewer`, `gitea-merger`, or the shared `gitea-tools` namespace — fails immediately with: ``` client is closing: EOF ``` Every subsequent call to that same namespace returns the same error, including cheap read tools such as `gitea_whoami` and `gitea_list_profiles`. Other MCP servers registered with the same client (for example `context7`) keep working, so this is **not** a global MCP-client outage. ## Why this is not a code defect This failure is a **transport-level** condition in the IDE / MCP client manager, not a missing or broken tool: - The tool can be present and registered in the Python `FastMCP` tool manager. - Direct Python inspection of the server confirms the tool exists. - Running the server manually and sending JSON-RPC over stdio works fine (offline spawn) — that path does **not** prove the IDE namespace is healthy. The client manager entered a closed state after the backing subprocess for that namespace terminated (or was killed) behind its back. Once closed, the client does **not** re-spawn the child on the next tool call — it just replays `client is closing: EOF`. The OS process may even still be alive if a parent language-server process is holding the stdio pipes open. This is the canonical "registered in FastMCP ≠ callable through the namespace" false-ready state. It is distinct from the **stale-runtime** family in #531 / #544, where the process is reachable but running behind `master`; that case is detected by the `ps`-based `_check_mcp_runtimes_diagnostics` in `gitea_mcp_server.py`. The EOF case is a dead/closed transport, not a stale one, so the `ps` check alone will not surface it. ## Recovery path (canonical — client reconnect only) Do the steps in order. Stop as soon as a live **client-namespace** call succeeds. 1. **Confirm the blast radius.** Call a cheap read tool on the failing namespace (`gitea_whoami` or `gitea_list_profiles`). Then call the same tool on a different MCP server (e.g. `context7`). - Only the Gitea namespace fails → single-namespace transport close. Continue. - Every server fails → restart the whole MCP client, not just one namespace. 2. **Reconnect the namespace through the client, not the shell.** Use the IDE / client MCP-reconnect action for that server entry (in Claude Code: `/mcp` → reconnect the affected `gitea-*` server). Reconnecting forces the client to spawn a fresh subprocess and re-open the pipe. This clears the closed-client state that a bare `kill`/respawn from a terminal does **not**. 3. **Do not "fix" it by importing the server or poking the process.** Reaching for `python -c 'import gitea_mcp_server ...'`, raw JSON-RPC from a shell, killing PIDs to force a respawn, or touching MCP config mtimes does **not** restore the *client's* view of the namespace and violates the daemon-import guard (#558, `docs/mcp-daemon-import-guard.md`). The only sanctioned repair is a **client reconnect / relaunch**. 4. **Verify through the same path the workflow will use.** After reconnect, call the specific tool the blocked workflow needs — not just any tool — through the target namespace. For a merge that means calling the merger-authorized adoption/merge tool through `gitea-merger`. A green `gitea_whoami` on one namespace does **not** prove another namespace or another tool is callable. Record success with: ```text gitea_assess_mcp_namespace_health(..., probe_source="client_namespace") ``` 5. **If reconnect does not clear it,** relaunch the client entirely, then repeat step 4. If EOF persists after a full relaunch, the backing subprocess is failing to start — inspect its stderr / launch config (command path, venv, `*_MCP_CONFIG`, `*_MCP_PROFILE` env) rather than retrying the call. Still do not use PID kill or config-touch as the primary recovery. ## Diagnostics to capture when reporting EOF Include all of these so the failure is actionable and reproducible: - **Namespace name** that returned EOF (`gitea-author` / `gitea-reviewer` / `gitea-merger` / `gitea-tools`). - **Tool** that was called and the **exact** error string. - **PID** of the backing process (if any) and whether it was still alive (informational only — not a recovery action). - **Profile / env** for that namespace (execution profile, `*_MCP_PROFILE`, worktree binding such as `GITEA_AUTHOR_WORKTREE`). - **Config path** the client launched the server from. - Result of the **cross-server control** call (did `context7` succeed?). ## Offline spawn probe (non-authoritative) `test_mcp_conn.py` performs a full JSON-RPC handshake against a **fresh subprocess** (`initialize` → `initialized` → `tools/list` → `tools/call`) and classifies with `probe_source=offline_spawn`. That is useful for offline launch/registration debugging. It is **not** proof the IDE-managed namespace is healthy. See `docs/mcp-namespace-health.md`. ## Do-not list during EOF recovery - Do **not** retry a blocked merge/adoption until the required tool is confirmed callable through the merger-authorized **client** namespace (see #543). - Do **not** clean, reset, or rebind a **foreign** worktree to work around the error. - Do **not** bypass the namespace with direct imports, raw API/curl, or in-memory state restoration. - Do **not** kill MCP PIDs or touch config mtimes as a substitute for client reconnect. ## Related - #531 / #544 — stale-runtime detection (`ps`-based); sibling failure mode. - #558 / `docs/mcp-daemon-import-guard.md` — why shell imports are not a repair. - `docs/mcp-client-registration.md` — per-server registration contract. - `docs/mcp-namespace-health.md` — probe sources and mutation enforcement.