William 的技术笔记

MCP 还是 CLI:给 Agent 接工具的两条路

2026-10-21 · #Agent#MCP#CLI#工具调用

给 Agent 接工具,现在大致有两种做法。

一种是 MCP(Model Context Protocol):为每个服务写一个 MCP Server,把能力包装成结构化的工具,Agent 通过协议发现并调用它们。

另一种是 CLI:给 Agent 一个 shell,让它直接跑 git、gh、kubectl、psql、curl,就像一个工程师坐在终端前那样。

这两条路经常被拿来对立讨论,“MCP 已死,CLI 就够了”和“CLI 太危险,一切都该走 MCP”两种声音都有。我的看法是:它们解决的是不同层面的问题,选哪个取决于 Agent 运行在哪里、给谁用、要接什么。

先说清楚两者是什么

MCP:一个协议

MCP 是 Anthropic 在 2024 年底开源的协议,目标是让“模型应用”和“外部能力”之间有一个统一的接口,常被比作 AI 领域的 USB-C。

几个关键点:

  • 基于 JSON-RPC 2.0,客户端(Agent 宿主,比如 Claude Code、Cursor)和服务端(MCP Server)之间交换结构化消息;
  • 传输层可以是本地的 stdio(宿主拉起一个子进程),也可以是远程的 Streamable HTTP(连接一个云端服务);
  • 服务端暴露几类原语:Tools(可调用的函数)、Resources(可读取的数据)、Prompts(预设的提示模板);
  • 每个 Tool 都有名字、描述和 JSON Schema 定义的参数,宿主把这些信息放进模型的上下文,模型据此决定调用哪个工具、传什么参数。

一次调用大概长这样:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "create_issue",
    "arguments": { "repo": "acme/web", "title": "登录页 500", "labels": ["bug"] }
  }
}

CLI:一个环境

CLI 不是协议,而是一种工作方式。Agent 只有一个通用工具,比如 bash,然后它自己决定敲什么命令:

gh issue create --repo acme/web --title "登录页 500" --label bug

模型不需要预先知道有哪些工具。它知道 gh 是什么,是因为训练数据里有海量的 gh 用法;不知道某个参数时,它会去跑 gh issue create --help。

一句话概括:MCP 是把能力“递给”模型,CLI 是让模型自己“去拿”能力。

核心差异

一、上下文开销

这是最近讨论 CLI 时被提得最多的一点。

MCP 的工具定义需要预先放进上下文。一个 GitHub MCP Server 可能有几十个工具,每个工具带描述和 Schema,接上五六个 Server,还没开始干活,几万 token 就没了。工具一多,模型选错工具的概率也会上升。

CLI 的开销是按需的。模型只在用到时才去读 --help 或 man page,而且对于 git、docker、curl 这类常见工具,它根本不需要读文档。

不过这个差异正在被缩小:不少宿主已经支持工具的延迟加载和按需搜索,只在需要时才把完整 Schema 放进上下文。所以这一点更多是“当前实现”的问题,不是协议本身的限制。

二、可组合性

CLI 有 Unix 五十年积累下来的组合能力:

gh pr list --state merged --json number,title,mergedAt \
  | jq -r '.[] | select(.mergedAt > "2026-10-01") | .title' \
  | sort | uniq -c

一条命令完成“查询 → 过滤 → 聚合”,中间结果不经过模型的上下文。如果用 MCP,通常是模型调用 list_pull_requests,拿到一大坨 JSON 放进上下文,再自己“看”一遍做过滤,既慢又贵,还容易数错。

这也是为什么有人提出“让 Agent 写代码去调 MCP 工具”,而不是一个个直接调用:本质上是在 MCP 之上把可组合性找回来。

三、结构化与可靠性

反过来,MCP 的优势在于契约明确:

  • 参数有 Schema,类型错了在调用前就能被拒绝;
  • 返回值是结构化数据,不用去解析人类可读的输出;
  • 工具的语义由服务提供方定义,比如 create_issue 就只做一件事。

CLI 的输出是写给人看的,格式可能随版本变化、受 locale 影响、混着进度条和颜色码。好在越来越多的 CLI 提供 --json 或 --output json,能缓解很多问题;但对于那些只有表格输出的老工具,模型就得靠“读”来理解,出错的概率更高。

四、运行环境与安全边界

这是我认为最决定性的差异。

CLI 方式的前提是:Agent 有一个 shell。有了 shell,它能做的事就远不止你想让它做的那几件,gh 能跑,rm -rf 也能跑,curl 能调 API,也能把环境变量发到外面去。所以 CLI 方式几乎必然要配合沙箱、网络出口控制和凭证隔离(上一篇Agent 沙箱讲的就是这些)。

MCP 的能力边界则由 Server 决定。一个只暴露 search_docs 和 create_ticket 两个工具的 Server,Agent 就只能做这两件事。这让它可以用在没有 shell 的环境里:网页聊天界面、手机 App、企业内部的客服 Agent。在这些场景里,给模型一个 shell 既不可能也不合适。

当然,MCP 也不是天然安全。工具描述本身可能被投毒,Server 返回的内容同样会带来提示词注入,第三方 Server 的代码质量也参差不齐。但至少“能做什么”是可枚举、可审查的。

五、认证

CLI 用的是本地凭证:gh auth login 存下的 token、~/.aws/credentials、~/.kube/config。在开发者自己的机器上,这非常顺手,Agent 直接继承了你的身份。但这也意味着 Agent 拥有和你一样的权限,没有更细的划分。

远程 MCP Server 走的是 OAuth:用户在浏览器里授权一次,Server 拿到的是一个有范围限制、可吊销的 token。对多用户的 SaaS 场景来说,这是正确的模型:你不可能让每个用户把自己的 AWS 密钥文件上传上来。

六、生态覆盖

CLI 的生态是现成的:几乎所有基础设施工具和开发者服务都有 CLI,而且模型已经“学过”它们。

MCP 的生态在快速补齐,尤其是那些本来就没有 CLI 的服务:Notion、Figma、Slack、各类 SaaS 后台,以及公司内部的系统。为这些服务写一个 MCP Server,往往比先写一个 CLI 再教模型用它更直接。

对比一览

维度 MCP CLI
本质 协议 运行环境
工具发现 预先注册,Schema 进上下文 按需,靠训练知识和 --help
上下文开销 工具多时较大 较小
可组合性 弱,结果经过模型 强,管道直接串联
输入输出 结构化、有契约 文本为主,依赖 --json
运行前提 宿主支持 MCP 即可 需要 shell,通常还需要沙箱
能力边界 由 Server 定义,可枚举 几乎无边界
认证 OAuth,适合多用户 本地凭证,适合个人
生态 SaaS、内部系统 开发与基础设施工具

什么时候用哪个

适合 CLI 的场景

  • 编码 Agent、开发者工具:Agent 本来就要在代码仓库里工作、跑测试、装依赖,shell 是必需品。这时候再给 git、gh、docker 包一层 MCP 往往是多余的;
  • 工具已经有成熟的 CLI,而且模型很熟悉它;
  • 需要组合与批处理:数据过滤、日志分析、批量操作,用管道一行解决;
  • 运行在已经隔离好的环境里:有沙箱、有出口代理,shell 的风险已经被控制住了。

适合 MCP 的场景

  • 没有 shell 的宿主:聊天应用、移动端、浏览器里的 Agent;
  • 面向多用户的产品:需要 OAuth、按用户隔离权限、审计每一次调用;
  • 服务没有 CLI,或者 CLI 很难用:SaaS 产品、内部系统;
  • 需要严格限定 Agent 能做什么:比如客服 Agent 只能查订单、退款,不能做别的;
  • 同一个能力要提供给多个 Agent 宿主:写一次 Server,Claude、Cursor、各种 IDE 都能接。

混合使用

实际上,最常见的形态是两者并存。一个编码 Agent 在沙箱里用 CLI 处理代码、Git 和构建,同时通过 MCP 接入 Jira、Sentry、内部文档这些没有好用 CLI 的系统。

还有一种值得关注的中间形态:用 文档 + 脚本 的方式给 Agent 提供能力。比如写一份说明,告诉 Agent “要做 X,就去跑 scripts/x.sh,参数是这些”,Agent 在需要时才读这份说明。它兼具 CLI 的按需加载和可组合性,又比让模型从零摸索一个陌生工具更可靠。Claude 的 Skills 就是这个思路。

一些实践建议

如果你在写 MCP Server:

  1. 工具要少而精。不要把 REST API 一对一映射成几十个工具,按 Agent 的任务来设计,比如一个 search_and_summarize_issues 往往比 list_issues + get_issue 好用得多;
  2. 描述写给模型看。说清楚什么时候该用、什么时候不该用、参数的含义和常见值;
  3. 控制返回值大小。支持分页、字段筛选,别一次返回几百 KB 的 JSON;
  4. 错误信息要可操作。“参数 repo 应为 owner/name 格式”比“400 Bad Request”有用得多。

如果你让 Agent 用 CLI:

  1. 优先用结构化输出,--json 配合 jq;
  2. 沙箱先行,网络出口和凭证的控制要在 shell 之外;
  3. 避免交互式命令,分页器、确认提示、-i 类参数都会让 Agent 卡住,设置 GH_PAGER=cat、--yes 这类开关;
  4. 限制输出长度,一条命令打出几万行日志会直接撑爆上下文。

小结

  1. MCP 是协议,CLI 是环境,它们并不在同一个层面上竞争;
  2. 有 shell 的地方,CLI 通常更省、更灵活,尤其是开发者工具和基础设施;
  3. 没有 shell、多用户、需要限定能力的地方,MCP 是更合适的选择;
  4. 上下文开销是 MCP 当前最大的痛点,但正在被延迟加载、代码调用等方式缓解;
  5. 大多数真实系统会混合使用:CLI 处理“本地的事”,MCP 连接“外部的服务”。

选工具的方式,归根结底还是看 Agent 在哪里跑、为谁工作、你愿意给它多大的权力。这和上一篇讲沙箱时的结论其实是同一件事。