Files
Gitea-Tools/docs/mcp-namespace-eof-recovery.md
T
sysadminandClaude Opus 4.8 efa7190041 docs: add MCP namespace client is closing: EOF recovery runbook (#543)
Adds docs/mcp-namespace-eof-recovery.md documenting the correct recovery
path when a Gitea MCP namespace (gitea-author/reviewer/merger/tools)
returns `client is closing: EOF`.

Satisfies acceptance criterion 7 of #543: symptom, root cause (closed
client transport vs a live-but-stale process), the sanctioned
reconnect/relaunch sequence, diagnostics to capture, and how
test_mcp_conn.py reproduces the registered-vs-callable gap. Distinguishes
this transport-close failure from the ps-based stale-runtime family in
#531/#544 and reinforces the no-direct-import guard (#558).

Docs-only; no code or test behavior changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-09 11:55:42 -04:00

5.3 KiB

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.

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

Do the steps in order. Stop as soon as a live 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, or killing PIDs to force a respawn 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.

  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.

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.
  • 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?).

Reproducing the registered-vs-callable gap

test_mcp_conn.py performs a full JSON-RPC handshake (initializeinitializedtools/list) plus a direct tool call against a namespace, which is what distinguishes "registered in FastMCP" from "callable through the client." Use it to confirm a namespace is genuinely reachable after a reconnect. It probes a namespace end-to-end; a namespace that returns EOF at the client fails this handshake, while ps-only checks still report the process as present.

Do-not list during EOF recovery

  • Do not retry a blocked merge/adoption until the required tool is confirmed callable through the merger-authorized namespace (see #543 canonical handoff).
  • 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.
  • #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.