# MCP namespace health diagnostics (#543)

Gitea MCP tools can be registered in the Python FastMCP server while the IDE's
live MCP namespace is still unusable. The failure usually appears as
`client is closing: EOF`, `transport closed`, or an empty response when calling
a tool such as `gitea_whoami`.

Do not treat static tool registration as proof that review or merge workflows
can proceed. A reviewer or merger flow must have **client-namespace** evidence
that the required tool is callable through the configured IDE MCP namespace.

## Probe sources (do not confuse them)

| Source | How obtained | Proves IDE namespace? |
| --- | --- | --- |
| `client_namespace` | Tool call through the IDE-managed MCP client | **Yes** |
| `offline_spawn` | `test_mcp_conn.py` subprocess JSON-RPC handshake | **No** (offline only) |

`gitea_assess_mcp_namespace_health` accepts `probe_source` and only sets
`ide_namespace_proven=true` for `client_namespace` success. Offline spawn
success never clears review/merge mutation gates.

## Client-namespace health check (canonical)

1. Through the IDE client, call a cheap tool on the target namespace
   (`gitea_whoami` or `gitea_list_profiles`).
2. Feed the live result into:

```text
gitea_assess_mcp_namespace_health(
    namespace="gitea-merger",          # or reviewer / author / tools
    registered_tools=[...],            # optional static list
    probe_result={"success": true, "result": {...}},
    probe_source="client_namespace",
)
```

3. A healthy client-namespace assessment is recorded in the MCP session and
   consulted by **live** `gitea_submit_pr_review` / `gitea_merge_pr` gates.
4. If the probe fails with EOF, recover via **client reconnect only** — see
   `docs/mcp-namespace-eof-recovery.md`. Do **not** kill PIDs or touch MCP
   config mtimes as a recovery procedure.

## Offline spawn probe (non-authoritative)

```bash
python3 test_mcp_conn.py --config ~/.gemini/config/mcp_config.json
```

This script spawns a **separate** server process from config, performs
JSON-RPC `initialize` → `tools/list` → `tools/call`, and classifies the
result with `probe_source=offline_spawn`. Use it for offline debugging of
launch command / registration. It does **not** prove the IDE-managed
namespace is healthy.

By default the script checks:

| Namespace | Required tool |
| --- | --- |
| `gitea-author` | `gitea_whoami` |
| `gitea-reviewer` | `gitea_whoami` |
| `gitea-merger` | `gitea_whoami` |
| `gitea-tools` | `gitea_list_profiles` |

## Recovery (canonical)

When a namespace returns EOF, follow
`docs/mcp-namespace-eof-recovery.md` in order:

1. Confirm blast radius (Gitea namespace vs all MCP servers).
2. **Reconnect the namespace through the client** (IDE reconnect / relaunch).
3. Do not repair via shell imports, raw JSON-RPC, PID kills, or config mtime
   touches — those do not restore the client's closed transport.
4. Re-verify the **specific** required tool through the target namespace.
5. Resume review/merge only after a successful `client_namespace` assessment.

## Enforcement

1. **State machine (read-only):** feed `blocks_merge_workflow` from a
   `client_namespace` assessment into
   `gitea_assess_review_merge_state_machine(live_namespace_broken=...)`.
2. **Live mutations:** `gitea_submit_pr_review` and `gitea_merge_pr` call
   `_live_namespace_health_gate` and fail closed when the session has a
   recorded unhealthy or non-client probe for the required namespace
   (`gitea-reviewer` for review, `gitea-merger` for merge).

When blocked, repair the IDE namespace and re-record a healthy
`client_namespace` assessment before retrying the mutation.

## Connected vs Attached Tool Surface (#708)

MCP servers can report **Connected** at the CLI / host inventory layer while the **active LLM session exposes none of their tool namespaces**.

### Core principle

* **Connected status at host layer ≠ attached tools in active session.**
* Required preflight proof is **live tool visibility + `gitea_whoami` call** through the target namespace, not host `Connected` status alone.
* When servers report Connected but namespaces are absent from attached tools, classify as `mcp_connected_namespaces_missing`.

### Forbidden unsafe fallbacks

When `mcp_connected_namespaces_missing` is detected, workflows must **fail closed** and must **never** encourage or perform:

* direct imports of MCP server Python modules
* CLI or raw Gitea API mutations as a substitute for native tools
* profile hopping to another MCP profile/namespace to bypass the empty session
* session-state overrides or hand-edited session/ledger files
* process kills (`pkill`), config mtime touches, or `.env` edits

Only sanctioned recovery: **client reconnect path**, followed by full preflight (`whoami` → capability resolve → task).

### Native detection tool

`gitea_assess_mcp_namespace_attachment` classifies the condition and records it in
the session. Pass the namespaces the host reports Connected and the namespaces
actually attached to the active session tool surface:

| Argument | Meaning |
|---|---|
| `connected_servers` | Namespaces the host/CLI reports Connected |
| `attached_session_namespaces` | Namespaces exposed in the active session tool surface |
| `required_namespaces` | Namespaces this workflow needs (defaults to the role namespaces) |
| `discovery_cache_hit` / `discovery_cache_age_seconds` | Client tool-discovery cache state |
| `auto_attach_attempted` / `auto_attach_succeeded` | Whether the runtime auto-attached |
| `session_tool_snapshot_at` / `namespace_connected_at` | Epoch seconds, to detect startup ordering races |

It returns `discovery_status`
(`namespaces_attached` | `connected_but_namespaces_missing` | `disconnected`),
`missing_namespaces`, `proof_of_connected_vs_attached` (per namespace
`{connected, attached}`), `error_type`, `reconnect_required`, `auto_recovered`,
`startup_ordering_race`, `late_attaching_namespaces`, `sanctioned_recovery_tool`,
and a reconnect-only `exact_next_action`.

### Startup ordering

`session_tool_snapshot_at` earlier than a namespace's `namespace_connected_at`
means that namespace could not have been in the session snapshot, however healthy
it looks now. That is reported as `startup_ordering_race` with the affected
namespaces listed — the multi-role parallel-connect case where Connected flips
true after the session tool list was already captured.

### Fail-closed gate

The recorded verdict gates mutations, mirroring the #543 health gate: a namespace
that has not been assessed does not gate, but one recorded Connected-but-unattached
blocks the mutation whose role namespace it is
(`review_pr` / `submit_review` → `gitea-reviewer`, `merge_pr` → `gitea-merger`,
`work_issue` / `create_pr` → `gitea-author`). `gitea_submit_pr_review` and
`gitea_merge_pr` return the block reason plus the hard-stop policy string, and the
only offered recovery is `gitea_request_mcp_reconnect`.

### Telemetry

The `telemetry` block carries `connected_count`, `attached_count`,
`required_count`, `missing_count`, `discovery_status`, `discovery_cache_hit`,
`discovery_cache_age_seconds`, `reconnect_required`, `auto_attach_attempted`,
`auto_recovered`, `startup_ordering_race`, and `error_type`. It contains namespace
names and counts only — never tokens, endpoints, env values, or filesystem paths.

