MCP 协议下的 Token 战争:Agent 上下文压缩从 mcp2cli 到 Context Gateway 的实战拆解

8 阅读6分钟

上周在公司内部给 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 太多了

本文从两个开源项目—— mcp2cliContext Gateway ——出发,拆解 Agent 上下文压缩的技术路线与实测效果。

问题到底有多严重?

一个标准的 MCP 调用链是这样的:

用户输入 → Agent → MCP Server → 工具 Schema → 工具调用 → 结果解析 → 回复

其中工具 Schema 是 Token 消耗大户。看一组实际数据:

MCP Server工具数Schema Token 占用单轮调用总 Token
GitHub MCP23~8,200~12,000
Slack MCP15~5,100~8,500
数据库 MCP31~11,400~16,000

单个 Server 还能忍。但现实中 Agent 往往同时接入多个 Server——3 个 Agent 各挂 3 个 MCP Server,一轮对话的 Schema 开销就能突破 50K Token。按 Claude 3.5 Sonnet 的 output 计价(根据 Anthropic 官方定价,15/Mtoken),一个月跑1000轮就是15/M token),**一个月跑 1000 轮就是 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 轮测试:

指标原生 MCPmcp2cli变化
Schema Token/轮31,2006,4004.9x ↓
端到端延迟2.8s1.4s2.0x ↓
工具选择准确率94.2%91.7%-2.5%
月成本(1000轮/天)$187$384.9x ↓

准确率掉了 2.5%,主要原因是 CLI 格式丢了 description 字段的语义细节。如果你的 MCP Server 工具命名本身就很清晰(比如 search_issuescreate_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 步:

  1. 意图提取:解析当前用户 query,生成意图向量
  2. 工具相关性打分:计算每个工具 Schema 和意图向量的余弦相似度
  3. Top-K 筛选:只保留 K 个最相关工具的完整 Schema,其余折叠为单行摘要
  4. 历史窗口压缩:对多轮对话历史做 sliding window + 摘要

这个方案的核心优势是精度损失极小——高相关的工具保留了全部语义信息。

实测数据

同一套 87 工具环境:

指标原生 MCPContext Gateway变化
Schema Token/轮31,2008,9003.5x ↓
端到端延迟2.8s2.1s1.3x ↓
工具选择准确率94.2%93.8%-0.4%
月成本$187$533.5x ↓

准确率只掉了 0.4%(对比 mcp2cli 的 2.5%),代价是压缩比没那么激进,而且多了中间件的调用延迟。

两种路线怎么选?

维度mcp2cliContext Gateway
压缩方式静态预编译动态运行时
压缩比4.9x3.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