- Distinguish client_namespace vs offline_spawn probe sources; only IDE client probes prove namespace health and feed mutation gates. - Record assessments in session; gate gitea_submit_pr_review and gitea_merge_pr when client-namespace health is unhealthy/non-proven. - Mark test_mcp_conn.py offline-only; align recovery docs to client reconnect (no PID-kill/config-touch as canonical recovery). - Rebase onto current master so #590 ledger isolation keeps TestMergePR.test_unknown_profile_blocks green.
This commit is contained in:
@@ -21,7 +21,8 @@ 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.
|
||||
- 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
|
||||
@@ -36,9 +37,9 @@ 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
|
||||
## Recovery path (canonical — client reconnect only)
|
||||
|
||||
Do the steps in order. Stop as soon as a live namespace call succeeds.
|
||||
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
|
||||
@@ -53,22 +54,28 @@ Do the steps in order. Stop as soon as a live namespace call succeeds.
|
||||
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.
|
||||
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.
|
||||
`*_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
|
||||
|
||||
@@ -77,33 +84,35 @@ 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.
|
||||
- **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?).
|
||||
|
||||
## Reproducing the registered-vs-callable gap
|
||||
## Offline spawn probe (non-authoritative)
|
||||
|
||||
`test_mcp_conn.py` performs a full JSON-RPC handshake
|
||||
(`initialize` → `initialized` → `tools/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.
|
||||
`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 namespace (see #543 canonical handoff).
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# MCP namespace EOF recovery
|
||||
# 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
|
||||
@@ -6,19 +6,54 @@ live MCP namespace is still unusable. The failure usually appears as
|
||||
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 live namespace evidence that
|
||||
the required tool is callable through the configured namespace.
|
||||
can proceed. A reviewer or merger flow must have **client-namespace** evidence
|
||||
that the required tool is callable through the configured IDE MCP namespace.
|
||||
|
||||
## Required namespace probes
|
||||
## Probe sources (do not confuse them)
|
||||
|
||||
Run the health check after changing MCP config, switching branches that affect
|
||||
server code, or seeing EOF from any Gitea namespace:
|
||||
| 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
|
||||
```
|
||||
|
||||
By default the script checks these namespaces and required tools:
|
||||
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 |
|
||||
| --- | --- |
|
||||
@@ -27,50 +62,27 @@ By default the script checks these namespaces and required tools:
|
||||
| `gitea-merger` | `gitea_whoami` |
|
||||
| `gitea-tools` | `gitea_list_profiles` |
|
||||
|
||||
Use `--namespace gitea-reviewer` to test only one namespace. A passing probe
|
||||
requires:
|
||||
## Recovery (canonical)
|
||||
|
||||
1. The namespace exists in the MCP config.
|
||||
2. JSON-RPC initialize succeeds.
|
||||
3. `tools/list` returns the required tool.
|
||||
4. `tools/call` successfully invokes the required tool.
|
||||
When a namespace returns EOF, follow
|
||||
`docs/mcp-namespace-eof-recovery.md` in order:
|
||||
|
||||
## Recovery steps
|
||||
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.
|
||||
|
||||
When a namespace returns EOF:
|
||||
## Enforcement
|
||||
|
||||
1. Note the reported namespace, required tool, PID, profile, environment
|
||||
summary, and config path.
|
||||
2. Stop any stale MCP server process for that PID.
|
||||
3. Reload or touch the MCP config so the IDE reconnects the namespace.
|
||||
4. Verify the namespace command, args, profile env, and checkout path point at
|
||||
the current repository.
|
||||
5. Re-run `python3 test_mcp_conn.py --namespace <name>`.
|
||||
6. Resume review or merge work only after the required tool call passes.
|
||||
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).
|
||||
|
||||
The read-only `gitea_assess_mcp_namespace_health` tool can classify probe
|
||||
evidence supplied by a client. It intentionally distinguishes
|
||||
`required_tool_registered=true` from `required_tool_callable=false`; the latter
|
||||
must block reviewer and merger workflows until the live namespace is repaired.
|
||||
|
||||
## Enforcement in the review/merge state machine
|
||||
|
||||
The block is not advisory. Feed the `blocks_merge_workflow` verdict from
|
||||
`gitea_assess_mcp_namespace_health` into
|
||||
`gitea_assess_review_merge_state_machine` as `live_namespace_broken`:
|
||||
|
||||
```text
|
||||
health = gitea_assess_mcp_namespace_health(namespace="gitea-merger", ...)
|
||||
state = gitea_assess_review_merge_state_machine(
|
||||
state_completion=...,
|
||||
pre_merge_gates=...,
|
||||
live_namespace_broken=health["blocks_merge_workflow"],
|
||||
)
|
||||
# state["merge"]["allowed"] is False whenever the live namespace is broken,
|
||||
# even when every review state and pre-merge gate is otherwise satisfied.
|
||||
```
|
||||
|
||||
When `live_namespace_broken=True`, `assess_workflow_blockers`,
|
||||
`can_approve`, `can_merge`, and `workflow_status` all fail closed. This is the
|
||||
guard that stops a merge from proceeding on a false-ready namespace, as in the
|
||||
PR #418 halt that motivated this work (#543).
|
||||
When blocked, repair the IDE namespace and re-record a healthy
|
||||
`client_namespace` assessment before retrying the mutation.
|
||||
|
||||
Reference in New Issue
Block a user