site logo

Marico's space

Agent 响应前的两笔 Token 账单:MCP Tools 消耗 71,929,Skills 消耗 1,109,242

编程技术 2026-10-02 14:48:43 11

最近折腾 Agent 上下文优化,踩了几个坑,这篇把问题说清楚。大家都在看第一张账单,几乎没人发现第二张。

什么意思?打开你的 Agent 上下文,在你输入任何字符之前数数已经加载了多少东西。这有两项,来自两个不同的地方:

  1. MCP(Model Context Protocol,模型上下文协议)工具的 schema。 每个连接的服务端都会发送它暴露的所有工具的完整 JSON 描述。
  2. Agent 技能(Skills)。 技能目录里每个 SKILL.md 都是整体加载的。

我用 tiktoken(OpenAI 官方的分词器),采用 cl100k_base 编码来测量这两项。工具那边已经很糟糕了,技能这边更糟。而解决这两个问题的思路是一样的。

账单 #1:工具的 schema

连接足够多的 MCP 服务器,光是工具描述就会吃掉你上下文窗口的很大一块。我的基准测量是一个包含 255 个工具的目录。渲染成完整的 JSON schema,那是 71,929 个 token。

而且这不是一次性成本。清单位会在每次对话轮次重新发送。在一个会话里问十二个问题,你就付了十二次这笔账。

根本原因是结构性的。选择一个工具只需要它的名字。你不需要完整的 schema 来决定是否调用某个东西。但默认的实现会在每次请求时预先发送所有工具的 schema。所以你实际上是在为那些大部分时候都不会打开的文档付钱。

账单 #2:技能,没人审查的那个

技能这边更大,因为它本质上就更大。工具是名字加 schema,技能则是整个文档:指令、示例、工作流、检查清单。当目录被暴露为"读取你需要的 SKILL.md"时,朴素实现会读取整个目录。

在这台机器上,技能目录包含 418 个技能。全部拼接在一起,每个 SKILL.md 是 1,109,242 个 token。

这塞不进上下文窗口,所以大多数方案做了那件看起来很谨慎但实际不是的事情。它们只加载部分内容,由 Agent 猜测它需要什么,然后每个技能付几千个 token,每轮都付,永远。你注意不到,因为账单分散在许多看起来合理的小决策里。

同一个修复同时解决两张账单

这个洞察一旦你看清了,其实挺无聊的。选择某样东西只需要它的名字。

  • 你不需要工具的完整 schema 来决定是否调用它。
  • 你不需要技能的完整文本来决定是否阅读它。

所以先暴露名字,按需获取内容。

工具。 255 个工具只保留名字的索引是 581 个 token。比原来的 71,929 减少了 99.2%。完整 schema 只在你真正调用工具时才出现。

技能。 一个宿主页指针 39 个 token 替换了整个目录。单次查询返回匹配的那几个技能,查询成本是 501 个 token。你只对胜出的技能读取完整的 SKILL.md 文件。同一个思路,另一张账单。

指针的全部职责就是一句话:不要加载目录,请求匹配结果,然后只读取那些文件。

我把这个做成了 mcptoon,一个零依赖的命令行工具。纯 Python 标准库,227KB,它让每个服务器和每个技能都保持配置就绪、可寻址,同时把它们的完整文本挡在上下文窗口之外。mcptoon manifest 给你工具的名字-only 视图。mcptoon skills resolve "<你想做什么>" 返回最佳匹配的技能,格式是 JSON。没有什么是预装的,在选择之前什么都不加载。

先测量你自己的数字

别拿我的目录当你的。税跟你接线多少成正比,修复也是。

统计你实际携带的内容。加载你真实的工具列表和技能目录,用 tiktoken 处理一遍,然后把完整内容跟名字-only 视图对比。如果差距很大,你有一张值得削减的账单。如果差距小,就不用。

这个测量才是关键。也是几乎所有人都跳过的步骤,这就是为什么第二张账单长期无人注意。

诚实的边界

当你有很多工具和技能时这才值得。如果你就跑几个服务器和十几个技能,这些都不重要。税是真实存在的但很小,而网关是又一个需要维护的东西。不要为了优化而优化。

我的经验法则:先审查,只有测量结果告诉你上下文真的拥挤了,才考虑用名字索引层。

值得讨论的部分

工具 schema 这边已经是个公认的问题了。Anthropic 自己的工程文档论证了这一点,Firecrawl 的基准测试也测量了同样的任务:1,365 个 token 通过 CLI 对比 44,026 个 token 通过 MCP,差距是 32 倍。这个讨论正在进行中。

技能这边还没有。而它其实是两者中更大的那个,因为技能是文档,不是 schema。

所以我想在评论区得到这个问题:当你的 Agent 加载一个技能时,加载的是指针还是整个文件?如果你测量过这个数字,我想看看,因为我觉得大多数人在付第二张账单却从未见过发票。