Skip to content

Commit da10967

Browse files
author
hsliu_ustc
committed
Installer: first-class ZCode target — merge mcp.servers into ~/.zcode/cli/config.json, skills in ~/.zcode/skills
1 parent 8354321 commit da10967

5 files changed

Lines changed: 132 additions & 9 deletions

File tree

‎QUICKSTART.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Repo knowledge lives at the edge in `adapters/<repo>/` (human-gated
175175
`manifest.yaml` + agent-established, evidence-gated `profile/`) — never in
176176
`src/`. `PROFILE_BRIEFING_ENABLED=0` runs the {no-profile} eval ablation arm.
177177

178-
## 10. Advanced: MCP server (Claude Code / Codex / Cursor)
178+
## 10. Advanced: MCP server (Claude Code / Codex / Cursor / ZCode)
179179

180180
```bash
181181
pip install -e '.[mcp]' # optional extra, kept out of the base install

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ install.cmd --repo-path D:\path\to\vllm-omni
3838
./install-mcp.sh --repo-path /path/to/vllm-omni
3939
```
4040

41-
安装器会识别本机的 Codex、Claude Code 和 Cursor,并安装 MCP、`imreview`、
41+
安装器会识别本机的 Codex、Claude Code、Cursor 和 ZCode,并安装 MCP、`imreview`、
4242
`imdesign`、`imcifix` 和 `imupdate` Skill。未识别到已知 Agent 时,会生成标准
4343
`infermatrix-copilot.mcp.json` 供其他 MCP 客户端导入。
4444

4545
Codex 重启后用 `$imreview` / `$imdesign` / `$imcifix` / `$imupdate`,也可以先运行 `/skills` 查找;
46-
`/imreview` 不是 Codex 的 Slash Command。Claude Code 和 Cursor 仍使用
46+
`/imreview` 不是 Codex 的 Slash Command。Claude Code、Cursor 和 ZCode 仍使用
4747
`/imreview` / `/imdesign` / `/imcifix` / `/imupdate`。
4848

4949
安装器还会创建 `~/.infermatrix-copilot/.env`。Direct 不需要模型密钥;使用
@@ -84,7 +84,7 @@ Use InferMatrixCopilot in Direct mode to review this PR.
8484
$imreview https://github.com/vllm-project/vllm-omni/pull/5172
8585
$imreview
8686
87-
# Claude Code / Cursor
87+
# Claude Code / Cursor / ZCode
8888
/imreview https://github.com/vllm-project/vllm-omni/pull/5172
8989
或者说
9090
“帮我审核一下这个pr xxxx,用知识库”

‎doc/MCP.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ install.cmd --repo-path D:\path\to\vllm-omni
2424
两个入口共用 `scripts/install_mcp.py`,但用户不需要安装或直接运行 Python:
2525
入口会自动安装 `uv`,再由 `uv` 提供隔离运行环境。安装器会:
2626

27-
- 自动识别 Codex、Claude Code 和 Cursor,检测到几个就安装几个;
28-
- 保留 Cursor 已有配置并先备份;
27+
- 自动识别 Codex、Claude Code、Cursor 和 ZCode,检测到几个就安装几个;
28+
- 保留 Cursor / ZCode 已有配置并先备份;
2929
- 没识别到已知 Agent 时生成 `infermatrix-copilot.mcp.json`,不会直接失败。
3030
- 创建 `~/.infermatrix-copilot/.env` 作为 Strict 的稳定配置入口;安装时传入
3131
`--repo-path` 会写入本地 vLLM-Omni checkout。模型密钥不会自动复制,使用
@@ -42,7 +42,7 @@ install.cmd --repo-path D:\path\to\vllm-omni
4242
$imreview <PR URL>
4343
$imupdate <local path, repository name, alias, or URL>
4444
45-
# Claude Code / Cursor
45+
# Claude Code / Cursor / ZCode
4646
/imreview <PR URL>
4747
/imupdate <local path, repository name, alias, or URL>
4848
```

‎scripts/install_mcp.py‎

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,60 @@ def _install_cursor(config_root: Path) -> None:
292292
_install_skills(cursor_root / "skills")
293293

294294

295+
def _install_zcode(config_root: Path) -> None:
296+
zcode_root = config_root / ".zcode"
297+
config_path = zcode_root / "cli" / "config.json"
298+
config_path.parent.mkdir(parents=True, exist_ok=True)
299+
300+
if config_path.exists():
301+
try:
302+
config = json.loads(config_path.read_text(encoding="utf-8"))
303+
except (OSError, json.JSONDecodeError) as exc:
304+
raise InstallError(
305+
f"ZCode config is invalid and was not changed: {config_path}"
306+
) from exc
307+
if not isinstance(config, dict):
308+
raise InstallError(
309+
f"ZCode config must be an object: {config_path}"
310+
)
311+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
312+
shutil.copy2(
313+
config_path,
314+
config_path.with_name(f"{config_path.name}.{timestamp}.bak"),
315+
)
316+
else:
317+
config = {}
318+
319+
# ZCode nests servers under mcp.servers and also keeps hooks and plugin
320+
# state in this file, so merge instead of rewriting the document.
321+
mcp = config.setdefault("mcp", {})
322+
if not isinstance(mcp, dict):
323+
raise InstallError(
324+
f"ZCode config mcp section must be an object: {config_path}"
325+
)
326+
servers = mcp.setdefault("servers", {})
327+
if not isinstance(servers, dict):
328+
raise InstallError(
329+
f"ZCode config mcp.servers must be an object: {config_path}"
330+
)
331+
servers[SERVER_NAME] = {
332+
"type": "stdio",
333+
"command": SERVER_COMMAND[0],
334+
"args": SERVER_COMMAND[1:],
335+
}
336+
config_path.write_text(
337+
json.dumps(config, ensure_ascii=False, indent=2) + "\n",
338+
encoding="utf-8",
339+
)
340+
_install_skills(zcode_root / "skills")
341+
342+
343+
def _zcode_installed(config_root: Path) -> bool:
344+
if shutil.which("zcode"):
345+
return True
346+
return (config_root / ".zcode").exists()
347+
348+
295349
def _cursor_installed(config_root: Path) -> bool:
296350
if shutil.which("cursor") or shutil.which("cursor-agent"):
297351
return True
@@ -311,6 +365,8 @@ def _detect_agents(config_root: Path) -> list[str]:
311365
agents.append("claude")
312366
if _cursor_installed(config_root):
313367
agents.append("cursor")
368+
if _zcode_installed(config_root):
369+
agents.append("zcode")
314370
return agents
315371

316372

@@ -339,7 +395,7 @@ def _parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
339395
parser.add_argument(
340396
"--agent",
341397
action="append",
342-
choices=("codex", "claude", "cursor"),
398+
choices=("codex", "claude", "cursor", "zcode"),
343399
help="Override automatic Agent detection. May be repeated.",
344400
)
345401
parser.add_argument(
@@ -392,6 +448,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
392448
"codex": _install_codex,
393449
"claude": _install_claude,
394450
"cursor": _install_cursor,
451+
"zcode": _install_zcode,
395452
}
396453
for agent in agents:
397454
try:
@@ -415,7 +472,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
415472
)
416473
if any(agent != "codex" for agent in agents):
417474
print(
418-
"Claude/Cursor: restart, then run: /imreview <PR URL>, "
475+
"Claude/Cursor/ZCode: restart, then run: /imreview <PR URL>, "
419476
"/imcifix <issue URL>, or /imupdate <repository>"
420477
)
421478
return 0

‎test/test_install_mcp_script.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import json
33
from pathlib import Path
44

5+
import pytest
6+
57

68
ROOT = Path(__file__).resolve().parents[1]
79
INSTALLER = ROOT / "scripts" / "install_mcp.py"
@@ -103,6 +105,70 @@ def test_cursor_install_preserves_existing_config(tmp_path):
103105
assert str(ROOT) in text
104106

105107

108+
def test_zcode_install_merges_nested_mcp_servers(tmp_path):
109+
installer = _load_installer()
110+
config_path = tmp_path / ".zcode" / "cli" / "config.json"
111+
config_path.parent.mkdir(parents=True)
112+
config_path.write_text(
113+
json.dumps(
114+
{
115+
"plugins": {"keep": True},
116+
"mcp": {"servers": {"existing": {"command": "existing"}}},
117+
}
118+
),
119+
encoding="utf-8",
120+
)
121+
122+
installer._install_zcode(tmp_path)
123+
124+
config = json.loads(config_path.read_text(encoding="utf-8"))
125+
assert config["plugins"] == {"keep": True}
126+
assert config["mcp"]["servers"]["existing"]["command"] == "existing"
127+
server = config["mcp"]["servers"]["infermatrix-copilot"]
128+
assert server["type"] == "stdio"
129+
assert server["command"] == "uvx"
130+
assert "infermatrix-copilot-mcp" in server["args"]
131+
assert list(config_path.parent.glob("config.json.*.bak"))
132+
assert (tmp_path / ".zcode" / "skills" / "imreview" / "SKILL.md").is_file()
133+
update_skill = tmp_path / ".zcode" / "skills" / "imupdate" / "SKILL.md"
134+
assert "{{INFERMATRIX_COPILOT_ROOT}}" not in update_skill.read_text(
135+
encoding="utf-8"
136+
)
137+
138+
139+
def test_zcode_install_creates_config_when_missing(tmp_path):
140+
installer = _load_installer()
141+
142+
installer._install_zcode(tmp_path)
143+
144+
config_path = tmp_path / ".zcode" / "cli" / "config.json"
145+
config = json.loads(config_path.read_text(encoding="utf-8"))
146+
assert config["mcp"]["servers"]["infermatrix-copilot"]["command"] == "uvx"
147+
assert (tmp_path / ".zcode" / "skills" / "imdesign" / "SKILL.md").is_file()
148+
149+
150+
def test_zcode_install_rejects_invalid_config(tmp_path):
151+
installer = _load_installer()
152+
config_path = tmp_path / ".zcode" / "cli" / "config.json"
153+
config_path.parent.mkdir(parents=True)
154+
config_path.write_text("{ not json", encoding="utf-8")
155+
156+
with pytest.raises(installer.InstallError):
157+
installer._install_zcode(tmp_path)
158+
159+
assert config_path.read_text(encoding="utf-8") == "{ not json"
160+
161+
162+
def test_zcode_detected_via_config_dir(monkeypatch, tmp_path):
163+
installer = _load_installer()
164+
monkeypatch.setattr(installer.shutil, "which", lambda command: None)
165+
166+
assert not installer._zcode_installed(tmp_path)
167+
(tmp_path / ".zcode").mkdir()
168+
assert installer._zcode_installed(tmp_path)
169+
assert "zcode" in installer._detect_agents(tmp_path)
170+
171+
106172
def test_codex_install_sets_timeout_and_installs_current_skill_location(
107173
monkeypatch, tmp_path
108174
):

0 commit comments

Comments
 (0)