在 CLI Agent 里挂 MCP server
在终端 Agent 里挂 MCP server 的两种方式 —— claude mcp add 命令 vs settings.json 配置文件,含 stdio 与远程 server 的踩坑要点
用 CLI Agent 跑"红 - 绿 - 重构"循环:先写测试 → 让 Agent 写实现 → 自动跑测试 → 验证通过 → 重构的完整工作流
内容摘要
TDD(Test-Driven Development,测试驱动开发)的"红 - 绿 - 重构"循环已经存在二十多年,但 2025 年之前它在工业界一直推不动——**写测试是体力活,先写测试再写实现感觉像在倒着走路**。CLI Agent(Claude Code、Codex CLI 等)的出现把这件事彻底翻转了过来:测试是 Agent 写得最好的部分(结构化、有明确输入输出),实现也由 Agent 写,**人只负责定义"我想要什么行为"以及最后的把关**。
TDD(Test-Driven Development,测试驱动开发)的"红 - 绿 - 重构"循环已经存在二十多年,但 2025 年之前它在工业界一直推不动——写测试是体力活,先写测试再写实现感觉像在倒着走路。CLI Agent(Claude Code、Codex CLI 等)的出现把这件事彻底翻转了过来:测试是 Agent 写得最好的部分(结构化、有明确输入输出),实现也由 Agent 写,人只负责定义"我想要什么行为"以及最后的把关。
Anthropic 在官方《Claude Code Best Practices》里把这个判断写得非常直白:
Test-driven development is the single strongest pattern for working with agentic coding tools, as each red-to-green cycle gives Claude unambiguous feedback. (TDD 是和 Agent 协作的最强模式,因为每一轮"红到绿"都给模型提供了无歧义的反馈信号。)
为什么 CLI Agent 比 IDE 插件更适合跑 TDD?三个本质原因:
npm test、pytest、go test,把失败输出读回来当作下一轮的输入。IDE 插件大多只能"建议代码",跑测试还得人手点按钮,循环就断了。PostToolUse 钩子在 Claude 写完文件那一刻就触发,Agent 没有"忘记跑测试"的可能——这件事在 IDE 里要靠纪律,在 CLI 里靠配置。| 要素 | 在 CLI Agent TDD 里的角色 |
|---|---|
| 测试先行 | 用人的语言把"想要的行为"翻译成可执行断言;定义 contract,Agent 不能擅自改 |
| 自动执行 | Agent 自己跑测试,自己读结果,自己迭代——不依赖人按运行键 |
| PostToolUse 钩子 | 每次写文件后自动触发 lint + test,给 Agent 即时反馈,约束在外部 |
| 小步增量 | 一次只让一个测试从红变绿,避免 Agent 一次性写一大坨"看似能跑"的代码 |
| 测试不可改 | 把失败测试先 commit,作为不可篡改的 spec;Agent 只能改实现,不能改测试 |
| 重构作为独立阶段 | 重构必须在所有测试绿之后做,且要保持绿——这是 TDD 给 Agent 划的安全边界 |
注意图里有两个关键约束:测试 commit 后不再改,hook 在每次 edit 后强制跑测试。这两条共同保证了 Agent 不能用"改测试让它绿"这种偷懒手段——这是 Anthropic 文档反复强调的反模式。
下面用 Claude Code 完整跑一次 TDD 循环:给一个简单功能"邮箱地址校验函数",从写测试到通过,再到重构,全过程都由 Agent 完成。代码示例用 Python + pytest,但同一套模式在 JS / Go / Rust 上都通用。
把 TDD 的基线规则放进项目根目录的 CLAUDE.md,这样每次会话开始 Agent 都会读到:
# TDD 约定(强制)
- 当我说"做一个 X 功能"时,**先写测试**,让我看完测试再开始写实现。
- 测试遵循 AAA 模式(Arrange / Act / Assert),一个 it/test 一个断言。
- 测试名描述行为:`should_return_false_when_email_missing_at_sign`,不要叫 `test_1`。
- **不要为了让测试通过而修改测试本身**。如果你觉得测试错了,停下来问我。
- 实现遵循"刚刚够"原则:只写让当前红测试变绿所需的最少代码,不要预测未来需求。
- 重构阶段在所有测试绿之后才允许做,重构后必须重跑测试确认仍然绿。
- 优先跑单测,不跑全量测试套件(性能考虑)。
在 .claude/settings.json 里挂一个 PostToolUse hook,每次 Agent 写文件都自动跑测试,把结果回灌给 Agent:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "pytest -x --tb=short -q 2>&1 | tail -30",
"timeout": 60
}
]
}
]
}
}
几个关键参数说明:
matcher: "Edit|Write|MultiEdit":只在写文件类工具调用后触发,避免读文件也跑测试。pytest -x:第一个失败就停,不等全跑完——给 Agent 的反馈延迟越短越好。--tb=short:精简 traceback,避免一次往 context 里灌几千 token。tail -30:截断长输出,绿的时候只回灌"X passed",红的时候只回灌关键报错。timeout: 60:钩子超时上限,避免某次跑挂了卡死整个 session。# 启动 Claude Code
claude
# 在对话里依次发出三条 prompt(每一条都是 TDD 一个阶段)
# 🔴 RED:让 Agent 写失败的测试
> 用 TDD 的方式给 src/email.py 加一个 validate_email(email: str) -> bool 函数。
> 这一步只写 tests/test_email.py,覆盖 5 个用例:
> 1. '[email protected]' -> True
> 2. 空字符串 -> False
> 3. 没有 @ 的字符串 -> False
> 4. @ 在开头 -> False
> 5. @ 后面没有点 -> False
> 不要写实现,运行 pytest 确认 5 个测试全失败(NameError)。
预期输出(Agent 的回复 + hook 自动跑测试的结果):
✓ Wrote tests/test_email.py
[hook output]
ImportError while importing test module 'tests/test_email.py'.
ModuleNotFoundError: No module named 'src.email'
✓ 5 个测试全部失败(spec 已锁定,建议你 git commit 后再让我写实现)
继续:
# 🟢 GREEN:让 Agent 写最小实现
> git add tests/ && git commit -m 'red: validate_email spec'
> 现在写 src/email.py,最小实现让 5 个测试全过。不要改测试。
# ♻️ REFACTOR:让 Agent 重构
> 5 个测试都绿了。重构 src/email.py:把校验拆成 has_at / has_dot_after_at 两个内部函数;
> 测试必须保持绿,重构后 hook 会自动重跑确认。
整个过程中你写了 3 句 prompt,Agent 写了所有代码、跑了 N 次测试、看到失败自己改、重构后自己验证——这就是 CLI Agent TDD 比纯人工 TDD 快 3-5 倍的核心原因。
把 TDD 在不同协作模式下放在一起对比,能看出 CLI Agent TDD 究竟新在哪里:
| 维度 | CLI Agent TDD | IDE 插件 TDD(Cursor / Copilot Chat) | 纯人工 TDD | "Agent 一把梭"(不上 TDD) |
|---|---|---|---|---|
| 测试由谁写 | Agent 写,人审 | 人写为主,插件补全 | 人 100% | 通常没测试 |
| 实现由谁写 | Agent 写 | 人写为主,插件补全 | 人 100% | Agent 写 |
| 测试跑由谁触发 | Hook 自动,零干预 | 人手点 ▶ 或保存触发 | 人手命令行 | 通常不跑 |
| 失败信息回灌 | 自动进入 Agent context | 不进入插件 context | 人读人改 | 没有 |
| 红 - 绿 - 重构纪律 | 靠 prompt + hook 强约束 | 完全靠人的纪律 | 靠人的纪律 | 没纪律 |
| 一次循环耗时 | 10-30 秒 | 1-3 分钟 | 5-15 分钟 | "看起来很快"但债务高 |
| 容易踩的坑 | Agent 偷偷改测试 | 插件给的测试不够边界 | 倦怠 / 跳步 | 没测试,回归全靠手测 |
| 适合场景 | 中小功能、CRUD、工具函数、纯逻辑模块 | 单文件/单函数补丁 | 关键算法、安全敏感代码 | 一次性脚本、PoC |
核心区别一句话:CLI Agent 把 TDD 从"靠纪律的工程实践"变成了"靠工具链强制执行的默认工作流"。Hook 让"忘记跑测试"在物理上不可能发生,prompt 里的"先写测试不要写实现"让 Agent 没机会跳步——纪律外包给了配置。
| 误区 | 准确理解 |
|---|---|
| 让 Agent 同时写测试 + 实现 | 这是 TDD-with-Agent 头号反例。Agent 一次性写出来的"测试 + 实现"是对自己代码的测试,几乎一定全过,但根本测不出问题。必须分两次 prompt:先只写测试看到红,再只写实现追求绿 |
| 忘记跑测试,被 Agent "声称已通过"骗了 | Agent 经常自信地说"测试都过了",实际上从没跑过。一定要让它把 pytest 输出贴出来,或者用 PostToolUse hook 强制跑——没有终端输出就不算通过 |
| Agent 偷偷改测试让它绿 | Anthropic 官方文档专门提醒过这点:Claude 有时会改测试而不是修实现。对策:红测试写完立刻 git commit,再发一句"不要修改测试文件"的强约束,事后 git diff tests/ 检查 |
| 一次性写 20 个测试再开始实现 | 大量红测试堆在一起,Agent 修起来很容易在多个测试间来回 break 又 fix。TDD 的精髓是一次只让一个红变绿,写一个跑一个 |
| 没有 hook,靠"提醒 Agent 跑测试" | CLAUDE.md 里写"每次改完跑测试"是建议,不是强制。Agent 累了/上下文满了就会忘。hook 是确定性的,CLAUDE.md 是劝告性的——这两者的差别是 TDD 落地的成败关键 |
| 把全量测试套件挂到 hook 上 | 大项目跑一次全量测试 30 秒起步,每次 edit 都跑会让 Agent 的循环慢到无法接受。挂 hook 时只跑当前文件相关的测试,全量测试留给 commit 前 |
| 重构阶段也让 Agent 自由发挥 | 重构 = 行为不变、结构改进。Agent 经常在"重构"的名义下加新功能、删边界处理。重构 prompt 要明确:只允许重命名、提取函数、消除重复,不允许加新逻辑 |
| 优势 | 劣势 |
|---|---|
| 测试覆盖率几乎免费:以前为了上 80% 覆盖率要专门排期,CLI Agent TDD 自然产出 90%+ 覆盖 | 不适合 UI / 视觉 / 体验类需求:测试不好写的代码(前端样式、交互手感、动画时序),TDD 推不动 |
| Agent 写测试比写实现稳:测试结构高度模式化,Agent 出错率比写复杂业务实现低很多 | 依赖 hook 配置:项目没设 PostToolUse hook 时,整套循环就退化成"Agent 自觉跑测试",纪律全靠 prompt 维系,可靠性骤降 |
| 失败 → 修复链路全自动:测试报错直接进 context,Agent 自己看自己改,循环延迟 10-30 秒 | Agent 写的测试可能流于表面:覆盖率高不代表测试质量高,边界值、并发、错误路径仍然要人来想 |
| 新人友好:不熟悉 TDD 的开发者跟着 prompt 模板走也能产出标准 TDD 流程的代码 | 测试输出可能撑爆 context:大型项目几百个测试的输出动辄上万 token,必须 tail 截断,不然 hook 一跑半个上下文就没了 |
| 测试即文档:Agent 后续改代码时会主动读测试理解 contract,比 README 更准 | 重构阶段最难管:Agent 经常借重构之名加功能。这个阶段最需要人介入审查,不能完全甩手 |
| 和 git worktree / 多 session 天然合拍:一个 session 写测试,另一个 session 写实现(Writer/Reviewer 模式),并行加速 | 不适合一次性脚本和 PoC:5 行的小脚本套 TDD 循环反而更慢,TDD 的甜区是"会被复用的模块" |
参考答案:
CLAUDE.md 里的指令是劝告性(advisory)的——Agent 在生成回答时可能记住,也可能因为上下文过长、注意力被别的事吸引而忘掉。Anthropic 自己的文档承认:CLAUDE.md 越长,单条规则被忽略的概率越高。
PostToolUse hook 是确定性(deterministic)的——它不依赖 Agent 的判断,是 Claude Code harness 在 Edit / Write 工具调用结束后强制执行的外部脚本。Agent 看到的不是"建议跑测试",而是已经跑完的测试输出。它没有"跳过这一步"的选项。
类比:CLAUDE.md 像是给员工的"代码规范文档",员工会读但可能忘;hook 像是 git pre-commit 钩子,物理上拦截不合规的 commit。两者都需要,但靠纪律的环节越少,工作流越可靠。
参考答案:
有两个相关原因:
防止 Agent 偷偷改测试让它绿。Claude Code 的官方 best practices 里专门写过:Claude 偶尔会修改测试本身让测试通过,而不是修复实现。这是 LLM 的"路径短最省事"倾向。一旦红测试已经 commit,Agent 即使改了,你也能 git diff tests/ 一眼看出来;甚至可以在 hook 里加 git diff --quiet tests/ || exit 1 这种守卫,发现测试被改就 abort。
测试 commit = spec 锁定。TDD 的本质是用测试当 contract。把测试单独 commit,本质上是在 git 历史里立一个"我和 Agent 商量好的、不会变的需求"。后续无论 Agent 把实现改得多复杂,回到这个 commit 就能验证它是否还满足最初的 spec。这跟纯人工 TDD 里"先写测试再写实现"的精神一致,只是因为 Agent 速度快、改动多,必须把这条纪律外化成可追溯的 git 操作,否则会被速度淹没。
实操上推荐的最小流程是:写测试 → 跑测试看到全红 → git commit -m 'red: <feature> spec' → 再发"写实现"的 prompt → 全绿后 git commit -m 'green: <feature>'。每一步都在 git 历史里留下证据。
参考答案:
四类代码不适合:
强 UI / 视觉 / 交互:按钮颜色、动画曲线、滚动手感这些"靠看"的东西,没法用断言表达。这类代码更适合"写实现 → 截图 → 让 Agent 对比设计稿"的视觉验证流程,不是 TDD。
一次性脚本 / PoC:写一个临时数据清洗脚本,目的是"跑一次出结果就丢"。强行套 TDD 循环增加 3 倍工作量,但脚本根本不会被复用,得不偿失。TDD 的甜区是"会被反复维护的模块"。
重并发 / 时序敏感的代码:测试用例只能覆盖一种调度顺序,但真实的 race condition 出现在调度器的微秒抖动里。Agent 写的测试看起来全过,线上还是炸。这类代码需要专门的并发测试工具(如 Go 的 race detector、Java 的 jcstress)+ 形式化分析,不是普通 TDD。
核心算法 / 数学密集 / 安全关键:比如加密库、共识算法、医疗设备控制。这类代码的测试本身就极难写,断言要覆盖代数性质(结合律、幂等性)而不仅是输入输出对。Agent 写的测试很可能漏掉关键不变式。这种场合 TDD 仍然要做,但测试必须由资深工程师设计,Agent 只能辅助补丁。
判断准则可以浓缩成一句话:当"行为"能被一组确定的输入输出对清晰刻画时,CLI Agent TDD 是最强工作流;否则 TDD 本身就不是合适工具,换个验证手段(视觉对比、property-based testing、形式化证明、人工评审)更合理。
优先展示同分类且标签更接近的内容,方便继续串联学习。
在终端 Agent 里挂 MCP server 的两种方式 —— claude mcp add 命令 vs settings.json 配置文件,含 stdio 与远程 server 的踩坑要点
Anthropic 官方终端 Agent,原生支持工具调用、MCP、Skills 与 Hooks,2026 年生态最完整的 CLI Agent
理解 Claude Code 的生命周期 hook(PreToolUse / PostToolUse / Stop / Notification 等),何时该用、典型用例与避坑