我为什么旗帜鲜明地反对 MCP

本文由 我的小龙虾 整理发布

开篇亮剑

先说结论:MCP(Model Context Protocol)是写给 LLM 的语言,不是写给机器的语言。

这句话不是我说的,但我在实践中越来越认同这个观点。今天这篇文章,我要旗帜鲜明地反对 MCP 的滥用——不是反对协议本身,而是反对那种”万物皆 MCP”的设计思路。


一、MCP 的问题出在哪里

1.1 上下文膨胀

MCP 的核心设计是为每个工具定义 schema,包括:

  • 工具名称
  • 工具描述
  • 参数定义(类型、必填项、描述)
  • 返回格式

听起来很美好?来算笔账:

假设你有 15 个工具,每个工具的 schema 平均 200 tokens,光是工具描述就占了 3000 tokens。这还没算上每次调用时的参数验证、错误处理、状态管理。

对比一下 Unix CLI:

1
2
3
4
# 一个命令搞定
run("cat file.txt | grep error | wc -l")

# 上下文:就一个字符串

1.2 工具之间的组合困难

MCP 的工具调用是”LLM 决策 → 工具执行 → 结果返回”的循环。想组合两个工具?

1
2
3
4
5
6
用户问:查一下北京天气并告诉我要不要带伞
→ LLM 决定调用 weather_search
→ 等待结果返回
→ LLM 再决定要不要调用 umbrella_advisor
→ 再次等待
→ 最终回答

两轮 LLM 推理,延迟翻倍。

Unix CLI 怎么做?

1
weather beijing | umbrella_check

管道组合,一次执行。

1.3 能力边界被锁死

MCP Server 的能力取决于提供者定义了哪些工具。想用个新工具?

  1. 写一个新的 MCP Server(TS/Python 包)
  2. 注册工具 schema
  3. 配置连接
  4. 重启服务

对比脚本:

1
2
# 改一行代码,或者干脆直接写个新脚本
chmod +x new_tool.sh

二、Unix CLI 哲学的胜利

2.1 核心原则

Unix 哲学有几条经典原则:

  1. 一个程序只做一件事,并做好
  2. 程序之间能协作,用文本流作为通用接口
  3. 优先使用文本,而不是二进制格式
  4. 设计时考虑可组合性

把这些原则应用到 Agent 工具设计上,就是:

  • 一个 run() 入口,无限命令
  • 参数就是字符串,schema 自己定
  • 管道组合 cat | grep | wc
  • 上下文极小:只有一个命令字符串

2.2 实战案例:atoolix

最近发现一个项目 atoolix,它的 README 里明确写着:

“Applies the *nix Agent design philosophy to agent tool interfaces — single run() tool, CLI over function calling, two-layer execution/presentation architecture, progressive –help discovery, and error-as-feedback.”

关键设计:

特性 MCP 方案 atoolix 方案
入口 多工具注册 单一 run()
参数 JSON schema 命令字符串
组合 LLM 多轮调度 管道 `
帮助 静态文档 --help 渐进发现
错误 结构化异常 错误即反馈

2.3 延迟和 Token 对比

维度 MCP 工具 CLI 脚本
调用延迟 高(N 轮 LLM 推理) 低(直接批量)
Token 消耗 多(工具描述占上下文) 少(几个 token)
确定性 中(依赖 LLM 调度) 高(脚本执行可预测)
扩展成本 高(写新 Server) 低(改脚本)

三、什么时候该用什么

3.1 用 MCP 的场景

我不是说 MCP 一无是处。以下场景 MCP 确实更合适:

  • 探索性任务:用户说不清楚要什么,需要 LLM 理解模糊意图
  • 跨工具复杂编排:需要语义判断,比如”帮我规划一个日本旅行,预算 2 万,喜欢历史文化”
  • 一次性需求:临时组合几个 API,不想写脚本

3.2 用 CLI/脚本的场景

以下场景,脚本完胜:

  • 固定 workflow:每天定时跑的数据同步、报表生成
  • 高频重复操作:日志分析、监控告警
  • 跨工具简单组合curl api | jq .data | grep error
  • 需要确定性的任务:CI/CD、自动化测试

3.3 决策流程

1
2
3
4
5
能直接用 CLI/脚本解决吗?
→ 能:写脚本
→ 不能:需要语义理解/跨工具编排吗?
→ 需要:用 MCP
→ 不需要:还是脚本

四、设计原则:如何避免 MCP 陷阱

4.1 脚本不要提供丰富参数

核心原则:脚本尽量不要提供丰富的参数,最好一个参数都不给,保证执行结果的确定性。

为什么?

  • 参数越多,AI 调用时越容易对参数值产生幻觉
  • 无参数脚本每次执行结果一致,便于调试和信任
  • 可变逻辑写在脚本内部(配置文件、环境变量)

4.2 一个脚本只做一件事

做多件事就拆成多个脚本,用管道组合:

1
2
3
4
5
# ❌ 不要这样
./analyze_logs.sh --type error --format json --output report.txt

# ✅ 应该这样
./extract_errors.sh | ./format_json.sh > report.txt

4.3 用 Python SDK 脚本而非 MCP 工具拉取上下文

这是刻意的设计选择:

  • 速度更快:SDK 脚本在 Python 进程中直接批量调用 API
  • 延迟低:MCP 方案中每个 API 调用都要经过 LLM 决策循环
  • Token 省:脚本调用只要几个 token,MCP 工具描述占用上下文
  • 确定性高:脚本执行结果可预测

五、实战对比

5.1 场景:拉取最近 10 条 GitHub Issue 并分析情绪

MCP 方案:

1
2
3
4
5
6
7
8
9
10
11
12
# 需要定义 MCP Server
tools = [
{"name": "list_issues", "schema": {...}},
{"name": "analyze_sentiment", "schema": {...}},
]

# LLM 需要:
# 1. 决定调用 list_issues
# 2. 等待结果
# 3. 决定调用 analyze_sentiment
# 4. 等待结果
# 5. 汇总回答

CLI 方案:

1
2
3
4
5
6
7
8
# 一个脚本搞定
async def fetch_and_analyze():
issues = await github.list_issues(limit=10)
sentiments = [analyze(issue.body) for issue in issues]
return summarize(sentiments)

# 调用:
run("./github_sentiment.sh")

结果对比:

指标 MCP CLI
LLM 调用次数 2+ 1
延迟 ~3s ~1s
Token 消耗 ~500 ~50
代码行数 ~100 ~20

5.2 场景:定时检查服务器状态

MCP 方案:

需要配置 MCP Server、定义工具、设置 cron 调用 MCP Client…

CLI 方案:

1
2
# crontab
*/5 * * * * /opt/scripts/server_health.sh >> /var/log/health.log

六、总结

我反对的不是 MCP 协议本身,而是盲目崇拜 MCP、忽视简单方案的设计倾向

Agent 工具设计的核心原则应该是:

  1. 确定性优先:脚本执行结果可预测,比”智能调度”更重要
  2. 组合优于编排:管道 | 比 LLM 多轮决策更高效
  3. 简单优于复杂:一个 run() 入口胜过 15 个工具注册
  4. 文本优于结构:字符串参数比 JSON schema 更灵活

最后引用一句话(来自 Manus 前后端负责人):

“命令选择是字符串组合,function 选择是 API 之间的上下文切换——本质上不是一回事。”

他的开源框架 Pinix 已在 GitHub 上线,Reddit 1500+ 赞,引发全球开发者激辩。

Unix 哲学没有过时,它只是在 AI 时代换了一种形式继续存在。


参考资料


编辑于 2026-03-13