上周在公司内部给 Agent 接了 5 个 MCP Server,总共 87 个工具。跑了两天,Token 账单直接翻了 4 倍——每次请求不管用不用得到,全量 Schema 都得塞进上下文。
MCP(Model Context Protocol)是 Anthropic 2024 年底发布的开放协议,目标是统一 AI Agent 与外部工具的对接方式。到 2026 年 Q1,Cursor、Claude Desktop、Windsurf 等主流 AI 开发工具都已支持 MCP。协议本身设计得很干净,但有一个工程问题越来越痛:工具描述吃掉的 Token 太多了。
本文从两个开源项目—— mcp2cli 和 Context Gateway ——出发,拆解 Agent 上下文压缩的技术路线与实测效果。
问题到底有多严重?
一个标准的 MCP 调用链是这样的:
用户输入 → Agent → MCP Server → 工具 Schema → 工具调用 → 结果解析 → 回复
其中工具 Schema 是 Token 消耗大户。看一组实际数据:
| MCP Server | 工具数 | Schema Token 占用 | 单轮调用总 Token |
|---|---|---|---|
| GitHub MCP | 23 | ~8,200 | ~12,000 |
| Slack MCP | 15 | ~5,100 | ~8,500 |
| 数据库 MCP | 31 | ~11,400 | ~16,000 |
单个 Server 还能忍。但现实中 Agent 往往同时接入多个 Server——3 个 Agent 各挂 3 个 MCP Server,一轮对话的 Schema 开销就能突破 50K Token。按 Claude 3.5 Sonnet 的 output 计价(根据 Anthropic 官方定价,187 光喂 Schema**。
路线一:mcp2cli — 静态预编译压缩
mcp2cli 的核心思路很直觉:把冗长的 JSON Schema 编译成 CLI 风格的单行描述,在送进 LLM 之前就完成瘦身。
原理拆解
原始 JSON Schema → mcp2cli 编译器 → CLI-style 描述 → 送入 LLM
直接看对比。原始 MCP 工具描述大概长这样(约 180 Token):
{
"name": "search_issues",
"description": "Search for issues in a GitHub repository using various filters",
"inputSchema": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"query": {"type": "string", "description": "Search query string"},
"state": {"type": "string", "enum": ["open", "closed", "all"]},
"labels": {"type": "array", "items": {"type": "string"}},
"sort": {"type": "string", "enum": ["created", "updated", "comments"]},
"per_page": {"type": "integer", "default": 30}
},
"required": ["owner", "repo"]
}
}
mcp2cli 编译后(约 35 Token):
search_issues <owner> <repo> [--query Q] [--state open|closed|all] [--labels L1,L2] [--sort created|updated|comments] [--per-page 30]
5 倍压缩,信息基本没丢。
实测数据
在 5 个 MCP Server、87 个工具的环境中跑了 200 轮测试:
| 指标 | 原生 MCP | mcp2cli | 变化 |
|---|---|---|---|
| Schema Token/轮 | 31,200 | 6,400 | 4.9x ↓ |
| 端到端延迟 | 2.8s | 1.4s | 2.0x ↓ |
| 工具选择准确率 | 94.2% | 91.7% | -2.5% |
| 月成本(1000轮/天) | $187 | $38 | 4.9x ↓ |
准确率掉了 2.5%,主要原因是 CLI 格式丢了 description 字段的语义细节。如果你的 MCP Server 工具命名本身就很清晰(比如 search_issues、create_pr),影响可以忽略;命名模糊的 Server 建议保留关键 description 字段。
快速上手
pip install mcp2cli
# 从 MCP Server 生成 CLI 描述
mcp2cli compile --server github-mcp --output tools.txt
# 在 Agent 中使用压缩后的工具描述
cat tools.txt | your_agent --tools-format cli
如果你用 OpenAI 兼容的 API 调用多个模型做对比测试,只需要切 model 参数,压缩层逻辑完全不用动:
import openai
client = openai.OpenAI(
base_url="https://api.ofox.ai/v1", # 国内直连,模型随便切
api_key="sk-xxx"
)
# 压缩后的工具描述直接当 system prompt 传入
response = client.chat.completions.create(
model="claude-sonnet-4-20250514",
messages=[
{"role": "system", "content": open("tools.txt").read()},
{"role": "user", "content": "帮我搜一下 langchain 仓库里关于 memory 的 issue"}
]
)
路线二:Context Gateway — 动态运行时裁剪
Context Gateway 走了另一条路:不碰 Schema 格式,在运行时根据用户意图动态选择要送给 LLM 的工具子集。
核心算法:Relevance-Weighted Pruning
Gateway 的裁剪流程分 4 步:
- 意图提取:解析当前用户 query,生成意图向量
- 工具相关性打分:计算每个工具 Schema 和意图向量的余弦相似度
- Top-K 筛选:只保留 K 个最相关工具的完整 Schema,其余折叠为单行摘要
- 历史窗口压缩:对多轮对话历史做 sliding window + 摘要
这个方案的核心优势是精度损失极小——高相关的工具保留了全部语义信息。
实测数据
同一套 87 工具环境:
| 指标 | 原生 MCP | Context Gateway | 变化 |
|---|---|---|---|
| Schema Token/轮 | 31,200 | 8,900 | 3.5x ↓ |
| 端到端延迟 | 2.8s | 2.1s | 1.3x ↓ |
| 工具选择准确率 | 94.2% | 93.8% | -0.4% |
| 月成本 | $187 | $53 | 3.5x ↓ |
准确率只掉了 0.4%(对比 mcp2cli 的 2.5%),代价是压缩比没那么激进,而且多了中间件的调用延迟。
两种路线怎么选?
| 维度 | mcp2cli | Context Gateway |
|---|---|---|
| 压缩方式 | 静态预编译 | 动态运行时 |
| 压缩比 | 4.9x | 3.5x |
| 准确率损失 | -2.5% | -0.4% |
| 部署复杂度 | 低(pip install) | 中(需部署中间件) |
| 适合场景 | 工具集稳定、追求极致省钱 | 工具动态变化、精度优先 |
我的建议:
- 个人项目 / 小团队:直接上 mcp2cli,简单粗暴省钱
- 生产环境 / 客户面向:Context Gateway 精度更稳
- 两个都要:先 mcp2cli 静态压缩,再过 Gateway 动态裁剪
我实际用的就是组合方案。在 87 工具环境下实测,组合压缩比 7.2x,准确率损失 -1.8%——这个 tradeoff 我觉得很划算。
不想引入依赖?手搓一个最小版本
如果你只是想在自己的 Agent 里快速加上工具筛选,50 行代码就能搞定:
import tiktoken
def compress_tools(tools: list[dict], query: str, top_k: int = 10) -> list[dict]:
"""最小化的工具上下文压缩:按关键词相关性保留 Top-K"""
query_tokens = set(query.lower().split())
scored = []
for tool in tools:
name_tokens = set(tool["name"].lower().replace("_", " ").split())
desc_tokens = set(tool.get("description", "").lower().split())
score = len(query_tokens & (name_tokens | desc_tokens))
scored.append((score, tool))
scored.sort(key=lambda x: -x[0])
result = []
for i, (score, tool) in enumerate(scored):
if i < top_k:
result.append(tool)
else:
result.append({
"name": tool["name"],
"description": tool["name"].replace("_", " ")
})
return result
在 30 个工具的场景下实测约 2.5 倍压缩,零依赖。关键词匹配换成 embedding 向量的话效果会更好,但那就得引入 sentence-transformers 了。
常见问题
Q: MCP 协议自身会解决 Token 膨胀问题吗?
MCP 规范正在讨论 tools/list 的分页和懒加载机制(参考 MCP spec discussions),但短期内改变不了 Schema 必须全量传给 LLM 这个基本事实。上下文压缩目前还是开发者自己的工程问题。
Q: 不同模型对压缩格式的适应性有差异吗?
差异不小。根据我的测试,Claude 系列对 CLI 风格的工具描述适应性最好(准确率几乎不掉),GPT 系列更偏好标准 JSON Schema 格式。跨模型测试的话建议在 API 调用层做统一抽象,压缩逻辑保持一致。
Q: 上下文压缩会影响 Agent 的多步推理吗?
工具选择阶段影响不大。但如果 Agent 需要在推理链中回溯之前调用过的工具结果,历史压缩可能导致信息丢失。建议对关键中间结果做显式状态存储(比如写到一个 state dict),而不是依赖上下文窗口记忆。
最后更新:2026-03-27