减少 MCP 令牌消耗:我们如何把工具定义削减 60%

代理能调用的每个 MCP 工具,都是模型可能在每次请求时读取的一段 JSON。我们的工具大约有 95,000 个令牌。本文介绍如何在两分钟内测量你自己的工具,我们在不删除任何工具的前提下削减了什么,以及防止它再次膨胀的预算检查。

10 月 27 日,一位付费用户取消了 AgentsRoom 订阅,理由是令牌。反驳之前,我们先测了测应用给它启动的每个代理额外加了什么。

最大的数字出乎意料。启动指令很小。交给每个代理的 MCP 工具(待办列表、项目记忆、代理间邮件、终端、SSH、数据库等)共 191 个,约 380 KB JSON。按每个令牌约 4 字节算,光工具定义就有约 95,000 个令牌。一周前还是 78,000。没人这样决定过,只是每个新工具都加了自己的一段话。

我们开发的 AgentsRoom 是一款桌面应用,并排运行 Claude Code、Codex 等编码 CLI,并为它们提供这些工具。下面讲我们测到了什么、在不删任何工具的前提下削减了什么,以及防止反弹的检查。这些都适用于你自己的 MCP 服务器。

简短回答

  • 工具定义是模型可能在每次请求时读取的 JSON:名称、描述、输入 schema。按每个令牌约 4 字节估算。
  • 每轮是否都要付这笔费用,取决于 CLI。 Claude Code 默认延迟加载 MCP 工具,需要时才加载定义。有些 CLI 每次请求都发送全部 schema。
  • 先测量,再削减。 用 Claude Code 的 /context,或用一个 40 行脚本向任意服务器索取工具列表。
  • 缩短文字,保留工具。 描述只说做什么、何时用。使用手册移到代理按需读取的文档。我们的工具从约 95,000 个令牌降到约 38,000 个,只剩原来的 2.5 分之一。
  • 在构建里设一个上限。 没有它,定义会自己反弹。

工具定义的成本,以及由谁来付

MCP 客户端连上服务器后,会请求工具列表。每一项包含名称、写给模型的描述和参数的 JSON schema。客户端把列表转成工具定义发给模型,模型由此知道工具存在、该怎样调用。

所以描述里的每个字都是模型的输入。读的频率取决于客户端:

  • Claude Code 默认延迟加载 MCP 工具。 其文档(2026 年 11 月 3 日读取,已安装 Claude Code 2.1.314)说,会话开始时只加载工具名称和服务器说明,Claude 需要某工具时才通过工具搜索加载其定义。在 ENABLE_TOOL_SEARCH=false 时、auto 模式下低于上下文窗口 10% 阈值时、早于 Claude Opus 4 和 Sonnet 4 的模型上,或对标记了 alwaysLoad 的服务器,定义会预先加载。
  • 其他 CLI 的做法不同。 在我们 10 月 28 日的测试中,让代理只回答“OK”:Claude Code 在我们的工具延迟加载时从 67,500 个令牌起步,Codex CLI 0.164 从 25,000 个令牌起步,没有整块发送我们的 schema,而 OpenCode 连接时就加载了我们的 175 个工具和 19 个浏览器工具,每次请求都发送。同一服务器,账单相差很大。

工具列表不变时,提示缓存能减轻成本:首次请求后,稳定的前缀以输入价格的一小部分从缓存读取。但它不会归还空间。每次请求,这些定义仍占着上下文窗口,紧挨着你的代码和对话。延迟加载模式也有代价:每个工具首次使用前要多一步搜索。

两分钟测量你的工具

在 Claude Code 中,运行 /context。它按来源拆分上下文窗口,并标出延迟或按需加载的 MCP 工具。在项目里开新会话,输入任何内容前运行,就能看到开工前配置占了多少。

对任何服务器、任何 CLI,直接问服务器。stdio 上的 MCP 服务器使用 JSON-RPC,每行一条消息:发送 initialize,再发送 tools/list,然后测量返回内容。下面的脚本就做这件事,只用 Node,零依赖:

// Usage: node mcp-tool-cost.mjs <command that starts the server> [args...]
import { spawn } from 'node:child_process';

const [cmd, ...args] = process.argv.slice(2);
const server = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'ignore'], shell: process.platform === 'win32' });
const tools = [];
let buffer = '';

const send = (msg) => server.stdin.write(JSON.stringify({ jsonrpc: '2.0', ...msg }) + '\n');
const listTools = (cursor) => send({ id: 2, method: 'tools/list', params: cursor ? { cursor } : {} });

server.stdout.on('data', (chunk) => {
  buffer += chunk;
  let end;
  while ((end = buffer.indexOf('\n')) >= 0) {
    const line = buffer.slice(0, end).trim();
    buffer = buffer.slice(end + 1);
    if (line) handle(JSON.parse(line));
  }
});

function handle(msg) {
  if (msg.id === 1) {
    send({ method: 'notifications/initialized' });
    listTools();
  } else if (msg.id === 2) {
    tools.push(...msg.result.tools);
    if (msg.result.nextCursor) listTools(msg.result.nextCursor);
    else { report(); server.kill(); }
  }
}

function report() {
  const rows = tools
    .map((tool) => ({ name: tool.name, bytes: Buffer.byteLength(JSON.stringify(tool)) }))
    .sort((a, b) => b.bytes - a.bytes);
  const total = rows.reduce((sum, row) => sum + row.bytes, 0);
  for (const row of rows.slice(0, 10)) {
    console.log(`${String(row.bytes).padStart(7)} bytes  ~${Math.round(row.bytes / 4)} tokens  ${row.name}`);
  }
  console.log(`${rows.length} tools, ${total} bytes, about ${Math.round(total / 4)} tokens`);
}

send({
  id: 1,
  method: 'initialize',
  params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'tool-cost', version: '1.0.0' } },
});

用 CLI 启动服务器的同一条命令来运行它,例如 node mcp-tool-cost.mjs npx -y @playwright/mcp@latest。它会打印最重的十个工具和总量。我们在 2026 年 11 月 3 日得到的结果:

服务器工具数JSON约合
@modelcontextprotocol/server-filesystem 2.1.01412,554 字节3,100 个令牌
@playwright/mcp 0.0.842521,034 字节5,300 个令牌
AgentsRoom 工具,10 月 28 日削减前191约 380,000 字节95,000 个令牌
AgentsRoom 工具,10 月 28 日削减后191约 152,000 字节38,000 个令牌
AgentsRoom 工具,11 月 3 日203159,957 字节40,000 个令牌

每个令牌 4 字节只是估算,够用来比较服务器、看趋势,不能用来预测账单。真实的分词器随模型和文本而变,我们在数了自己的 38 亿个令牌时就发现了。

上面的文件服务器都算合理。问题在总和:五个这种规模的服务器,不算我们这种,已有 20,000 个令牌。

我们削减了什么,又没动什么

规则是:不删除任何工具,不删除任何参数,不删除任何允许的取值或必填字段。 代理必须保留全部能力,能改的只有文字。

  1. 工具描述用一到三句话说明做什么、何时用。 不讲怎么做。字节都堆在“怎么做”上:规则、默认值、边界情况、示例,以及代理某次用错工具后补写的警告。
  2. 一个参数给一句话,或者什么都不写,前提是名称、类型和允许取值列表已说明一切。多个工具反复出现的参数(project、author、工单 id)统一用一句标准说明。
  3. 使用手册是搬走,不是删掉。 每个工具族有一个说明文件,代理首次调用该工具族前会先读它。我们最大的工具(保存代理团队的那个)现在只在代理请求时通过工具调用返回完整字段参考,不再夹带在 schema 里。
  4. 第二遍核对了每个事实。 从描述删掉的每句话都在说明文件里查过,缺的就补上。

部分结果,按每个工具的 JSON 字节数:

工具之前之后
teams_save19,7059,791
triggers_save16,0144,071
backlog_update13,1504,253
backlog_create11,5654,399

总计 380 KB 变成了 152 KB。原有十三个工具超过 4 KB,现在还剩四个,因为它们字段太多。整件事的思路是:选工具时,模型只需知道它存在、何时调用。细节可以等到使用时再说。

写 MCP 服务器的话,Claude Code 还给了一个理由:一开始就加载的工具描述会在 4,096 个字符处截断,用搜索匹配描述时也只读这个限度内的部分。之后的重要内容可能永远不会被看到。

启动文件也用同样的办法

工具 schema 不是唯一的固定成本。代理在用工具前,还会读一份工具说明文件,已长到 57.7 KB,约 14,000 个令牌,每次启动和恢复都完整读取。有用户反馈,恢复后的 Codex 代理几分钟就触及工作上下文上限。

解决思路相同。文件变成一个约 11 KB 的索引:适用于所有工具的规则,加上每个工具族一行,指向它自己的文件。31 个工具族文件只在首次调用该族前读取,文字一字未改。只碰待办列表的代理,永远不会读到数据库部分。

这也适用于很长的 CLAUDE.md 或 AGENTS.md:始终加载的文件只保留每个任务都需要的内容,其余放链接。详见我们的 AGENTS.md 与上下文文件指南。

让推送失败的预算

最让我们担心的不是 95,000,而是一周前的 78,000。每个工具加一段话,每段都合情合理,总量却在七天里涨了 20%。

于是大小变成了一项构建检查。一个 JSON 文件记录预算:

{
  "totalBytes": 160000,
  "perToolBytes": 4000,
  "oversized": {
    "teams_save": 9791,
    "triggers_save": 4071,
    "backlog_update": 4253,
    "backlog_create": 4399
  }
}

脚本在沙箱里启动我们的 MCP 服务器,向每个服务器索取工具,按字节测量 JSON.stringify(tool),总数超过 totalBytes 或某个工具超过 perToolBytes 时报错退出。原本就过大的四个工具连同大小一起列出:一个只能单向转动的棘轮。它们可以变小,不能变大;其中一个变小时,脚本会要求调低数字。它在每次推送前和发布构建中运行。上限可以提高,但那是写进文件的决定,不是让检查通过的手段。

六天后的 11 月 3 日,同一检查读到 203 个工具、159,957 字节:多了十二个工具,离上限仅 43 字节。下一个工具得自己腾地方。这正是这项检查的用意。

关掉你用不到的

最便宜的令牌,属于代理从来看不到的工具。

  • 只在需要的项目里启用服务器。 用户级别声明的服务器会跟着你进入每个仓库。在 Claude Code 中,放进项目的 .mcp.json,它就只待在有用的地方。
  • alwaysLoad 只留给少数几个工具。 Claude Code 的文档说得很直白:每个预先加载的工具都会占用本可留给对话的上下文。
  • 除了定义,也要留意工具结果增加了什么。 返回整页或整张表的工具,一次调用的成本可能超过其定义一整天的开销。

在 AgentsRoom 中,设置 > AgentsRoom 工具列出我们的每个工具族及其大小,你可以针对所有项目、某个项目、某个文件夹或某个代理,关闭一个工具族或单个工具。关闭的工具根本不会列给代理,不占任何上下文。AgentsRoom MCP 页面介绍了这些工具,会话追踪则按大小排序显示填满代理上下文的内容,含 MCP 工具。

我们没有改动的部分

同一次测量还发现了另外两项成本,我们有意没动。

  • 启动简报:告诉每个代理其角色和状态汇报方式,Claude 的中位数约 5,000 个令牌,约占 200,000 令牌窗口的 2.5%,从第二次请求起从缓存读取。
  • 每个代理在一轮结束时写的状态文件:基于 71 份本地 Claude 转录测量,约占上下文令牌的 2.5%。

缩减任何一项,省下的都不到窗口的 3%,还可能损害代理的工作质量。工具定义有 95,000 个令牌,时间花在了那里。

大家真正会问的问题

MCP 工具会消耗多少令牌?

每个工具的成本是它的完整定义:名称、描述和输入 schema,以 JSON 发送。粗略按每个令牌 4 字节计。2026 年 11 月 3 日实测:官方 filesystem 服务器 14 个工具约 3,100 个令牌,Playwright MCP 25 个工具约 5,300 个。AgentsRoom 的工具缩短前约 95,000 个令牌,如今约 40,000 个。

Claude Code 会把所有 MCP 工具都加载到上下文窗口里吗?

默认不会。Claude Code 的工具搜索(tool search)会延迟加载 MCP 工具定义:会话开始时只加载工具名称和服务器说明,Claude 需要某个工具时才加载其定义。设置 ENABLE_TOOL_SEARCH=false、使用早于 Claude Opus 4 和 Sonnet 4 的模型,或给服务器标记 alwaysLoad,定义就会重新在一开始进入上下文。

MCP 服务器能减少令牌消耗吗?

可以,前提是工具返回简短答案,替代代理原本要完整读取的内容,比如用数据库查询代替整份导出。但定义占用上下文,结果也一样:Claude Code 默认在单个 MCP 结果超过 10,000 个令牌时警告,在 25,000 个令牌处截断。只有结果比它替代的内容更小,MCP 服务器才省令牌。

怎样查看我的 MCP 工具占用了多少上下文?

在 Claude Code 中运行 /context:它按来源拆分上下文,并标出延迟加载的 MCP 工具。对任何服务器、任何 CLI,都可以向服务器索取工具列表并测量 JSON,本文的 40 行脚本做的就是这件事。

好的 MCP 工具描述是什么样的?

用一到三句话说明工具做什么、何时调用,把代理会搜索的词放在前面。一个参数一句话;名称、类型和允许取值已说明一切时就不写。规则、边界情况和长示例放进代理按需读取的文档。况且 Claude Code 本就会把预先加载的描述截断在 4,096 个字符。

怎样防止工具定义再次膨胀?

给它们一个构建能检查的预算。我们有一个 JSON 文件,写着总上限和单工具上限,还有一个脚本:启动我们的 MCP 服务器,测量每个工具,超过任一上限就报错退出,每次推送前运行。原本就过大的工具可以变小,不能变大。

简而言之

你的 MCP 工具是模型要读的文字,却没人像审查代码那样审查它。先测一次:Claude Code 里用 /context,任何服务器都可用上面的脚本。然后让每条描述只说做什么、何时用,把“怎么做”挪到代理需要时才读的文件里,再在构建里放一个数字防止反弹。

我们没删任何工具,就从约 95,000 个令牌降到约 38,000 个。如果你用同一套工具运行多个代理,AgentsRoom 可免费下载,MCP 服务器页面介绍了如何只声明一次服务器,用于所有 CLI 和项目。

下载 AgentsRoom

在一个窗口中运行你所有项目的所有 AI 代理。

免费下载 AgentsRoom

配套应用:随时随地监控你的 Agent

使用 Claude、Codex、Antigravity CLI 或其他 AI 提供商。

获取扩展程序
Chrome Web Store

把 Bug 和需求直接发送到您的公开待办清单。

多项目管理
多供应商
多代理运行
实时状态
文件差异与提交
移动应用
实时预览
代理团队
浏览器自动化
Backlog 驱动开发
提示词库
技能库
查看所有功能

继续阅读