減少 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 驅動開發
提示詞庫
技能庫
檢視所有功能

繼續閱讀