第一时间捕获有价值的信号
本文译自 MCP vs CLI: Benchmarking Tools for Coding Agents。本文通过 120 次评估运行(3 个任务 × 4 个工具 × 10 次重复),对比了 MCP 和 CLI 两种方式在编码 Agent 中的实际表现。

MCP 服务器(简称 MCP)风靡一时。然而,使用 Agent 编码工具的从业者通常持怀疑态度。许多 MCP 用不必要的输出淹没了上下文窗口,或者提供了数十个工具,通过污染上下文来降低 Agent 的性能。许多从业者(包括我自己)转而让编码 Agent 直接调用 CLI 工具。但如果我们错了呢?
MCP 作为薄包装器
就像很多会议本可以是电子邮件一样,很多 MCP 本可以是 CLI 调用。例如,有 GitHub MCP Server,它重新实现了 GitHub CLI 中已经可用的功能。与告诉你的编码 Agent 直接使用其 shell 工具运行 GitHub CLI 相比,使用那个 MCP 几乎没有什么好处。实际上,大多数时候,GitHub MCP Server 会导致比直接让 Agent 运行命令行工具更糟糕的结果。
另一个流行的选择是 Context7,它提供代码片段来帮助 Agent 使用流行的库。我在那里发现了一些我自己的库,这些片段完全没用。更有效的方法是让 Claude 克隆依赖项的仓库,然后让子 Agent 分析它并为手头的任务生成一个简洁的用户手册。
但这取决于 MCP 和可用的 CLI 工具。有时没有易用的 CLI 工具,使得 MCP 是明智的。其他时候 CLI 工具太冗长。对于没有内置 shell 工具的 LLM 客户端,MCP 服务器也是唯一的选择。而且有状态的工具作为 MCP 比作为 CLI 更容易实现,因为 MCP 服务器默认是有状态的。
我想通过编写一个 MCP 服务器和一个相应的 CLI 来量化差异,这两个工具可以与 Claude Code 一起使用,并且有 Claude 在训练期间遇到的等效标准 CLI 工具。然后我写了一个 hacky 的评估框架来评估:
- 什么成本更高
- 什么花费更多时间
- 哪种方法成功率更高
- 标准工具的固有知识是否胜过对以前未见过的工具的上下文学习
构建 CLI 和 MCP 服务器
我从构建一个对我自己本质上有用的工具开始:terminalcp,一个(功能较弱的)tmux 替代方案,带有一个 MCP 服务器,让 Agent 控制终端应用程序(调试器、REPL、vim、htop),就像 Playwright 控制浏览器一样。
这是 terminalcp 使用 CLI 版本用 lldb 调试可执行文件并在第二个终端窗口中观察会话的快速示例:
这里是 Claude Code 使用 terminalcp MCP 服务器版本用 lldb 调试同一个可执行文件:
注意我如何从另一个终端实例附加到 lldb 会话并与 Claude 一起调试。
你可以通过观看 terminalcp v0 的制作过程来无聊死自己,或者查看源代码。这是一个快速概述:
- TerminalManager 是核心:使用 node-pty 在伪终端中生成进程,使用 xterm.js 渲染 ANSI 序列。
- TerminalServer 作为持久守护进程运行,需要时自动生成。通过 Unix 套接字与客户端通信(也适用于 Windows,谢谢 Node.js)。
- TerminalClient 处理 MCP/CLI 与服务器之间的通信。
- MCP 服务器 只是将 LLM 工具调用转发给终端服务器。死简单。
- CLI 逻辑 我塞进了 index.ts 中。解析参数,使用 tmux 风格的符号键名和控制序列处理特殊键(上、下等)。
- AttachClient 让用户附加到正在运行的会话,如视频中所示。
我试图保持 terminalcp Token 高效。服务器用纯文本回复,不是 JSON(对于 MCP 服务器,那个纯文本仍然被包裹在 JSON-RPC 消息中,呜呜呜)。MCP 服务器暴露一个单一的 terminalcp 工具,而不是每个命令一个。这不只是巫术。在旧版 Claude Code 文档中,Anthropic 实际上提到你应该向 LLM 暴露的工具有一个限制,否则它会感到困惑(我再也找不到那个参考了)。你还可以找到这个 旧的 Claude Sonnet 3.7 食谱,他们在那里引入了一个批处理工具,这样 Claude 就可以并行调用多个工具。
最后,命令让你只选择你需要的输出。stdout 可以只返回渲染的终端回滚缓冲区的最后 N 行。stream 可以剥离 ANSI 代码,并使用 since_last 只返回自上次调用以来的新输出。这里是所有命令,MCP 工具参数作为 JSON 和相应的 CLI 调用:
# start - 生成带有可选名称和工作目录的进程
{"action": "start", "command": "python3 -i", "name": "repl", "cwd": "/home/user"}
terminalcp start repl "python3 -i"
# stop - 杀死一个或所有进程
{"action": "stop", "id": "repl"} # 或者只是 {"action": "stop"} 用于所有
terminalcp stop repl # 或者只是 terminalcp stop 用于所有
# stdin - 向进程发送输入
{"action": "stdin", "id": "repl", "data": "print('hello')\r"}
terminalcp stdin repl "print('hello')" ::Enter
# stdout - 获取渲染的终端屏幕,带有可选行数限制
{"action": "stdout", "id": "repl", "lines": 50}
terminalcp stdout repl 50
# stream - 获取原始输出,可选使用 since_last 增量
{"action": "stream", "id": "repl", "since_last": true, "strip_ansi": false}
terminalcp stream repl --since-last --no-strip-ansi
# list - 列出所有会话
{"action": "list"}
terminalcp ls
# term-size - 获取终端尺寸
{"action": "term-size", "id": "repl"}
# 未在 CLI 中暴露
# version - 检查兼容性
{"action": "version"}
terminalcp version
# kill-server - 关闭所有东西
{"action": "kill-server"}
terminalcp kill-server
最后,这是 MCP 服务器向像 Claude Code 这样的 MCP 客户端报告的工具描述:
用虚拟终端控制后台进程。重要提示:完成后始终使用 "stop" 操作清理进程。
示例:
启动开发服务器:{"action": "start", "command": "npm run dev", "cwd": "/path/to/project"}
发送带回车的文本:{"action": "stdin", "id": "proc-123", "data": "npm test\r"}
发送箭头键:{"action": "stdin", "id": "proc-123", "data": "echo hello\u001b[D\u001b[D\u001b[Dhi \r"}
发送 Ctrl+C:{"action": "stdin", "id": "proc-123", "data": "\u0003"}
停止进程:{"action": "stop", "id": "proc-abc123"}
停止所有进程:{"action": "stop"}
获取终端屏幕:{"action": "stdout", "id": "proc-123"} # 当前视图 + 回滚
获取最后 50 行:{"action": "stdout", "id": "proc-123", "lines": 50}
获取所有输出:{"action": "stream", "id": "proc-123"} # 从进程开始
只获取新输出:{"action": "stream", "id": "proc-123", "since_last": true} # 自上次 stream 调用
输出模式:
stdout: 终端模拟器输出 - 返回用户看到的渲染屏幕。
限制为 10K 行回滚。最适合:交互式工具、TUI、REPL、调试器。
stream: 原始进程输出 - 返回进程写入 stdout/stderr 的所有文本。
默认剥离 ANSI 代码(设置 strip_ansi: false 以保留)。没有历史限制。
使用 since_last: true,只返回自上次在该进程上的 stream 调用以来的新输出。
最适合:构建日志、测试输出、监控长时间运行的进程。
stdin 的常见转义序列:
Enter: \r 或 \u000d
Tab: \t 或 \u0009
Escape: \u001b
Backspace: \u007f
Ctrl+C: \u0003
Ctrl+D: \u0004
Ctrl+Z: \u001a
箭头键:Up=\u001b[A Down=\u001b[B Right=\u001b[C Left=\u001b[D
导航:Home=\u001b[H End=\u001b[F PageUp=\u001b[5~ PageDown=\u001b[6~
删除:\u001b[3~ 插入:\u001b[2~
功能键:F1=\u001bOP F2=\u001bOQ F3=\u001bOR F4=\u001bOS
Meta/Alt:Alt+x=\u001bx(ESC 后跟字符)
交互式示例:
Vim:stdin "vim test.txt\r" → stdin "iHello\u001b:wq\r" → stdout
Python:start "python3 -i" → stdin "2+2\r" → stdout
构建监控:start "npm run build" → stream (since_last: true) → 重复
中断:stdin "sleep 10\r" → stdin "\u0003" (Ctrl+C)
注意:命令通过 bash -c 运行。使用绝对路径,不是别名。
这不太完美。例如,stdin 可以让你在输出中指定一个要监视的模式,并带超时等待它,然后返回新输出。这将节省客户端和服务器之间的往返。但它对我的评估来说足够好了。
设计评估
我的目的不是创建一个完全科学的评估。为此,你应该查看 Eugene Yan 的博客,那里有很棒的信息。我只是想用一些基本统计数据来在聚会上烦人。
这是鸟瞰图:我定义了三个需要终端控制的任务(启动进程、发送输入、获取输出)和四个要测试的工具:terminalcp MCP 服务器、terminalcp CLI、tmux 和 screen。任务定义和工具定义是简单的 Markdown 文件。工具定义包括一些 frontmatter,定义了如何在需要时将它们添加到 Claude Code 的工具集中,以及如何在使用该工具的运行后进行清理。这些是我的评估框架的输入。
这个超级 hacky 的评估框架是这样工作的:
-
运行测试:对于每个任务和工具组合,框架生成一个带有干净配置的新 Claude Code 实例(没有自定义 MCP,没有 CLAUDE.md 文件)。每个任务/工具组合运行 10 次,因为 LLM 是不确定性的。框架将任务和工具定义组合成一个带有指示的单一提示,以输出 TASK_COMPLETE 或 TASK_FAILED 标记。Claude Code 实例通过 TerminalManager 驱动,这样测试运行者可以在 Claude 停顿时推动它。在每次运行结束时,它发出
/cost以捕获时间、Token 和成本。 -
捕获所有内容:每次运行产生多个输出文件:
- 发送的提示
- 终端回滚缓冲区(你在屏幕上看到的)
- 原始 ANSI 流(带和不带转义码)
-
提取统计数据:解析输出以获取成功/失败标记、成本、时间和按模型的 Token 使用情况。
-
用 Claude 评判:框架使用 Claude Code 来评判每个任务/工具组合,读取输出并评估什么工作了、什么失败了以及为什么。它还建议对任务进行改进。在第二遍中,Claude 对每个任务的所有工具进行排名,包括分析、洞察和建议。
框架可以并行运行评估,多次重复它们以保持一致性,并在运行之间处理清理。完整运行会产生很多文件。

这就是为什么我用 Claude 作为评判来给我一些初步洞察,然后我可以用这些洞察来导航文件进行手动分析。
让我们快速查看一下工具和任务定义。
工具定义
我试图让工具定义遵循完全相同的结构,这样比较是公平的。这包括特殊键、多行输入、回滚处理和 Agent 需要用工具执行的其他操作的说明,以完成任务。目标是通过上下文信息为每个工具提供平等的立足点。
terminalcp MCP 服务器已经报告了工具描述,所以我只添加了一些更多提示。这里是完整定义:
(工具定义内容在此省略)
任务定义
我创建了三个任务,锻炼终端控制的不同方面。它们应该不言自明。可能最有趣的是项目分析任务,其中 Agent 需要命令 OpenCode 并导航其 TUI。
(任务定义内容在此省略)
评估提示示例
这是评估框架在组合任务和工具定义时实际发送给 Claude Code 的内容。这是带有 tmux 的 Python REPL 任务的提示:
(提示示例内容在此省略)
运行评估
评估通过一个简单的 CLI 运行,我在其中指定要测试哪些任务/工具组合以及多少次重复。框架为每个组合生成提示,通过 TerminalManager 生成 Claude Code,并监控执行。如果 Claude 停顿,它会被推动继续。完成后,它捕获成本并保存所有输出。
这是它看起来的样子:
由于我正在评估终端多路复用器,我可以附加到会话并观察 Claude 在做什么。
统计和评判
所有运行完成后,第二个 CLI 命令解析输出,提取统计数据,并让 Claude 评判每个任务/工具组合并对工具进行排名。

所有这些都被写入最终的 evaluation-summary.json 文件,看起来像这样:
(JSON 内容在此省略)
你可能已经注意到,该结构也允许评估其他编码 Agent。我已经开始着手这方面的工作,因为我真的很想看看 Gemini CLI 和 OpenCode 的表现。我们看看我是否有时间做这件事。
结果
经过 120 次评估运行(3 个任务 × 4 个工具 × 10 次重复),以下是出现的结果:
成功率
| 工具 | Debug (LLDB) | Project Analysis | Python REPL | Overall |
|---|---|---|---|---|
| terminalcp (MCP) | 10/10 (100%) | 10/10 (100%) | 10/10 (100%) | 100% |
| terminalcp (CLI) | 10/10 (100%) | 10/10 (100%) | 10/10 (100%) | 100% |
| tmux | 10/10 (100%) | 10/10 (100%) | 10/10 (100%) | 100% |
| screen | 10/10 (100%) | 0/10 (0%) | 10/10 (100%) | 67% |
总成本
| 工具 | Debug (LLDB) | Project Analysis | Python REPL | Total |
|---|---|---|---|---|
| terminalcp (MCP) | $4.80 | $6.71 | $7.94 | $19.45 |
| terminalcp (CLI) | $3.86 | $7.91 | $8.18 | $19.95 |
| tmux | $3.73 | $11.05 | $7.16 | $21.93 |
| screen | $6.00 | $14.74 | $17.09 | $37.83 |
平均成本
| 工具 | Debug (LLDB) | Project Analysis | Python REPL |
|---|---|---|---|
| terminalcp (MCP) | $0.480 ± $0.165 | $0.671 ± $0.131 | $0.794 ± $0.035 |
| terminalcp (CLI) | $0.386 ± $0.119 | $0.791 ± $0.101 | $0.818 ± $0.117 |
| tmux | $0.373 ± $0.101 | $1.105 ± $0.183 | $0.716 ± $0.137 |
| screen | $0.600 ± $0.193 | $1.474 ± $0.265 | $1.709 ± $0.419 |
总时间
| 工具 | Debug (LLDB) | Project Analysis | Python REPL | Total |
|---|---|---|---|---|
| terminalcp (MCP) | 13m 42s | 16m 52s | 20m 27s | 51m 1s |
| terminalcp (CLI) | 16m 11s | 24m 12s | 25m 31s | 65m 55s |
| tmux | 14m 47s | 35m 2s | 23m 17s | 73m 6s |
| screen | 17m 42s | 35m 47s | 25m 37s | 79m 6s |
平均时间
| 工具 | Debug (LLDB) | Project Analysis | Python REPL |
|---|---|---|---|
| terminalcp (MCP) | 1m 22s ± 23s | 1m 41s ± 26s | 2m 2s ± 7s |
| terminalcp (CLI) | 1m 37s ± 20s | 2m 25s ± 18s | 2m 33s ± 22s |
| tmux | 1m 28s ± 19s | 3m 30s ± 36s | 2m 19s ± 22s |
| screen | 1m 46s ± 29s | 3m 34s ± 47s | 2m 33s ± 22s |
Token 使用
关键发现
-
MCP vs CLI 真的是平手:terminalcp MCP 和 CLI 版本都实现了 100% 的成功率。MCP 版本快 23%(51m vs 66m)并且便宜 2.5%($19.45 vs $19.95)。
-
Screen 在项目分析上完全失败:Screen 在项目分析任务上有 0% 的成功率——它无法在 OpenCode 的 TUI 中切换模型。这使其整体成功率降至 67%。
-
MCP 的隐藏优势:没有安全检查:terminalcp MCP 较低的 Haiku Token 使用(35k vs 1.3-2M)揭示了 CLI 工具在每次 bash 调用时触发 Claude Code 的恶意命令检测。MCP 完全绕过了这个开销。这也意味着更少的往返 Anthropic 服务器。
-
任务复杂度决定赢家:对于更简单的任务(LLDB 调试、Python REPL),tmux 实际上在成本上比 terminalcp 高出 10-22%。但对于需要更多来回的复杂任务(项目分析),terminalcp 更干净的输出使其比 tmux 有 39% 的成本优势。总体总和接近:terminalcp $19-$20,tmux $22。
重现结果
如果你想深入挖掘细节,你可以下载完整评估结果。框架写出每次运行的原始 ANSI 流。你甚至可以用 cat 回放 Claude Code 会话:
简单模式:运行单独的提示
只需从评估结果中获取任何 -prompt.md 文件并将其提供给 Claude Code。确保你安装了所需的工具:
- terminalcp:克隆仓库,运行
npm install,然后在克隆的仓库目录中运行 Claude Code - tmux:通过包管理器安装(
brew install tmux、apt-get install tmux等) - screen:通常在 Linux/macOS 上预装
完整模式:运行完整评估
克隆仓库并运行评估框架:
git clone https://github.com/badlogic/terminalcp
cd terminalcp
git checkout ac9272ed03a40e4d9666ded80667688b16a7a16
npm install
# 运行所有评估(3 个任务 × 4 个工具 × 10 次重复)
npx tsx test/eval/run.ts
# 或者运行特定组合
npx tsx test/eval/run.ts --agents claude --tasks python-repl --tools tmux --repeat 10
# 运行完成后生成统计数据
npx tsx test/eval/stats.ts
可用选项:
--agents:要使用的 Agent(目前只是:claude)--tasks:任务名称(debug-lldb、python-repl、project-analysis)或文件路径--tools:工具名称(terminalcp、terminalcp-cli、tmux、screen)或文件路径--repeat:每个组合的重复次数(默认:1)--parallel:并行评估数(默认:1)
对于 debug-lldb 任务,你需要安装 LLDB。对于 python-repl 任务,你需要安装 Python 3+。对于 project-analysis 任务,你需要安装 opencode,并且你需要在环境中设置 GROQ_API_KEY,因为任务通过 Groq 在 opencode 中使用 GPT OSS 120B。
那么,MCP 还是 CLI?
答案是,至少对于这个特定案例,它的重要性不如你想象的那么大。真正重要的是:
-
工具设计:一个设计良好的、Token 高效的工具可以击败标准工具。terminalcp 干净的输出和简单的接口尽管 tmux 和 screen 有训练数据优势,但仍胜过它们。
-
文档质量:清晰、简洁的带示例的说明可以与训练数据中的表现一样好。terminalcp 工具描述足以实现 100% 的成功率。
-
Token 效率:如果设计得好,MCP 和 CLI 都可以是 Token 高效的。许多 MCP 的问题不是协议——而是它们是设计糟糕的包装器,到处倾倒不必要的 JSON。
我的收获?也许与其争论 MCP vs CLI,我们应该开始构建更好的工具。协议只是管道。重要的是你的工具是帮助还是阻碍 Agent 完成任务的能力。
也就是说,如果你从头开始构建一个工具,并且你的用户已经有一个可用的 shell 工具,那么只需做一个好的 CLI。它更简单、更可移植。另外,你的 CLI 的输出可以通过管道到另一个 CLI 工具来进一步过滤和处理,这可以增加 Token 效率,但代价是额外的指令。这对于 MCP 是不可能的。
一旦你有了一个设计良好的、Token 高效的 CLI 工具,在其之上添加一个 MCP 服务器就非常简单了。