我输入了一个两个词的提示——"Run tests"——到我的 AI 编程智能体里,我的 API 计费面板立刻记录到28,400 个输入 token。我没有附加任何源文件。我的对话历史完全是空的。然而,在 LLM 生成第一个字符之前,我的模型活跃上下文窗口中近 15% 就已经蒸发了。
罪魁祸首不是庞大的代码库索引,也不是臃肿的系统提示。而是我在过去一个月里急切配置的七个模型上下文协议(MCP)服务器。
连接 GitHub、Jira、Postgres、Sentry、Slack、Docker 和文件系统 MCP 服务器,感觉就像给你的 AI 智能体赋予了超能力。配置文件里看起来像是生产力升级的东西,却悄悄变成了对话每一轮的税负——降低推理准确率、拖慢首 token 时间,并消耗你的 token 预算。
工具定义如何劫持提示
一个常见的误解是,MCP 工具在后台闲置,直到模型决定调用它们。LLM 工具调用的底层机制并非如此。
为了让 LLM 知道何时以及如何调用工具,宿主应用必须将每一个已注册工具的完整 JSON Schema 定义注入到每一次 API 请求的系统上下文中(通过tools参数,位于Anthropic Tool Use API或OpenAI Function Calling中)。
当你安装一个一体化 MCP 服务器——比如完整的 GitHub 或云服务商集成——你很少只得到一两个工具。你会得到 30 到 60 个不同的端点(create_issue、update_pull_request、list_workflow_runs、get_commit_status等)。每个工具都带有:
- 一段冗长的描述,说明何时使用它、何时避免使用它。
- 嵌套的 JSON Schema 对象,定义每个参数、枚举和过滤器。
- 嵌入在属性描述中的校验约束和使用示例。
Schema 膨胀的解剖
让我们看看使用一个全面的第三方 MCP 服务器时,通过网络发送的单个典型工具定义:
{
"name": "search_project_issues_advanced",
"description": "跨仓库搜索和过滤项目 issue。每当用户询问 bug、任务、冲刺或工单状态时使用此工具。支持分页、按创建或更新日期排序,以及按负责人、标签、里程碑或状态过滤。",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "与 issue 标题或正文 markdown 匹配的全文本搜索字符串。"
},
"state": {
"type": "string",
"enum": ["open", "closed", "all"],
"description": "按 issue 当前生命周期状态进行筛选。默认为 open。"
},
"labels": {
"type": "array",
"items": { "type": "string" },
"description": "用于筛选的精确标签名列表(应用 AND 逻辑)。"
},
"sortBy": {
"type": "string",
"enum": ["created", "updated", "comments"],
"description": "用于对返回的 issue 列表排序的字段。"
}
},
"required": ["query"]
}
}
仅这一个 schema 就要花费大约180–220 个 token。现在把它乘以一个真实的开发者配置:
- GitHub MCP Server:约 35 个工具(约 8,500 个 token)
- Issue Tracker / Docs MCP:约 25 个工具(约 6,200 个 token)
- 数据库与可观测性 MCP:约 20 个工具(约 4,800 个 token)
- 浏览器 / 文件系统 / Git MCP:约 25 个工具(约 5,500 个 token)
在你还没提出任何问题之前,25,000 个 token的僵化 JSON schema 就已经被注入到你的提示词顶部。而且由于智能体工作流以循环方式运行——读取文件、运行命令、检查错误、编辑一行——一个 10 步的任务并不是只支付一次这 25,000 个 token 的代价。即使提示词缓存抵消了部分费用,模型的注意力机制仍然必须在整个 10 步过程中关注250,000 个 token 的累积 schema 噪声。
上下文腐化与工具混淆
为额外的输入 token 付费固然令人烦恼,但真正的损害发生在你的智能体的智能上:
-
工具选择幻觉:当 LLM 面对 80 多个相互重叠的工具时(例如,
read_file、get_file_contents、inspect_repo_file、search_code),它选择正确工具的能力会急剧下降。它开始把一个工具的参数字传给另一个工具,或者因 schema 校验错误而陷入无限重试循环。 - 注意力稀释(“迷失在中间”):每一个用于描述你从不使用的 Jira sprint 筛选器的 token,都在与你的实际代码、堆栈跟踪和架构规则争夺注意力。
- 不受控的输出转储:许多单体式 MCP 工具会把原始的、未分页的 API 响应直接返回到上下文窗口中——在你只需要 3 行状态摘要时,却转储了 15,000 个 token 的原始 JSON 元数据。
在 TypeScript 中审计你的 MCP Token 税
为了确切了解我的工具定义消耗了多少上下文,我写了一个零冗余的 TypeScript 审计器,用于测量工具 schema 序列化后的 token 占用,并将臃肿的多服务器配置与精简、聚焦的工具集进行对比。
你可以直接使用 Node.js 原生 TypeScript 执行来运行此脚本(node --experimental-strip-types audit-tools.ts在 Node 22.6+ 上,或直接node audit-tools.ts在 Node 23.6+ 上):
// audit-tools.ts
export interface ToolDefinition {
name: string;
description: string;
inputSchema: Record<string, unknown>;
}
export interface AuditReport {
toolCount: number;
rawBytes: number;
estimatedTokensPerTurn: number;
tokensPer10StepLoop: number;
}
/**
* 估算 JSON schema 的 LLM token 用量(结构化 JSON 约每 token 3.6 个字符)
* 并计算多步智能体循环中累积的上下文压力。
*/
export function auditToolFootprint(tools: ToolDefinition[]): AuditReport {
const serialized = JSON.stringify(tools);
const rawBytes = Buffer.byteLength(serialized, "utf8");
const estimatedTokensPerTurn = Math.ceil(serialized.length / 3.6);
return {
toolCount: tools.length,
rawBytes,
estimatedTokensPerTurn,
tokensPer10StepLoop: estimatedTokensPerTurn * 10,
};
}
// 模拟一个臃肿的 45 工具 MCP 配置与一个聚焦的 3 工具微型配置
const bloatedSetup: ToolDefinition[] = Array.from({ length: 45 }, (_, i) => ({
name: `enterprise_integration_action_${i + 1}`,
description:
"Comprehensive enterprise integration endpoint supporting filtering, pagination, sorting, metadata expansion, and audit logging across workspaces.",
inputSchema: {
type: "object",
properties: {
workspaceId: { type: "string", description: "Target workspace UUID" },
filterQuery: { type: "string", description: "Lucene-compatible filter syntax" },
includeMetadata: { type: "boolean", description: "Include full audit trail" },
limit: { type: "number", description: "Max records to return (1-500)" },
cursor: { type: "string", description: "Pagination cursor from previous call" },
},
required: ["workspaceId", "filterQuery"],
},
}));
const microToolSetup: ToolDefinition[] = [
{
name: "run_focused_check",
description: "Run local project verification and return concise failure lines only.",
inputSchema: {
type: "object",
properties: {
scope: { type: "string", enum: ["types", "tests", "lint"] },
},
required: ["scope"],
},
},
];
console.table({
"Bloated MCP Stack (45 tools)": auditToolFootprint(bloatedSetup),
“聚焦型微工具(1 个工具)”: auditToolFootprint(microToolSetup),
});
基准测试结果
当我将默认的“连接一切”配置与精简的、面向具体任务的工作流在 50 次真实调试会话中进行对比时,差异立竿见影:
| 指标 | 单体式 MCP 栈(52 个工具) | 聚焦型微工具(4 个工具) | 影响 |
|---|---|---|---|
| 基线 Schema 开销(第 1 轮) | 24,600 tokens |
640 tokens |
基线噪声减少 97% |
| 10 步智能体循环占用 | 246,000 tokens |
6,400 tokens |
大幅节省上下文 |
| 首次工具选择准确率 |
78%(频繁出现参数混淆) |
99% |
工具幻觉几乎为零 |
| 平均首 token 时间(TTFT) | 3.4 seconds |
1.1 seconds |
智能体响应速度提升 3 倍 |
小型、可组合的微工具
与其加载庞大的、通用型 MCP 服务器(将整个 REST API 暴露给你的 LLM),不如构建并使用小型、单一用途的开源工具专为智能体使用而设计。
1. 优先使用聚焦型 CLI 脚本,而非臃肿的 Schema
如果你的智能体已经有一个 bash/终端执行工具,它就不需要一个包含 30 个工具的 GitHub MCP 服务器。一个 40 行的 TypeScript CLI 工具,封装你的确切工作流、剔除 JSON 噪声,并且只打印可操作的行,就能在输入(零 Schema 膨胀)和输出(精选的 stdout)两方面节省数千个 token。
#!/usr/bin/env node
// scripts/pr-summary.ts — 完整 GitHub MCP 服务器的零膨胀替代方案
import { execFileSync } from "node:child_process";
const prNumber = process.argv[2];
if (!prNumber) {
console.error("Usage: pr-summary <pr-number>");
process.exit(1);
}
// 只获取 AI 推理实际所需的精确字段
const raw = execFileSync(
"gh",
["pr", "view", prNumber, "--json", "title,state,body,files"],
{ encoding: "utf8" }
);
const data = JSON.parse(raw);
console.log(`PR: ${data.title} [${data.state}]`);
console.log(`Changed Files (${data.files.length}):`); for (const file of data.files) { console.log(` - ${file.path} (+${file.additions}/-${file.deletions})`);
}
2. 设计“窄腰”MCP 工具
当你确实构建或使用 MCP 服务器时,请遵循 Unix 哲学:
- 整合 CRUD 蔓延:与其暴露 12 个用于列出、筛选和读取 issue 的独立工具,不如暴露 1 或 2 个范围严格限定的只读工具。
-
精简属性描述:编写简洁、高信号的 schema 描述。LLM 不需要用三段话解释一个
limitinteger 是做什么的。 - 在边界处截断并格式化输出:绝不要向模型返回原始的 50 KB API 响应体。在 TypeScript 工具处理函数内部先过滤并格式化响应,再返回给智能体。
如何为你的 AI 智能体配备工具
收藏这张矩阵,用来判断某个工作流应该放在 MCP 服务器、轻量级 CLI 脚本,还是项目技能中:
| 方案 | 前期 token 成本 | 输出噪声 | 最佳用例 | 反模式风险 |
|---|---|---|---|---|
| 单体式第三方 MCP | 🔴 非常高(5k–25ktokens) |
🔴 高(原始 API JSON) | 快速的非编码探索 | 在所有项目中全局启用 5 个以上服务器 |
聚焦式微型 MCP(1–3个工具) |
🟢 低(300–800tokens) |
🟢 低(精选文本) | 有状态集成、数据库只读查询、自定义团队工作流 | 为每个边缘情况都添加标志位到inputSchema
|
| 零膨胀 TypeScript CLI | ⚡零(使用现有 shell 工具) | 🟢 极小(自定义 stdout) | Git 自动化、测试运行器、日志解析器、CI 分诊 | 未能在出错时提供清晰的--help输出 |
| 按需技能 / 提示词 | ⚡加载前为零 | 🟢 低 | 领域特定规则、发布检查清单、代码生成模板 | 把所有指令都塞进全局系统提示词 |
精简 AI 工作流的起飞前检查清单
在开始下一次编码会话之前,花 60 秒做一遍这项审计:
- [ ]禁用全局 MCP 服务器:绝不要全局加载 Jira、Slack 或数据库 MCP。仅在主动需要时按项目或按会话启用它们。
- [ ]将活跃工具上限设为 15 个:如果你的智能体同时有超过 15–20 个活跃工具,请审计并禁用过去一周内未触发过的工具。
- [ ]区分读工具与写工具:禁用具有破坏性或写入密集的 MCP 端点(
delete_repo,merge_pr,drop_table)——它们会浪费上下文 token 并引入安全风险。 - [ ]用轻量级 TypeScript CLI 替换话痨式 MCP:如果某个工具只是包装了一个 REST 端点或本地命令,就把它变成一个小巧、可组合的脚本,返回干净、经过总结的文本。
要点
AI 智能体的能力不是用你给它挂了多少工具来衡量的——而是用还剩多少干净、未被污染的上下文可用于实际推理来衡量的。通过舍弃臃肿、单体式的 MCP 服务器,转而采用小巧、锋利、单一用途的 TypeScript 工具,你能削减 token 成本、消除工具幻觉,并让智能体明显更快。
你的编辑器里现在有多少个 MCP 工具处于激活状态?随着你添加更多集成,你有没有注意到智能体变慢了?
与我们联系
如果你觉得这篇文章有帮助,欢迎看看我们的开源工具,让我们建立联系!
- 🛠️Necoz 开源: GitHub 上的 necoz-oss|DEV 上的 @necoz
- 💻作者 GitHub: johnnylemonny
