MCP(Model Context Protocol)几乎成了 AI 编码助手的"标配接口"——各大模型平台争相支持,社区工具服务器遍地开花。但 Quandri 工程团队最近甩出一组实测数据,直接戳破了"标配"背后的账单:4 个 MCP 服务器注册的工具定义,就要吃掉约 21000 个 token,占掉一个 20 万 token 上下文窗口的 10.5%。换句话说,还没开始干活,十分之一的"内存"已经被工具声明占满了。
这不是一个可以忽略的小数。当你的任务本身就需要长代码、多文件上下文时,每 1% 的窗口都值得计较。
21000 token 从哪来?
MCP 的工具定义不是一行注释,而是完整的 JSON Schema 声明——每个工具需要描述名称、参数类型、参数约束、返回格式,甚至附带人类可读的 description 字段供模型理解语义。一个典型的 MCP 工具定义大概长这样:
{
"name": "search_files",
"description": "Search for files matching a pattern in the workspace. Returns file paths and metadata.",
"inputSchema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "Glob pattern to match, e.g. '**/*.ts'"
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return",
"default": 50
},
"include_hidden": {
"type": "boolean",
"description": "Whether to include hidden files",
"default": false
}
},
"required": ["pattern"]
}
}
一个工具就几百 token。一个 MCP 服务器通常注册 5–15 个工具,4 个服务器叠加,21000 token 并不夸张。问题在于:这些 token 每次请求都要发送,不管你这次对话是否用到 search_files。
这就像每次打开终端都先加载一整本 man 手册——你只想跑 ls,但它先把 grep、sed、awk 的完整帮助文档塞进你的工作记忆。
开销的连锁反应
token 占用只是第一层。它引发的连锁问题更值得警惕:
- 有效上下文缩水:20 万窗口减去 2 万工具定义,再减去系统提示和对话历史,留给实际代码和文件内容的窗口可能只剩 60–70%。复杂重构任务需要同时看多个文件时,窗口捉襟见肘。
- 成本放大:按 GPT-4 级别模型的定价,2 万 input token 每次请求多花约 $0.06。一天 50 次交互就是 $3,一个月近 $90——全花在"声明工具"上。
- 响应质量下降:模型在冗长的工具列表中更容易"迷路",选错工具或忽略用户意图的概率上升。Quandri 的测试也印证了这一点——工具越多,模型调用准确率越不稳定。
什么时候 CLI 反而更划算?
Quandri 团队的核心结论是:不是所有场景都需要 MCP。当你的需求是"让模型执行一组确定性操作"时,直接调用 CLI 命令往往更轻、更快、更可控。
典型适合 CLI 替代的场景:
| 场景 | MCP 方案 | CLI 替代 |
|---|---|---|
| 搜索项目文件 | 注册 search_files 工具 |
find . -name "*.py" |
| 读取文件内容 | 注册 read_file 工具 |
cat src/main.py |
| 运行测试 | 注册 run_tests 工具 |
pytest tests/ |
| Git 操作 | 注册 git_status、git_diff 等多个工具 |
git diff HEAD~1 |
CLI 方案零工具定义开销,模型只需要知道"你可以执行 shell 命令"这一条信息。
下面是一个实际可用的对比实验——用 Python 搭一个最小 CLI 代理,让模型通过执行命令完成任务,无需任何 MCP 服务器:
"""
最小 CLI 代理:模型通过执行 shell 命令完成任务,零 MCP 开销
依赖:openai >= 1.0, python-dotenv
运行前设置环境变量:OPENAI_API_KEY=sk-xxx
"""
import subprocess
import json
from openai import OpenAI
client = OpenAI() # 从环境变量读取 API key
SYSTEM_PROMPT = """你是一个编码助手。你可以通过执行 shell 命令来完成任务。
可用指令格式:
<cmd>命令内容</cmd>
例如查看项目结构:<cmd>find . -type f -name "*.py" | head -20</cmd>
例如读取文件:<cmd>cat src/main.py</cmd>
例如运行测试:<cmd>pytest tests/ -q</cmd>
每次只输出一条命令。等待执行结果后再决定下一步。"""
def run_cli_agent(task: str, max_steps: int = 8):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": task},
]
for step in range(max_steps):
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0,
)
reply = resp.choices[0].message.content
print(f"\n--- Step {step + 1} ---")
print(f"模型输出:\n{reply}")
# 提取 <cmd>...</cmd> 中的命令
if "<cmd>" in reply and "</cmd>" in reply:
cmd = reply.split("<cmd>")[1].split("</cmd>")[0].strip()
print(f"执行命令: {cmd}")
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True, timeout=30
)
output = result.stdout + result.stderr
print(f"输出 (前500字):\n{output[:500]}")
messages.append({"role": "assistant", "content": reply})
messages.append({"role": "user", "content": f"命令输出:\n{output}"})
if "任务完成" in reply or "DONE" in reply:
print("\n✅ 任务完成")
break
else:
print("模型未输出命令,任务结束")
break
# 示例:让模型找到项目中的测试文件并运行
run_cli_agent("找到这个项目中所有 pytest 测试文件,然后运行它们,报告结果。")
这个代理的系统提示只有几行,token 开销不到 200。对比 MCP 方案的 21000 token,差距是 100 倍。当然,它的能力范围也更窄——但如果你今天只需要"搜文件、看代码、跑测试",窄就是优势。
MCP 仍然不可替代的场景
CLI 不是万能药。以下场景 MCP 的优势明显:
- 需要结构化交互:比如数据库查询返回 JSON、API 调用需要精确参数校验——CLI 的自由文本输出反而增加模型解析负担。
- 安全边界要求:MCP 服务器可以对每个工具做权限控制(只读、限定路径等),而
shell=True是一把打开的万能钥匙,生产环境风险极高。 - 多工具编排:当任务需要组合 5+ 个不同服务(代码搜索 + 文档查询 + 部署触发 + 日志分析),CLI 的串联成本和出错率会快速上升。
关键判断标准:你的模型需要"理解工具语义"还是"执行确定性命令"? 前者选 MCP,后者选 CLI。
决策清单
在引入 MCP 之前,跑一遍这个清单:
- 算 token 账:列出你计划注册的所有 MCP 工具,估算总定义 token 数(每个工具约 300–800 token)。如果超过上下文窗口的 5%,考虑裁剪。
- 评估使用频率:哪些工具 80% 的对话都不会用到?把它们拆到单独的"按需加载"服务器,只在特定任务中激活。
- 检查替代路径:对于确定性操作(文件搜索、命令执行、日志查看),CLI 或直接 API 调用是否足够?
- 测量准确率:用你的典型任务测试模型在 MCP 工具列表中的调用准确率。如果低于 85%,工具列表可能已经过长。
- 安全审计:如果选择 CLI 方案,必须限定可执行命令的白名单,禁止
rm、sudo、网络请求等危险操作。
MCP 不会死——它解决的是真实互操作问题。但"所有交互都走 MCP"是一种过度抽象。就像你不会用 Swagger 文档来执行 git pull,有些事情直接敲命令就是最优解。工程师的职责是在协议优雅性和实际开销之间找到那个具体的平衡点,而不是追着标准跑。