MCP 还是 CLI?我更关心 Agent 怎样使用工具
从 Pi 的 MCP + Codemode 出发,重新理解工具发现、代码编排和权限边界:CLI 与 MCP 不必二选一。
最近我对 MCP 和 CLI 的关系有些困惑。
一边是“CLI 更 Agent-native”的讨论:Agent 会写代码,给它一个 Shell,它就能探索命令、组合管道、处理结果。另一边,Pi 又把 MCP 与 Codemode 做进了产品。看起来像是工具路线在反复摇摆。
和 AI 讨论、再对照官方资料之后,我现在更倾向于一个判断:这不是谁取代谁,而是我们把不同层次的问题混在了一起。
CLI 是一种可执行接口,MCP 是一种标准化集成协议。真正决定使用体验的,还包括工具如何被发现、任务如何被编排,以及执行边界由谁守住。
先拆开三个问题
设计一个 Agent 工具时,我会先问:
- 能力怎么实现?比如读取赛事数据、搜索文档、创建 Issue。
- 能力怎么暴露?通过 CLI、API,还是 MCP 工具接口?
- 多个动作怎么组合?每一步都让模型决策,还是把确定的部分交给程序?
这三个问题相关,但不是同一个问题。
CLI 通常由程序接收命令和参数、执行操作、返回结果。人、脚本、CI 和有进程执行能力的 Agent 都可以使用它,不一定非要经过 Shell。
MCP 则约定了客户端和服务器之间的交互。以工具为例,服务器提供名称、描述和输入 Schema,客户端通过 tools/list 发现工具,通过 tools/call 调用;工具还可以提供结构化结果。协议并不要求宿主把所有工具一次性塞进模型上下文。MCP 工具规范
因此,一个 MCP Server 背后完全可以调用 CLI、API 或共享的业务模块。讨论“CLI 还是 MCP”,很多时候其实是在讨论:我们需要哪个入口,以及谁来组织调用。
CLI 为什么让人觉得自然
对开发者工具来说,CLI 有一个很实在的优势:可以复用已经存在的工程工作流。
比如,查询仓库里的 Bug Issue,只把标题留给模型:
gh issue list --repo OWNER/REPO --state open --label bug \
--limit 100 --json number,title \
--jq '.[0:5] | map({number, title})'
这里 OWNER/REPO 是需要替换的仓库名;示例取的是返回顺序中的前五项,不是经过评估的“最重要五项”。GitHub CLI 支持标签筛选、JSON 字段选择和 jq 表达式过滤。命令文档
模型不需要先读完整列表,再逐条抄写结果。确定性的筛选在程序里完成,模型只接收有用的信息。
但“只有一个 Shell 工具”不意味着没有成本。Agent 仍然要读帮助、理解参数、处理环境差异;命令如果输出大量日志,一样会挤占上下文。一个只有人类可读表格、错误提示含糊的 CLI,也未必适合 Agent。
我更看重的是:能否按需学习、稳定执行,并控制返回的信息量。 --help、结构化输出、可靠退出码和清晰错误信息,比“命令行”这个形式更重要。
MCP 的成本,常常来自使用方式
如果宿主把几百个工具的完整定义都提前交给模型,再把每次调用的大量结果原样返回,上下文与推理往返当然会变重。
但这是某种工具暴露和编排方式的成本,不是所有 MCP 实现都必然如此。Anthropic 在 2025 年的工程文章中讨论了两种优化:按需加载工具定义,以及在代码执行环境中处理数据后再返回结果。Code execution with MCP
这让我意识到,公平的比较不应该是“精心设计的 CLI”对“把所有东西塞进上下文的 MCP”。应该比较同一个任务、同一套能力、类似的输出约束。
发现一次工具需要多少轮?真正执行需要多少轮?结果包含多少无关字段?失败之后能不能恢复?这些才是可以测量的问题。
Pi 的变化:协议与编排可以分开
截至本文写作时,Pi 官方文档提供了四种 MCP 工具暴露方式:direct、codemode、deferred 和 hidden。其中默认的 codemode 不直接向模型声明全部工具,而是允许脚本通过 searchTools()、describeTool() 等方式按需发现。Pi MCP 文档
Codemode 让模型编写 JavaScript,在 QuickJS 沙箱中调用工具、组合调用并过滤结果;脚本输出才返回给模型。这个沙箱没有直接的文件系统或网络接口,对外能力通过工具获得。Pi Codemode 文档
我把这种设计理解为两件事的结合:MCP 提供集成入口,代码负责部分执行编排。这是我的架构解读,不是“整个行业已经统一路线”的结论。
例如同一个“列出 Issue、按作者统计、返回前五名”的只读需求,可以有两条路径:
CLI: 查询命令 → JSON → 本地聚合 → 五项结果
MCP + Code:查询工具 → 结构化结果 → 脚本聚合 → 五项结果
结果可以很接近。关键变化并不是工具名称,而是中间数据不必每一步都经过模型。
这也不意味着所有流程都应该塞进一个脚本。需要判断、澄清或确认的步骤仍然应该回到模型和人;代码适合承担的是已经明确的筛选、转换和独立读取。
权限:接口清楚,不代表自动安全
MCP 的具名工具和参数,更方便宿主观察“调用了什么、目标是什么”。但工具名叫 read_document,并不能证明实现只读;Schema 合法,也不代表动作获得了用户授权。规范同样提醒客户端,不应无条件信任非可信服务器提供的工具注解。MCP 工具规范
CLI 可以使用受限凭证、固定参数和隔离环境;MCP 也需要宿主审批、服务端鉴权和最小权限。对于基于 HTTP 的 MCP,授权规范提供了相关机制,但业务操作的权限仍需要正确实施。MCP 授权规范
我会把“能调用”和“允许调用”分开设计:读取可以在明确范围内自动执行,发布、删除、支付等对外生效的操作,需要独立的权限策略。
代码编排也不能绕开这些检查。一次脚本调用十个工具,不应该只审查脚本外壳,而忽略里面的真实动作。脚本后半段失败,也不意味着前半段已经完成的写操作会自动撤销。
如果是我来做工具,会怎么选
下面是我的工程取舍,不是协议本身规定的最佳实践。
| 场景 | 我会优先考虑 | 需要确认的条件 |
|---|---|---|
| 本地开发、仓库操作、CI 自动化 | 成熟 CLI 或共享核心 + CLI | 安装可控,输出稳定,凭证权限受限 |
| 面向多个 Agent 宿主的远程服务 | MCP 接口 | 客户端兼容、认证流程、租户隔离和审计 |
| 大量只读数据需要过滤与聚合 | CLI 管道或 MCP + 代码执行 | 结果可结构化,分页完整,输出规模可控 |
| 少量高频、语义明确的工具 | 直接工具调用 | 不为简单任务增加发现和脚本成本 |
| 已有可靠 API / SDK | 先复用核心能力 | 不为了追逐接口形式重复造轮子 |
如果一项能力同时服务开发者、脚本和 Agent,我会倾向于先把业务核心做好,再提供 CLI;确实需要跨宿主集成时,再增加 MCP 适配层。
但我不会要求 MCP 必须通过启动 CLI 子进程实现。CLI 与 MCP 共享同一套库,往往更容易保留结构化错误、减少序列化,也能避免两边业务逻辑分叉。
如果产品本来就是远程、多用户服务,也可以先提供 MCP。为了“CLI-first”多造一个没人使用的入口,同样是一种维护负担。
回到我的 OpenCLI 实践
给 OpenCLI 做 HLTV 适配器时,我关心的是:能否把页面里的赛事和选手数据,整理成可以重复调用、结果稳定的命令,而不是让 Agent 每次从头探索网页。具体过程写在这篇贡献记录里。
现在再看,这更像是在建设一层可编程的能力。CLI 是它的一个入口;以后如果有跨宿主集成需求,MCP 可以成为另一个入口,两者不需要互相否定。
我也不会因为接上 MCP,就假定浏览器自动化背后的脆弱性消失了。页面结构变化、会话失效、数据缺失,仍然需要在能力实现层解决。包装协议不能代替测试和维护。
这和我之前写的人是更重要的 Harness是连着的:工具提供执行,人决定目标、边界与验收。选择接口只是其中一环。反复适用的方法,则可以进一步沉淀成 Skill,具体写在从提示词到工作方法里。
最后,我不再急着站队
我现在更愿意把“Agent-native”当成一组要求,而不是某一种接口的专属标签:能力能被发现,输入输出清楚,结果可以组合,权限受到约束,失败能够被理解。
CLI、MCP、API 和代码执行都可能满足其中的一部分,也都有自己的成本。
所以我更想问的不是“谁赢了”,而是:这项能力怎样实现最可靠,怎样让 Agent 用得高效,又怎样让人保留真正重要的控制?
本文整理自一次与 AI 的讨论,并于 2026 年 10 月 4 日对照官方资料核实。示例用于解释工程取舍,不是两种路线的性能基准测试。