创见博客
MCP特别费token?为什么只说了一句「你好」,Agent 却吃掉了 2.4 万 Token?
七崽爱吃小饼干2026/08/04阅读 8

我最近抓了一次 Agent 的请求包。

这次对话平平无奇,用户只说了两个字:

text
你好

模型也只回了一句:

text
你好,有什么需要我帮你处理的?

如果只看聊天窗口,这大概是一次轻得不能再轻的请求。但抓包里的数字完全不是这么回事:请求正文超过 12 万字符,输入消耗了 24,621 个 Token。

两个字的问题,为什么需要两万多个 Token?

继续往请求体里翻,答案藏在一个叫 tools 的字段里。

那些没有被调用的工具

这次请求一共带了 111 个工具。

其中有两个 Playwright MCP Server。一个是普通 Playwright,另一个通过浏览器扩展连接。它们各自暴露了 24 个工具,功能几乎完全一样:打开网页、点击按钮、输入文字、读取页面快照、查看网络请求、上传文件、切换标签页……

也就是说,仅 Playwright MCP 就给模型塞了 48 个工具定义,合计约 33,353 个字符。

问题在于,这一轮对话根本没用到浏览器。模型没有调用任何 MCP 工具,只是礼貌地回了一句问候。但在回答之前,这 48 份工具说明已经作为输入上下文发给了模型。

这有点像你走进一家五金店,只问老板“你好”,老板却先递给你一本三百页的商品手册。你当然不需要买扳手,但在决定“不买扳手”之前,手册已经递过来了。

理解这个现象,要先弄清楚 MCP 在 Agent 里究竟处于什么位置。

模型其实不认识 MCP

MCP 的全称是 Model Context Protocol,中文通常译作“模型上下文协议”。它让 Agent 能用相对统一的方式连接浏览器、数据库、文件系统、Figma 或企业内部平台。

这个名字很容易让人产生一个错觉:模型会直接通过 MCP 与外部系统通信。

实际上,大多数时候并不是这样。

真正连接 MCP Server 的,是 OpenCode、Claude Desktop 这类 Agent Host。模型夹在更上面,只负责决定“要不要使用某个工具”。

text
大语言模型
    ↑
Agent Host / MCP Client
    ↑
MCP Server
    ↑
浏览器、数据库或其他系统

MCP Server 启动后,会通过 tools/list 告诉 Host 自己有哪些能力。比如浏览器 MCP 可能返回一个叫 browser_navigate 的工具:

json
{
  "name": "browser_navigate",
  "description": "Navigate to a URL",
  "inputSchema": {
    "type": "object",
    "properties": {
      "url": {
        "type": "string",
        "description": "The URL to navigate to"
      }
    },
    "required": ["url"]
  }
}

这段信息对程序来说很好理解:工具叫 browser_navigate,作用是打开一个 URL,调用时必须传入字符串类型的 url。

但这还只是 MCP Client 和 MCP Server 之间的语言。要让模型使用它,Agent Host 还要再做一次翻译。

MCP 最终变成了 tools

在这次抓包里,Agent 请求的是一个 Responses API。OpenCode 从 MCP Server 拿到工具列表后,把每个 MCP 工具转换成了 Function Calling 格式,放进请求的 tools 数组。

转换后的工具大致是这样:

json
{
  "type": "function",
  "name": "playwright_browser_navigate",
  "description": "Navigate to a URL",
  "parameters": {
    "type": "object",
    "properties": {
      "url": {
        "type": "string",
        "description": "The URL to navigate to"
      }
    },
    "required": ["url"],
    "additionalProperties": false
  }
}

工具名前多了一个 playwright_。这是 MCP Server 的名字,用来区分不同 Server 提供的同名工具。

所以,从模型的视角看,并不存在一个抽象的“MCP”。它看到的只是一组普通工具:每个工具都有名字、用途和参数说明。模型根据用户的问题,判断该调用哪一个。

如果模型决定打开网页,它会返回一次 Function Call:

json
{
  "type": "function_call",
  "name": "playwright_browser_navigate",
  "arguments": "{\"url\":\"https://example.com\"}"
}

Agent Host 收到后,才会根据 playwright_ 这个前缀找到对应的 MCP Server,通过 tools/call 真正执行 browser_navigate。执行结果再被送回模型,模型据此继续回答。

完整过程其实是:

text
MCP Server 声明能力
→ Agent Host 将能力转换成 tools
→ 模型选择工具
→ Agent Host 调用 MCP Server
→ 执行结果返回模型

MCP 负责让外部能力可以被发现和调用,tools 才是这些能力进入模型上下文的方式。

系统提示词里并没有藏一份 MCP 手册

看到这里,可能会有另一个疑问:这些 MCP 能力是不是也被写进了系统提示词?

至少在这次抓包里,没有。

我检查了完整的 developer prompt。里面没有 Playwright 的工具列表,也没有 browser_click、browser_snapshot、tools/list 或 tools/call 等具体内容。

系统提示词负责规定 Agent 的行为方式,例如怎样编辑代码、什么时候使用工具、如何验证修改。工具定义则单独放在请求顶层的 tools 数组中。

可以把两者理解成:

text
系统提示词告诉模型:你应该怎样工作。
tools 告诉模型:你现在能做哪些事情。

提示词里确实出现了几次“MCP”这个词,但只是某些 Skill 的简介提到自己能处理 MCP 配置,并不是 MCP 工具本身。

Token 就花在“我能做什么”上

一个工具定义看起来不长,几十个工具叠在一起就完全是另一回事了。

模型不仅要知道工具名,还要理解工具描述、参数名、参数类型、必填项、枚举值,以及嵌套对象和数组的结构。像点击按钮这样的工具,就要说明目标元素、鼠标按键、是否双击、是否按住组合键。表单和文件上传工具的参数还会更复杂。

这次抓包的数据很直观:

内容规模
请求中的全部工具111 个
两个 Playwright MCP 的工具48 个
全部工具定义约 96,267 字符
Playwright MCP 工具定义约 33,353 字符
完整请求正文约 120,372 字符
输入 Token24,621

字符数和 Token 数并不是一比一关系,但比例已经足够说明问题:Playwright MCP 的工具定义约占全部工具字符数的三分之一,占整个请求正文字符数的 27.7%。

而这一次,用户只是说了“你好”。

更麻烦的是,这些工具通常不只发送一次。很多 Agent 实现会在每轮模型请求中附上当前可用的完整工具列表。即使这一轮没有调用工具,模型也要先拿到工具定义,才能做出“不调用”的决定。

Prompt Cache 可以降低重复计算的成本和延迟,但它没有从结构上消除这部分上下文。工具仍然在那里。

这次还存在一个额外的浪费:普通 Playwright MCP 和 Extension Playwright MCP 各自提供了一套几乎相同的 24 个工具。同一个点击能力,模型会同时看到:

text
playwright_browser_click
playwright-extension_browser_click

重复能力不仅多花 Token,还把选择题变难了。模型需要判断两个看起来几乎一样的工具到底该用哪一个。

为什么 Skill 看起来便宜得多

MCP 和 Skill 经常一起出现在 Agent 配置里,但它们解决的并不是同一个问题。

MCP 更像工具箱。它提供可以执行的动作:点击按钮、查询数据库、读取网页、调用业务接口。

Skill 更像操作手册。它告诉 Agent 怎样完成某类任务:如何写一篇技术文章、如何部署项目、如何整理会议纪要。

两者在 Token 消耗上的真正差异,不是名字,而是常见的加载方式。

一个设计合理的 Skill 系统通常采用渐进式加载。模型一开始只看到 Skill 的名称和一句简介:

text
article-writing:用于撰写文章、教程和长篇内容。

只有用户真的要求写文章时,Agent 才加载完整的写作规范。也就是先看目录,再按需翻开其中一本手册。

MCP 工具在很多实现里却是另一种路径:Agent Host 先把所有工具的完整 Schema 都发给模型,再让模型决定用不用。它不是先给目录,而是直接把工具箱里的每件工具连同说明书一起摊在桌上。

这就是为什么 MCP 往往比 Skill 更吃上下文。

不过,把结论简单说成“MCP 比 Skill 费 Token”也不严谨。如果系统把所有 Skill 的完整正文一次性塞进提示词,Skill 一样昂贵。反过来,如果 MCP 能根据任务动态选择工具,它也可以很轻。

真正决定成本的是:能力是全量注入,还是按需加载。

更轻的选择:Skill + CLI

如果一个系统已经有成熟的 CLI,其实不一定还要为它接一层 MCP。

大模型天生就很擅长使用命令行。它在训练数据里见过大量 Shell、Git、npm、Docker、数据库客户端和云平台命令,理解“执行命令、读取输出、根据报错修正参数”这种工作循环。对于 Agent 来说,真正需要常驻的执行能力可能只有一个终端工具。

至于某个业务 CLI 具体有哪些命令、鉴权方式和安全约束,可以写进 Skill。

于是整个过程变成:

text
平时只暴露 Skill 的名称和简介
→ 用户提出相关任务
→ Agent 按需加载完整 Skill
→ Skill 告诉 Agent 应该调用哪个 CLI
→ Agent 通过终端执行命令
→ 读取 JSON 或文本结果并继续工作

比如操作一个博客系统,模型不需要在每一轮都携带 draft_get、draft_update、draft_publish、article_search 等十几个 Function Schema。系统平时只需要告诉它:这里有一个 visionary-cli Skill,可以管理文章和草稿。

真正遇到发布文章的任务时,Agent 再加载 Skill,得到类似这样的命令说明:

bash
visionary-cli draft update \
  --id 688 \
  --content-file tmp/article.md \
  --json

执行层面仍然具备完整能力,但这些知识不必常驻在每一次模型请求里。

这种组合把职责拆得很干净:

text
Skill 负责渐进式披露知识
CLI 负责稳定地执行能力
终端工具负责连接 Agent 与 CLI

它还有一个实际好处:CLI 本身可以被人类、脚本和 CI 复用。遇到问题时,Agent 也可以先执行 --help,而不是要求模型事先记住每个参数。

当然,Skill + CLI 也不是所有场景的答案。如果工具需要持续会话、实时事件、复杂交互状态,或者希望客户端自动发现并用严格 Schema 校验参数,MCP 仍然很有价值。但对于已经存在 CLI、任务以命令执行和结果读取为主的场景,直接上 MCP 可能确实太重了。

判断标准不应该是“能不能做成 MCP”,而应该是:这项能力是否值得在每轮请求里占据一份完整工具定义。

MCP 不一定要这么重

这次抓包里,最直接的优化是只保留一个 Playwright MCP。两套几乎相同的工具没有必要同时常驻,仅这一项就能砍掉一半 Playwright 工具定义。

更进一步,Agent 可以先判断任务类型,再决定加载哪些工具。代码修改不需要浏览器工具,普通问候更不需要。只有当用户真的要操作网页时,才把 Playwright 工具交给模型。

也可以把 Skill 的渐进式加载思路借过来:先给模型一个轻量的能力索引,确认需要浏览器之后,再展开完整的 24 个工具。工具描述本身也可以适当压缩,避免在每个参数里重复背景信息。

如果系统已经有可靠的 CLI,还可以更直接地绕开大批工具注入:保留一个终端工具,用 Skill 在需要时告诉 Agent 如何调用 CLI。这样执行能力没有减少,常驻上下文却可以小很多。

这些优化都指向同一件事:不要把“Agent 拥有什么能力”和“模型这一轮需要知道什么能力”混为一谈。

工具越多,不等于 Agent 越好

MCP 解决了一个很重要的问题:它让外部能力可以用统一协议接入 Agent。但接入能力只是第一步,如何管理这些能力,仍然是 Agent Host 的责任。

这次请求里,用户只说了“你好”,模型却收到了 111 个工具。48 个来自 Playwright MCP,其中还有一半是重复能力。最终工具一个没用,输入却消耗了 24,621 个 Token。

所以,MCP 上下文消耗大的根本原因,并不是 MCP 协议本身,而是下面几件事叠在了一起:

text
大量工具
× 完整 JSON Schema
× 每轮重复发送
× 多个 Server 能力重叠

一个 Agent 可以连接一百种能力,但不代表它应该在每轮对话里把一百份说明书都背给模型听。

有些能力适合 MCP,有些能力用 Skill + CLI 已经足够。选择后者并不是倒退,而是把模型已经擅长的终端执行能力利用起来,再用 Skill 补上领域知识和操作边界。

当工具越来越多,真正重要的能力不再是“还能接入什么”,而是“这一刻应该让模型看到什么”。能用一份按需加载的说明书和一条命令解决的问题,就没有必要每次都把整间工具仓库搬进上下文。

评论
0/100