已发布资料基础官方资料 + 个人实践
·8 分钟0 次阅读AI Agent CLI 工程
文章/AI 与智能体

CLI 还是 MCP:为什么“万物皆 CLI”只对了一半

AI Agent CLI 工程第 2 篇:区分用 CLI 运行 Agent 与让 Agent 调用 CLI,基于 MCP 2026-07-28 规范比较 CLI、MCP 和 SDK/API,并用同一领域函数实现可验证的 CLI/MCP 双适配器。

#CLI#MCP#Agent#Codex#Claude Code
文章目录
  1. 一分钟概览
  2. 1. 先拆开两个方向
  3. 1.1 Agent 以 CLI 形式运行
  4. 1.2 Agent 把 CLI 当作工具
  5. 2. 为什么 CLI 对 Agent 特别有吸引力
  6. 2.1 现成工具很多
  7. 2.2 进程边界容易观察
  8. 2.3 本地认证可以复用
  9. 2.4 组合成本低
  10. 3. 一个适合 Agent 调用的 CLI 应该长什么样
  11. 3.1 用窄命令表达明确意图
  12. 3.2 人类输出和机器输出分开
  13. 3.3 列表必须有上限
  14. 3.4 大结果返回文件,不塞 stdout
  15. 3.5 错误要稳定而可行动
  16. 4. CLI 的合同到底是什么
  17. 5. MCP 增加了什么
  18. 6. 用同一个核心函数实现两种接口
  19. 7. 跑通 CLI 和 MCP 2.0
  20. 8. CLI、MCP、SDK/API 怎么选
  21. 9. “万物皆 CLI”在哪些地方成立
  22. 10. 适合 Agent 调用的 CLI 检查清单
  23. 发现与输入
  24. 输出与上下文
  25. 副作用与安全
  26. 运行可靠性
  27. 下一步练习
  28. 参考资料

Agent 工程里有一句很有吸引力的话:万物皆 CLI。

它的经验基础很扎实。Codex、Claude Code 可以直接调用 gitghrg、测试命令和项目脚本;一个团队已有的内部工具,只要放进 PATH,Agent 往往当天就能使用,不必先搭一套协议服务。

但把这句话当成普遍架构结论,会漏掉工具发现、Schema、跨宿主互操作、远程认证和治理问题。这篇不站队,而是做一个可运行比较:

同一个“查询 Agent 运行记录”的能力,先做成适合 Agent 调用的 CLI(Agent-Friendly CLI),再用 MCP Python SDK 2.0 暴露为工具。两边共用领域函数,只比较接口成本和能力边界。

项目说明
内容类型Agent 工具接口设计与 CLI/MCP 对照实战
适合读者正在给 Codex、Claude Code 或自建 Agent 接入内部工具的开发者
阅读 / 跟做时间约 20 分钟 / 45-60 分钟
环境要求Python 3.10+;CLI 零依赖;MCP 示例使用官方 mcp==2.0.0
代码检查点23d99e9
实验下载agent-cli-capability-lab-0.2.0.zip
SHA-256A04884139D17A89588971B7997AB48F6F85654D74DBEBB9CA85607B6A8BF6006
资料核对日期2026-08-10
实验边界本地确定性数据;不比较模型质量,不覆盖远程 OAuth 和生产部署

一分钟概览

  1. “Agent 是 CLI”和“Agent 使用 CLI”是两个不同方向。
  2. CLI 是进程接口:命令、参数、cwd、环境、stdin/stdout/stderr 和退出码。
  3. MCP 是应用层协议:发现工具、交换 Schema、调用能力,并承载资源、Prompt 和结构化内容。
  4. MCP 的 stdio 传输也会启动子进程,所以 CLI 和 MCP 不是互斥技术。
  5. MCP 2026-07-28 核心已改为无状态,不能再用“CLI 无状态、MCP 有状态”做区别。
  6. 本地、短时、已有工具链优先考虑 CLI;需要跨宿主发现、统一 Schema 和远程治理时再考虑 MCP。
  7. 最稳妥的实现不是写两套业务逻辑,而是“核心能力 -> CLI 适配器 / MCP 适配器”。

图 1:一个领域能力从核心函数分出 CLI 和 MCP 两个接口,再分别被 Codex、Claude Code 与其他宿主调用图 1:一个领域能力从核心函数分出 CLI 和 MCP 两个接口,再分别被 Codex、Claude Code 与其他宿主调用

图 1:CLI 与 MCP 不需要争夺业务逻辑所有权。它们更适合做薄适配层,让同一能力在不同接入环境中复用。

1. 先拆开两个方向

讨论“Agent 和 CLI”时,常常把两种关系混在一起。

1.1 Agent 以 CLI 形式运行

bash
codex exec "review this repository"
claude -p "review this repository"

此时 CLI 是 Agent 的进程入口。调用者关心工作目录、权限、事件、退出码和超时。这是上一篇的主题。

1.2 Agent 把 CLI 当作工具

bash
git diff --stat
gh pr view 42 --json title,statusCheckRollup
agent-lab runs get run-003 --format json

此时 Agent 是调用者,CLI 是能力接口。设计重点变成:模型能否从帮助信息理解它、参数是否容易填对、结果是否适合放进上下文、写操作是否有明确副作用。

图 2:Agent 作为 CLI 被调度,与 Agent 把 CLI 作为工具调用的方向区别图 2:Agent 作为 CLI 被调度,与 Agent 把 CLI 作为工具调用的方向区别

图 2:一个方向管理 Agent 生命周期,另一个方向设计 Agent 可用能力。两者都经过操作系统进程,却解决不同问题。

2. 为什么 CLI 对 Agent 特别有吸引力

OpenAI 的 Harness Engineering 实践提到,Agent 可以直接使用 gh、仓库脚本等标准开发工具;官方 Agent-Friendly CLI 用例则强调可预测 JSON、分页、按 ID 精确读取和大结果文件导出。这些做法之所以有效,来自 CLI 的几个朴素优势。

2.1 现成工具很多

gitrgjqgh、数据库客户端、云平台 CLI、测试和构建系统早已存在。Agent 不需要一套只为 LLM 重写的世界。

2.2 进程边界容易观察

命令行、参数、输出和退出码天然可以写入追踪记录(Trace),也容易在人类终端里复现。出问题时,开发者能运行同一条命令,而不是只能重放一段模型对话。

2.3 本地认证可以复用

成熟 CLI 往往已有登录、配置文件和权限体系。Agent 在受控环境中可以复用它们,但这也是风险:凭证复用不等于权限合理,外部执行框架仍要限制环境、命令和副作用。

2.4 组合成本低

Shell 可以把小工具串起来。不过“可组合”不意味着应该让模型随意生成长管道。自动化里更稳妥的方式仍是参数数组、固定命令模板和独立中间产物。

3. 一个适合 Agent 调用的 CLI 应该长什么样

配套实验实现了三个只读能力:

bash
python -m agent_cli_lab.cli runs list --limit 2 --format json
python -m agent_cli_lab.cli runs get run-003 --format json
python -m agent_cli_lab.cli reports export run-003 --output reports/run-003.json

对应的设计判断如下。

3.1 用窄命令表达明确意图

runs get <id>query --expression "..." 更容易发现,也更容易做权限审计。稳定 ID 比让模型按标题猜资源可靠。

3.2 人类输出和机器输出分开

默认文本适合终端扫描,--format json 提供稳定字段。不要通过去掉 ANSI 颜色和正则表达式把人类表格勉强解析成协议。

3.3 列表必须有上限

实验限制 1 <= limit <= 100。真实接口还应返回 total、分页游标或是否还有下一页,防止一次结果吞掉上下文。

3.4 大结果返回文件,不塞 stdout

reports export 返回文件路径和字节数。Agent 可以先看摘要,再决定是否读取产物,而不是把数十万字符塞回下一轮推理。

3.5 错误要稳定而可行动

未知 ID 返回 stderr JSON,并以退出码 2 结束:

json
{"error":"unknown run id: missing"}

这比 Python 堆栈更适合作为工具反馈。写操作还应增加 --dry-run、幂等键、预期版本和明确确认;本文示例刻意只读,因此没有伪造一个没有真实副作用的 --dry-run

4. CLI 的合同到底是什么

一个 CLI 的“协议”来自多项约定,而不是单独的 JSON:

text
可执行文件 + 子命令 + 参数
+ cwd + env + stdin
+ stdout + stderr + exit code
+ help/version + 产物

它非常适合同机短生命周期任务,却有几个天然弱点:

  • 工具发现通常依赖 PATH--help,没有统一目录;
  • 不同 CLI 的 JSON、错误和分页风格不同;
  • 输入 Schema 常藏在帮助文字里,宿主需要额外解析或人工编写说明;
  • 远程调用、OAuth、租户隔离和跨语言客户端不是 Shell 自动解决的;
  • 富文本、图片、资源引用等结果需要额外文件约定。

这并不说明 CLI 落后,只说明它是一种进程接口,而不是完整的 Agent 工具协议。

5. MCP 增加了什么

MCP 在工具场景中提供标准化的 tools/listtools/call,工具携带名称、描述与 JSON Schema;协议还定义 Resources、Prompts、结构化内容和传输方式。

值得特别更新的是:2026-07-28 规范把核心协议改为无状态。 initialize/initialized 握手和 Mcp-Session-Id 已从这一版协议中移除,版本、客户端身份和能力改为随请求携带;需要预先了解服务能力时,可以选择调用 server/discover。因此下面这种对照已经不可靠:

text
错误:CLI 是无状态,MCP 是有状态

更准确的区别是:

text
CLI:操作系统进程与用户界面约定
MCP:Agent 宿主与能力提供方之间的应用层协议

MCP 的 stdio 传输同样会启动本地子进程并通过 stdin/stdout 交换消息。它不是“摆脱 CLI”,而是在进程之上增加统一消息和发现合同。

图 3:CLI 进程合同与 MCP 应用层协议在不同层解决问题图 3:CLI 进程合同与 MCP 应用层协议在不同层解决问题

图 3:stdio MCP 仍依赖进程与标准流;Streamable HTTP 则把同一协议带到远程服务。状态性已经不是 2026-07-28 核心规范的分界。

6. 用同一个核心函数实现两种接口

实验目录的关键结构是:

text
agent_cli_lab/
├─ records.py       # 领域能力
├─ cli.py           # argparse 适配器
└─ mcp_server.py    # MCPServer 2.0 适配器

records.py 不知道 CLI 或 MCP:

python
def get_run(run_id: str, path: Path = DEFAULT_DATASET) -> dict[str, Any]:
    for run in _load(path):
        if run.get("id") == run_id:
            return run
    raise RunNotFoundError(f"unknown run id: {run_id}")

CLI 只负责参数和退出码:

python
data = get_run(args.run_id)
_emit(data, args.format)

MCP 2.0 适配器只负责注册工具:

python
from mcp.server.mcpserver import MCPServer

server = MCPServer("agent-cli-lab", version="0.2.0")

@server.tool(structured_output=True)
def get_run(run_id: str) -> dict[str, Any]:
    return get_run_record(run_id)

这种结构有三个好处:

  1. CLI 和 MCP 不会产生两套业务判断;
  2. 领域能力可以用普通单元测试快速覆盖;
  3. 更换传输或 Agent 宿主时,不必重写数据访问和错误语义。

7. 跑通 CLI 和 MCP 2.0

先验证零依赖 CLI:

bash
cd agent-cli-capability-lab-0.2.0/phase-8-agent-cli-engineering
python -m unittest discover -s tests -v
python -m agent_cli_lab.cli runs list --limit 2 --format json

此时如果尚未安装 MCP SDK,test_mcp_adapter 会显示为 skipped,这是预期结果;其余 CLI 与领域逻辑测试应通过。

再在隔离环境安装官方 MCP Python SDK:

bash
python -m venv .venv
.venv/Scripts/python -m pip install "mcp==2.0.0"
.venv/Scripts/python -m unittest discover -s tests -v
.venv/Scripts/python -m agent_cli_lab.mcp_server

macOS/Linux 把解释器路径换成 .venv/bin/python

最后一条命令会启动 stdio MCP 服务并等待宿主请求,终端看起来会停在那里,这是服务的正常状态,可以用 Ctrl+C 退出。工具发现是否正确由测试验证,不要把“进程一直运行”误判为卡死。

本机验证结果为 8 tests passed,其中 MCP 测试检查:

  • tools/list 可以发现 list_runsget_runexport_report
  • get_run 的结构化结果与领域函数一致;
  • CLI 未知 ID 以稳定错误和退出码 2 返回;
  • 导出产物的内容和字节数可以验证。

如果环境里已有 MCP 1.x,仅检查 import mcp 会产生误判。本文第一次测试正是这样失败的:包存在,但 mcp.server.mcpserver.MCPServer 不存在。迁移检查应验证确切版本和实际导入路径。

8. CLI、MCP、SDK/API 怎么选

场景优先选择原因
Agent 在本机调用 git、测试、构建工具CLI现成、低成本、可复现
团队已有稳定内部 CLI先 CLI先验证真实使用频率,再决定是否协议化
多个 Agent 宿主需要自动发现同一工具MCP统一发现和 Schema
需要返回图片、资源引用或多种内容块MCP协议有结构化内容模型
远程多租户工具,需要统一认证与治理MCP 或正式服务 APIShell 本身不解决远程身份与租户边界
单一应用内部的高频强类型调用SDK/API少一层进程和序列化开销
一次性迁移或运维脚本CLI易审查、易在终端重放
大批量低延迟数据路径SDK/API避免每次启动进程和 Agent 上下文开销

图 4:根据本地性、发现需求、远程治理和调用频率选择 CLI、MCP 或 SDK API图 4:根据本地性、发现需求、远程治理和调用频率选择 CLI、MCP 或 SDK API

图 4:选择顺序不是从“简单”升级到“高级”,而是先看边界。很多内部能力长期停在高质量 CLI 就足够。

一个实用判断顺序是:

text
已有稳定 CLI,且调用发生在同一受控环境?
  是 -> 先直接使用 CLI
  否 -> 是否需要跨宿主发现、统一 Schema 或丰富内容?
         是 -> 考虑 MCP
         否 -> 是否是单应用、高频、强类型调用?
                是 -> SDK/API
                否 -> 先做最小 CLI 或固定工作流验证需求

9. “万物皆 CLI”在哪些地方成立

它作为工程启发很有价值:

  • 先复用现有工具,不为 Agent 重建整个基础设施;
  • 让关键操作能在终端里被人复现;
  • 用文件、退出码和结构化输出留下证据;
  • 把复杂 GUI 操作压缩为可自动化命令。

但作为完整架构结论只对了一半:

  • CLI 没有统一工具发现和 Schema 协商;
  • PATH 可见不等于权限合理;
  • 本地认证可复用不等于适合远程多租户;
  • stdout JSON 不自动成为跨宿主协议;
  • 进程可启动不代表超时、取消、幂等和产物已被治理。

我更愿意把它改写成:

能做成清晰 CLI 的能力,通常已经拥有不错的任务边界;需要被多个 Agent 宿主长期治理时,再把稳定核心适配成 MCP 或 SDK/API。

10. 适合 Agent 调用的 CLI 检查清单

发现与输入

  • --help--version 稳定可用。
  • 子命令窄而明确,不要求模型发明查询语言。
  • 使用稳定资源 ID,支持按 ID 精确读取。
  • 参数有边界、默认值和明确错误。

输出与上下文

  • 提供稳定 --format json,stdout 不混诊断信息。
  • stderr 用于可行动错误,退出码有文档。
  • 列表支持限制和分页。
  • 大结果导出到显式文件并返回产物元数据。

副作用与安全

  • 写操作有 dry-run 或草稿阶段。
  • 支持幂等键、预期版本或冲突检查。
  • 不回显 Secret,环境变量最小继承。
  • 高风险动作保留批准点和审计证据。

运行可靠性

  • 调用者设置 cwd、超时和输出上限。
  • 取消时处理子进程和结果未知状态。
  • 版本更新有契约测试。
  • CLI、MCP 和 SDK 适配器共用领域逻辑。

下一步练习

给你正在使用的一个内部 CLI 做一次 30 分钟体检:

  1. 让 Codex 只读运行 tool --help,写出它理解的三个核心能力。
  2. 用三个正常输入、两个边界输入和一个未知 ID 测试退出码与输出。
  3. 把最大结果导出为文件,比较前后上下文大小。
  4. 判断它是否真的需要 MCP:写出至少一个跨宿主发现、远程治理或丰富内容的明确需求。
  5. 若需要 MCP,只新增薄适配器,不复制业务逻辑。

完成后,你得到的不是“CLI 与 MCP 谁赢了”,而是一份能解释当前选择、未来迁移条件和验证证据的接口决策。

参考资料

继续阅读