2025年我做AI工具评测的那段时间,几乎把所有主流MCP server都装了个遍。为了这件事我甚至把几台开发机的Node环境和Python环境都重新整理了一遍,折腾到半夜。然后我发现一个特别有意思的事实:这二三十个MCP server里,有接近一半本质上就是在给现有CLI工具套壳。某个数据库MCP server,底层稳定调用psql和mysql客户端;某个文件系统MCP server,就是把ls、find、cp、mv这些原生命令重新包装了一遍;还有相当一部分server连执行方式都一样——spawn一个子进程跑shell命令,再把stdout塞回JSON结构返回给模型。
装到第五个这种“套壳server”的时候,我停下来问了自己一个问题:既然底层就是CLI,AI为什么不能直接调CLI?这个问题越想越深,后来我发现AI智能体真正需要的交互范式,可能恰恰是Unix先驱们五十年前留下的那套“文本进、文本出”的命令行接口,而不是一个又一个贴着标准化标签的MCP协议层。这篇文章我不急着下结论,而是把整个推导过程、实操踩坑和历史上的相似案例都摊开讲一遍。
1. 现象观察:MCP很热,但我看到一半server在给CLI套壳
先把这个话题的背景说清楚。MCP的全称是Model Context Protocol,Anthropic在2024年底开源的一套开放协议,目的是给大模型应用和外部工具、数据源之间建立一个标准化的连接层。它的核心概念是工具(tools)、资源(resources)、提示(prompts),消息格式基于JSON-RPC 2.0。随后OpenAI也在2025年宣布支持MCP,Google那边生态也在跟进,所以整个行业说“MCP是智能体时代的USB-C接口”是有道理的,它解决了一个真实存在的痛点:以前每个AI应用都要自己对接工具API,协议不统一,每个工具商都要为一个客户定制一份SDK。
但我在实际试用过程中发现,MCP的宣布形态和真实工程形态之间存在一道裂缝。很多官方或第三方提供的MCP server,并不是真的为AI场景重新设计了数据交互方式,而仅仅是把已有的命令行工具“翻录”成JSON-RPC接口。比如你想让智能体查一下数据库,网上能搜到一堆database MCP server,翻开它的源码,核心逻辑就是拼一个psql命令,然后执行,然后把stdout包进result对象里。你想让智能体搜一下代码仓库,对应的MCP server多半是在内部调用了rg、grep或者git grep。这不是某一家server的问题,我看了十几个之后隐隐觉得,这是一种行业惯性:大家默认“标准协议”肯定比“直接跑命令”更高级,于是拼了命把所有能力都塞进JSON-RPC的框里。
这时候拿出来对比一下两种范式的实际形态,会很直观:
| 维度 | CLI范式 | MCP范式 |
|---|---|---|
| 数据格式 | 纯文本(stdout/stderr) | JSON-RPC结构化对象 |
| 输入方式 | 命令+参数 | 工具名称+arguments对象 |
| 输出状态 | exit code + 文本 | result / error 嵌套JSON |
| 可组合性 | 管道、重定向、脚本 | 需要编排层、框架层 |
| 可调试性 | 终端直接跑,结果立现 | 常驻进程+inspector,链路长 |
| 程序生命周期 | 每次调用短进程 | 常驻server进程 |
| 部署成本 | 单个二进制 | 运行时+依赖+config+网络 |
| 生态沉淀 | 五十年全球实战 | 两年新生态 |
这张表放在这儿不是为了制造非黑即白的对立,而是想说明一件事:CLI这套范式在AI出现之前就已经是人和工具之间的“最短文本通路”,到了LLM时代它的价值非但没有衰减,反而因为AI是纯文本消费者,被放大了。后面我会逐步拆开讲。
2. 底层逻辑:文本流是LLM的母语,CLI天然与模型同构
2.1 预训练语料里的秘密:模型早就“会”用CLI了
很多人忽略了一个关键事实:大模型在预训练阶段读过海量的人类文本,而里面最密集、最规范、最不需要猜测语义的文本之一,就是命令行使用记录。
想想看——GitHub上的README写着git clone、npm install、docker run;Stack Overflow的热门答案贴着一整段bash命令;各种技术博客的教程里全是curl -X POST、jq .items[0].id;Linux的man page和源代码注释里全是命令示例。模型对ls -la会输出什么、grep -rn "xxx"会匹配什么、exit code 1通常意味着什么,这些模式已经深深印在权重里。
这意味着,当我们把AI智能体的工具接口设计成“直接执行CLI命令”时,模型几乎不需要额外学习。它看到find . -name "*.log" -mtime -7,它脑子里涌现的是海量的、从训练语料里总结出来的执行预期。反过来,如果给它一个MCP server的JSON schema,让它理解tools/list返回的工具定义,这套schema每个server风格还不一样,有的用驼峰参数名,有的用下划线,有的返回嵌套对象,模型能学,但每换一个server都要重新适应一遍。
这也是为什么我在实际使用中,遇到一个CLI agent和一个MCP agent做同样的事情时,CLI agent的“上手速度”总是快得多——它不需要加载工具说明文档,也不需要额外的“系统提示词”去解释工具用法。我只需要告诉它一句:你可以使用任意shell命令。它就懂了。
2.2 最短路径推导:为什么多一个协议层就多一分损耗
我们退回到本质来看。LLM的推理过程是“token进,token出”,它读入的是文本,输出的也是文本。CLI的接口是“命令文本进,输出文本出”。两者之间没有任何格式转换,这条链路的长度是:模型生成命令文本→shell进程执行→stdout文本回到模型上下文。
MCP链路就不一样了:模型要先生成一个JSON-RPC调用(工具名+arguments)→经过MCP client发送到MCP server→server解析参数,可能再去spawn一个子进程跑实际命令→拿到结果后再把数据封装成result对象→client再转发给模型。同样一个“查看某个目录下最近修改的文件”操作,CLI模型只需生成ls -lt ~/projects/ | head -20;MCP模型要先生成工具调用list_files带上一堆参数,等server返回数据,然后还得判断这些数据够不够,不够再发起第二次调用。
链路每多一层,出问题的概率就指数级上升。服务器端解析出错、参数类型不匹配、返回结构里的某个字段是null、网络传输中断、server进程崩溃……这些在MCP模式里都是需要模型自己消化和处理的异常情况。而在CLI模式里,shell把几乎所有的异常都映射成三样东西:stdout、stderr和exit code。模型看到exit code 2,马上知道是命令用法不对,可以自己去看usage或者换个写法。就这么简单。
2.3 一次直观对比:读日志、查数据的CLI写法与MCP写法
我在本地搭过一个对比测试。任务很常规:统计项目日志目录下ERROR出现的总数,并按错误类型排序输出前五行。
CLI agent收到任务后,自然生成了一套组合命令:
cd /var/log/myapp ls -la grep -h "ERROR" *.log | wc -l grep -h "ERROR" *.log | grep -oE "ERROR_[A-Z_]+" | sort | uniq -c | sort -rn | head -5每一步的输出都是干净文本:第一行ls -la告诉它有哪些日志文件;第二行给出ERROR总数;第三行直接给出类型分布top5。整个过程不需要任何外部工具定义,不需要server配置,模型自己就会判断下一步该跑什么。
同样的任务换到MCP agent上,我先得给它配一个带list_log_files、search_in_file、read_lines等工具的server。模型要先调用list_log_files拿到文件列表,再调用search_in_file带上一堆正则参数,一次调用只能搜一种模式,想统计多字节类型就得把错误类型的枚举全部试一遍。如果server返回的结果是JSON数组而不是格式化文本,模型还要阅读冗长的结构。比CLI方案至少多消耗三倍以上的思考步骤,token开销就更不用说了。
2.4 Token经济学:CLI可裁剪上下文,MCP往往被迫全量接收
还有一个特别实在的问题:上下文窗口是智能体最贵的资源,而CLI是天然支持“按需裁剪”的。
假设要让AI检查一个1GB日志文件里的错误,它完全可以只读最后200行的错误信息和前20行的时间范围,用tail -n 200 logs/app.log把它需要的部分拿进上下文。想看得更细?tail -n 200 | grep ERROR。想只看某个时间段?sed -n '/2026-01-01 10:00:00/,/2026-01-01 11:00:00/p' logs/app.log。模型的每一个问题都可以变成一次精准的文本裁剪。
MCP server返回的是什么?通常是一个完整的结构化数据对象。有些server确实支持输入过滤条件,但每个server的参数设计都不一样,有的支持limit,有的支持offset,有的一股脑返回全部内容。这时候你会面临一个糟糕的选择:要么全量收下,白白烧掉几千token;要么再花一轮对话给模型解释这个server的过滤机制。我评测的时候,好几个server在工具描述里写着“返回文件内容”,但根本没有提供start_line和end_line参数,AI一次就能把一个中型源码文件全部吞进上下文。
3. 实操侧写:接入MCP server过程中反复踩到的那些坑
3.1 配置复杂度与运行时依赖
聊完了理论,说点发生过在我机器上的真实状况。
第一个坑是配置。MCP server要跑起来,涉及的东西远比一个CLI工具多:要装好对应的运行时,Node的版本要满足要求;要写server配置,指定command、args、env;还要确认网络端口是否被占用;有些server还要你先启动一个后台服务,或者先初始化一个数据库。一套流程下来,快的半小时,慢的能折腾一个下午。而一个CLI工具,下载好二进制或者执行一条brew install就完事了,--version一跑,立刻知道能不能用。
你可能觉得“配置一次以后就不用管了呀”。问题在于,这些server的版本迭代速度极快。我有一台开发机上,某个MCP server的两个项目版本要求的Node版本之间产生了冲突,升级Node之后旧的server直接崩了,而那个项目群组里早期绑定的server还依赖旧版本。这种环境地狱在我接触CLI工具的十来年里几乎没遇到过。
3.2 常驻进程的脆弱性
第二个坑是进程模型。MCP server是常驻服务,一个server进程同时对接所有agent调用。这意味着它一旦崩溃,所有正在跑的任务全部失败。CLI工具则完全不同:每次调用都是新的短进程,跑完就退出,天然没有“服务器挂了”这种状态。
我实际碰到过不只一次:某个MCP server在跑完一个超大查询后内存暴涨,进程直接OOM。然后agent的下一个调用就报“client closed”或“connection reset”。模型完全不知道发生了什么,只能反复重试,最后还是失败。换成CLI方式,psql -c "SELECT ..."如果在执行过程中内存不足,进程自己退出,错误信息直接清晰地出现在stderr里,模型立马就能判断“这个查询太重了,得加上LIMIT或者换个方案”。
3.3 黑盒调试:当MCP链路出错时没有stderr可看
第三个坑也是最让人抓狂的:MCP的调试是黑盒。
CLI的排错路径极其直接——你把这个命令手动跑一遍,stdout和stderr清清楚楚,exit 1到底哪里错了基本一眼就能定位。MCP链路如果出错,你需要打开MCP inspector、查看server日志、检查JSON-RPC的返回结构、猜测是server解析问题还是client转发问题,经常会绕一大圈才能找到原因。有一次我遇到一个server返回501,查遍文档都不知道什么意思,最后才发现是server版本过旧、接口已废弃。这种排查过程对生产效率是极大的拖累。
3.4 权限模型反而更粗:MCP的边界是工具,CLI的边界是参数
还有一个常被误解的点是安全。不少人觉得MCP有标准协议、有工具白名单,比让AI直接执行shell更安全。但实际上MCP的权限边界往往更粗。
一个MCP server暴露的是“工具级”的权限。如果它提供了delete_file和read_file工具,那么AI只要在工具列表里看到它们,理论上就都能调用。控制权在“这个工具是否存在”,而不在“这个具体操作是否应该允许”。CLI方式则相反——权限边界体现在命令参数上:ls只能读目录,rm必须明确传-rf和路径才会删东西;git push只影响当前仓库,curl的--data-binary @/etc/passwd才能把文件内容发出去。命令本身是细粒度的,模型显然也会更谨慎地构造参数。
再说审计。shell history、终端的SHELL_LOG、甚至直接在日志里记录所有被执行的命令,这些都是现成的、人人都懂的审计手段。你给AI配一个CLI白名单,所有命令落盘,后面出问题查日志非常清晰。MCP的调用记录分散在各个server自己的日志里,格式还不统一,想追溯一次事故的完整调用链非常痛苦。
3.5 轻量替代方案:白名单CLI工具包+系统提示词
正是因为被MCP折腾够了,我后来自己改成了一套“CLI优先”的智能体方案。
做法不复杂:在系统提示词里给AI明确一份允许执行的命令白名单,比如ls、cat、grep、awk、sed、find、git、curl、jq、psql这几类,同时附上每条命令的简要说明和常用参数示例。然后再给它一个约束:优先使用这些命令完成任务,遇到它们解决不了的问题再报告。
这套方案跑了几个月,效果出乎意料地好。原本需要一个进程常驻的数据库MCP server才能做到的事,现在一条psql -c命令就解决;原本一个文件系统server提供的那些功能,ls和find全包了。AI的意图理解能力和代码生成能力足够强,它不需要严格的工具schema来指导自己,给它一句“你用grep和awk能做这个”它就知道怎么组合。
4. 历史视角:中间协议层的宿命与文本管道的生命力
4.1 协议考古:CORBA、SOAP、Thrift、gRPC的启示
如果我们把时间轴拉长看,会发现一个规律:凡是试图在“调用者”和“被调用者”之间强加一层复杂协议中间层的技术,最终都会因为复杂度失控而逐渐退潮。
90年代有人用CORBA,想让不同语言写的对象能在网络上互相调用,结果搞出了一套繁琐的IDL和对象引用体系,做事极其笨重,后来基本销声匿迹。千禧年前后SOAP红极一时,为了在不同系统间传个消息要包好几层XML信封,业务方苦不堪言,最后被RESTful API替代。再后来Thrift和gRPC在特定大厂内部站住了脚,靠的是性能和强类型,但它们也没能像Unix管道那样变成“通用接口”。
反过来看CLI和文本流,那真是生命力顽强到让人敬畏的东西。1970年代Unix管道的设计,一个命令的输出接另一个命令的输入,靠的就是纯粹的字节流。没有协议健壮性的承诺,没有类型系统,没有结构化元数据,只有一个约定:标准输入、标准输出、标准错误、退出状态码。这套约定撑住了人类和计算机的对话几十年,如今到了LLM时代,它反而刚刚好踩在模型最舒适的感知区。
4.2 为什么CLI契约的“松”恰是它的“强”
很多人会说,CLI的接口太松散了——没有版本管理,没有schema,命令输出格式动不动变。这话放在人机交互层面确实有道理,但对LLM来说,这个“松”恰恰是它最大的优势。
LLM是一个概率模型,它不依赖严格的类型系统来保证语义正确,它依赖的是统计规律和上下文理解。命令的输出格式就算变化了,模型也能根据上下文和文本内容自行推断含义。ls -l第一行开头如果是total 8,它知道那是总大小;序列里第二列如果是数字和日期,它明白那是大小和修改时间。在一个“松”的契约下,模型完全可以像一个经验丰富的老工程师一样,见招拆招地解读输出。而MCP那套严格的JSON schema,恨不得把所有字段类型、描述都固定下来,对模型来说这反而是一种束缚——它需要先解析schema,再按这个schema组织调用,再等结果,再核对结果里缺了什么。
有一个类比我想了很久,现在觉得越来越贴切:MCP之于CLI,有点像“为了标准化API调用而设计的ERP系统”之于“一个老师傅的直接经验”。ERP把一切流程都制定了规范的接口卡,覆盖所有部门的业务;老师傅则不用填任何表单,看一眼现场就知道该干什么。对通用智能体来说,最舒服的交互方式永远是“直接上手操作工具”,而不是“先填写一份接口申请表”。
4.3 反面案例:当MCP server的schema和实现不一致时
我实践中遇到过不止一次双方“打架”的情况:一个server的tools/list里声称只支持三个参数:query、limit、offset。但真正调用的时候,server后端某个依赖内部其实需要database和table两个参数,没有它就会默默返回一个空列表。AI反复调用三次都拿不到数据,它还以为是自己查询写错了,不断变换query的内容,空耗了好几轮。这种“schema与实现不一致”的问题,在CLI世界里基本不存在,因为命令本身不存在接口描述这层“元数据”,实际能不能跑,跑一下就知道。
4.4 协议标准化的诱惑与陷阱
还有一点想提醒所有团队:我们总是会被“标准化”这个词打动,觉得有了标准就能降低接入成本、复用生态。这个逻辑在人和人协作时完全成立,但在模型和工具协作时却要打折扣。因为模型天然就是一个“复用众包经验”的专家,它不需要一套新的标准协议来学习,它已经学会了人类几千年积累下来的操作接口——命令、参数、文本输出。你非要在上面加一套新的协议标准,相当于给一个已经会说通用语言的人塞一本全新的行业术语手册,他不一定会说错,但效率必然下降。
5. 生态侧证:主流AI厂商的产品形态都在向CLI回归
5.1 Claude Code、Codex CLI与Gemini CLI的统一取向
我们再看看行业头部玩家的动向。
Anthropic自家的Claude Code,是一个跑在终端里的智能体工具,核心交互方式就是和它对话,然后由它来执行命令、读文件、跑测试,它默认的工具面板里不仅有Read、Write、Edit,更关键的是一个Bash工具,允许模型直接执行shell命令。GitHub、数据库、测试、构建,它全部通过CLI体系完成,几乎没有默认接任何MCP server才能干活的说法。
OpenAI的Codex CLI同样是命令行形态,你装着它之后,基本就是和智能体在终端里协作,让它操作文件系统、跑git、执行测试。Google的Gemini CLI也是类似的设计。三大AI厂商的核心智能体产品不约而同选择了“CLI+文本流”作为第一交互界面,这绝非偶然。因为它们都知道,模型最擅长消费的就是文本,最省事、最稳定的通道就是把命令行交到模型手里。
你可能说,厂商同时也开放了MCP支持。是,它们支持MCP,但那是为了兼容生态、覆盖CLI不擅长的UI操作领域,而不是把MCP定义为智能体的“终极范式”。产品形态的投票已经很诚实了。
5.2 反思:MCP的价值不在覆盖CLI,而在覆盖CLI之外的UI世界
到这里我也要把话说公道:MCP绝不是一无是处,它在某些场景下是唯一可行的方案。典型的有这么几类:
一是UI密集型任务。CLI天生没有屏幕坐标、没有像素、没有窗口概念,要让AI去操作浏览器、桌面应用、IDE界面,就必须有一个工具能把这些UI元素抽象成模型可调用的接口,比如Playwright MCP、Computer Use这类。CLI给不了这层能力的。
二是跨应用的数据协调。假如你想让AI帮你读完日历、查看邮件、创建Notion笔记,这三个应用如果都没有好用的CLI(很多商业应用确实没有),那MCP server就提供了一种统一的接入方式。这种情况下,CLI确实无从下手。
三是团队统一封装。如果公司里有一堆内部API,不想直接暴露给模型,用MCP server封装一层统一入口、统一鉴权、统一日志,这也是合理的工程选择。
所以我的观点从来不是“消灭MCP”,而是“先CLI,后MCP”。CLI能覆盖的场景绝不绕路,CLI覆盖不了的地方再考虑MCP补位。在同一个智能体里,完全可以既有CLI工具又有MCP server,比例按实际需求来。
5.3 给团队的选型建议
结合踩坑经验,我给你们一个可以直接抄走的决策清单:
- 如果任务可以用现成CLI完成(文件操作、日志分析、代码扫描、数据库查询、Git操作、网络请求),直接给模型配白名单命令,别上server。
- 如果应用本身提供了CLI(各种官方cli工具),优先用官方CLI,MCP server往往只是它的一个更脆的封装。
- 如果目标是浏览器自动化、桌面应用控制、无CLI的商业SaaS软件、跨多个私有应用统一调度,再考虑上MCP。
- 如果团队想完整审计AI行为,CLI命令日志比MCP工具调用日志好查得多,首选记录
bash history或命令日志。 - 不妨在同一份agent配置里,定义好CLI白名单,再按需挂载少量MCP server,前端网关还是那个网关,后端工具可以混合。
我自己的agent目前就是这种混合架构:文件、日志、代码、网络这些高频率基础操作全部走CLI白名单,只有涉及浏览器操作时会调Playwright MCP。跑了大半年,稳定性明显好于以前“什么都接MCP”的阶段。
6. 从“套装协议”到“通用文本”:一次范式的认知转换
写到这里我想起自己在MCP评测中最后悔的一段弯路。当时为了给AI接通本地的日志分析能力,我花了一个周末去部署一个日志分析MCP server——装依赖、调配置、改端口、看日志,累得够呛。后来我突然想明白一件事:这个server的核心能力无非就是grep + awk + sort。既然模型自己就会生成这些命令,我还专门为它搭一个服务干吗?
那天晚上我把server卸载掉,在agent的系统提示词里加了一行:如果任务是与日志文件相关,你可以直接使用shell中可用的文本处理工具,自行选择合理的命令行方案。之后所有日志分析任务,AI都自己跑grep、wc、sort、uniq完成,又快又稳,还没有中间层报错。
这件事给我的启发挺大的。我们这一代工程师已经习惯了一层层往上搭抽象:HTTP要包成RPC,RPC要包成消息队列,消息队列要包成微服务网关。但LLM的思维方式完全不一样,它可以直接理解最底层、最原始的文本流。给模型加一大堆协议层,反而是在限制它。CLI之所以是AI智能体更合适的交互范式,核心原因就是:它不需要额外的代理,它就是那个能直接被模型看到、摸到、感受到的“真实世界接口”。
这个观点现在慢慢也在社区里得到了验证。越来越多人在讨论智能体时会把“能不能执行任意CLI命令”视为一种关键能力,而不是可选项。未来无论MCP生态怎么发展,我相信“文本进、文本出”的CLI交互会始终是智能体工具箱里优先级最高的那一档。因为它最朴素,也最接近智能体思考的本质。