Skip to content

fix(mcp): isolate local scan source failures - #646

Merged
xintaofei merged 2 commits into
xintaofei:mainfrom
dawNotPoi:fix/632-isolate-mcp-source-errors
Sep 3, 2026
Merged

fix(mcp): isolate local scan source failures#646
xintaofei merged 2 commits into
xintaofei:mainfrom
dawNotPoi:fix/632-isolate-mcp-source-errors

Conversation

@dawNotPoi

Copy link
Copy Markdown
Contributor

Summary

  • continue local MCP discovery when one application config cannot be read or parsed
  • keep server lookup and all mutation paths strict so writes never use an incomplete view
  • preserve source priority, app union, and the OpenClaw/Kimi merge rules

Testing

  • cargo test --no-default-features --lib commands::mcp::tests::
  • cargo test --no-default-features --lib
  • cargo check --no-default-features --lib --bin codeg-server
  • cargo clippy --no-default-features --lib --bin codeg-server -- -D warnings
  • cargo check --no-default-features --bin codeg-mcp
  • cargo clippy --no-default-features --bin codeg-mcp -- -D warnings

Closes #632

@dawNotPoi
dawNotPoi marked this pull request as ready for review September 3, 2026 09:44
…omplete scan

xintaofei#632's file is a 0-byte `~/.gemini/config/mcp_config.json` — Antigravity
touches it into existence before it ever writes a server. serde reports
`EOF while parsing a value at line 1 column 0`, so isolating the source
leaves that agent permanently invisible AND unwritable: every writer starts
by reading the file it is about to change, so nothing can put content into
it again. Treat empty and whitespace-only as "nothing configured", the rule
the Hermes reader already applied to its own config.

Two sources could still fail without the scan noticing, which would make the
fail-closed half of the isolation a no-op:

- `read_hermes_servers` swallowed I/O errors and invalid YAML into an empty
  map, so Hermes went silently absent instead of reporting a failure.
- Every JSON reader plus the Codex and Grok TOML readers gated on
  `Path::exists()`, which is false for ANY failed stat — a permission wall
  or a symlink loop read as "not configured". `read_config_to_string` makes
  the read itself the test and carries the path into the error detail.

Replace the scan-mode flag with an explicit write guard. `scan_local_servers`
returns `LocalMcpScan { servers, warnings }` and no longer fails, while
`mcp_upsert_local_server` and `mcp_set_server_apps` call
`require_complete_scan` before their first write — the strict
`find_local_server` on the upsert path ran after the writes, reporting
failure on a save that had already landed in fourteen configs.

Surface the warnings instead of only logging them: the settings page names
the agent whose config could not be read and why, and disables Save and
Create while any source is degraded, so a draft seeded from a partial scan
cannot be submitted after an out-of-band repair.

Closes xintaofei#632
@xintaofei

Copy link
Copy Markdown
Owner

Thanks for this — the reader table is a real improvement over the 15 copy-pasted loops, and I kept it as-is. I'd independently worked on #632 before seeing this PR, so rather than open a competing one I've pushed my work on top of your commit (bf71cdde, fast-forward, your commit untouched underneath). Happy to discuss any of it.

What I kept from your commit: local_mcp_readers() and the single generic loop. Adding a 15th agent is now one line instead of another copy-paste block. Your successful_scan_preserves_openclaw_and_kimi_merge_rules test stays too — it covers a merge rule that had no test before.

What changed, and why.

  1. An empty config is not a corrupt one. 【bug】本地 MCP 只解析 .gemini\config\mcp_config.json,空文件即整体加载失败、已配置的 MCP 全不显示 #632's file is a 0-byte ~/.gemini/config/mcp_config.json — Antigravity touches it into existence before it ever writes a server, so EOF while parsing a value at line 1 column 0 is exactly what a healthy uninitialised install produces. Isolating the source fixes the list, but that agent then stays invisible forever and unwritable: every writer starts with read_json_file on the file it is about to change (e.g. upsert_antigravity_server_at), so nothing can put content into it again. The file is empty → the write is refused → the file stays empty. read_json_file now treats empty and whitespace-only as {}, which is the rule the Hermes reader already applied to its own config.

  2. Two sources could fail without the scan noticing, which made the fail-closed half of the isolation a no-op:

    • read_hermes_servers swallowed I/O errors and invalid YAML into Ok(empty), so Hermes went silently absent rather than reporting a failure. Strict mode then believed the scan was complete.
    • Every JSON reader plus the Codex and Grok TOML readers gated on if !path.exists(). Path::exists() is false for any failed stat — a permission wall or a symlink loop reads as "not configured". New read_config_to_string makes the read itself the test and carries the path into the error detail. (Verified with a symlinked ~/.gemini/settings.json loop: now surfaces Too many levels of symbolic links (os error 62) instead of silently reporting the agent as unconfigured.)
  3. Strict mode replaced by an explicit write guard. The scan is now infallible and returns LocalMcpScan { servers, warnings }; mcp_upsert_local_server and mcp_set_server_apps call require_complete_scan before their first write. Under Strict, mcp_upsert_local_server's find_local_server ran after the write loop as a reload — so a degraded scan reported failure on a save that had already landed in fourteen configs.

  4. The warnings are surfaced, not just logged. tracing::warn! is invisible to the person whose config is broken; they just see an agent quietly missing from the list. The settings page now names the agent and the reason, and disables Save/Create while any source is degraded — otherwise a draft seeded from a partial scan could be submitted after an out-of-band repair, carrying a stale app list that removes the server from agents the user never unchecked.

Verification (all green on bf71cdde):

  • cargo clippy --all-targets --features test-utils -- -D warnings
  • cargo test --features test-utils — 3354 passed, 0 failed
  • cargo clippy --no-default-features --bin codeg-server --lib -- -D warnings
  • cargo test --no-default-features --bin codeg-server --lib — 3323 passed, 0 failed
  • cargo clippy --no-default-features --bin codeg-mcp -- -D warnings
  • tsc --noEmit, eslint, prettier --check, vitest run (402 files / 5746 tests), pnpm build

Plus an end-to-end check against a real codeg-server with a fake $HOME: 0-byte config → 200 with all servers and no warning; corrupt config → 200 with servers plus a warning naming the file, 422 on save with the other configs provably unmutated, save succeeds once repaired.

One assertion of yours is inverted: empty_antigravity_file_remains_a_reader_errorempty_antigravity_file_reads_as_no_servers, for the reason in (1). strict_scan_still_fails_on_a_single_broken_source became a_failed_source_is_refused_by_the_write_guard, testing the same invariant through the guard that replaced the mode flag.

@xintaofei
xintaofei merged commit 0f7d550 into xintaofei:main Sep 3, 2026
7 checks passed
xintaofei added a commit that referenced this pull request Sep 3, 2026
Conversation forks are now more reliable, staying on the reply and session you chose while carrying their settings into the next prompt.
Pi questions, MCP settings, and Windows network-share launches are more dependable too.

## New

- **DeepSeek can now fork a conversation at the exact reply you picked** (deepseek-acp 0.8.0), instead of falling back to the end of the session.

## Improved

- **“Fork from here” is now the single, clearer way to branch a conversation** — the redundant composer shortcut is gone, and the reply action stays visible with an explanation while a response is still running.

## Fixed

- **Conversation forks now reliably open on the intended new session and are ready for the very next prompt**, preserving the selected turn plus supported model, effort, and mode settings without jumping back, hanging, or losing a fast follow-up.
- **Pi’s multiple-choice prompts now use the same interactive question card as other agents**, and the choice you make remains visible in the transcript. (#644)
- **One unreadable agent config no longer takes down the MCP settings page** — healthy configs still load, empty files are handled normally, and codeg identifies the problem and pauses writes until every existing setting can be preserved safely. (#646, @dawNotPoi)
- **Windows agents launched from UNC workspaces now start in the correct project directory**, including `.cmd` and `.bat` launchers used by npm-installed agents in WSL or network-share projects. (#638, @damiandelmas)

Thanks to @dawNotPoi and @damiandelmas for contributing to this release.

-----------------------------

# 发布版本 0.30.2

会话分叉现在更可靠了:它会稳稳留在你选中的回复和新会话上,并带着原有设置继续接收下一条消息。
Pi 问答、MCP 设置,以及 Windows 网络路径下的智能体启动也变得更加稳定。

## 新增

- **DeepSeek 现在能从你选中的那条回复精确分叉**(随 deepseek-acp 0.8.0 到位),不再悄悄退回到会话末尾。

## 改进

- **「从这里分叉」成为统一而清楚的会话分叉入口**——输入框旁重复的快捷入口已移除,回复生成期间按钮也会留在原位并说明暂时不可用,不再忽隐忽现。

## 修复

- **会话分叉现在会可靠地打开并留在预期的新会话中,紧接着发出的下一条消息也能正常发送**,选中的分叉点和原会话支持的模型、思考强度及模式都会保留,不再跳回旧会话、卡住或让快速跟进的回复消失。
- **Pi 发起的多选提问现在会显示为统一的可交互问答卡片**,选项可以直接点击,回答结果也会保留在会话记录中。(#644)
- **某个智能体的配置文件不可读时,MCP 设置页不再整页失效**——其余配置仍会正常显示,空白配置文件也能正常处理,同时 codeg 会指出问题来源,并在无法安全保留全部设置时暂停写入。(#646@dawNotPoi)
- **Windows 现在能从 UNC 工作区的正确项目目录启动智能体**,包括 WSL 或网络共享项目中由 npm 安装的 `.cmd`、`.bat` 启动器。(#638@damiandelmas)

感谢 @dawNotPoi@damiandelmas 为本次发布做出的贡献。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【bug】本地 MCP 只解析 .gemini\config\mcp_config.json,空文件即整体加载失败、已配置的 MCP 全不显示

2 participants