ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MCP接入后Token消耗失控?抓包实测与六种压缩手段

MCP接入后Token消耗失控?抓包实测与六种压缩手段 1. 从一次账单异常说起MCP 接入后 token 消耗为何失控事情的起因很简单。我把 OpenClaw 接上了 MCPModel Context Protocol想让本地工具链和模型之间打通省去每次手动复制粘贴上下文的麻烦。配置跑通的那一刻确实很爽工具调用顺畅文件读写、命令执行、网页抓取都能自动完成。但用了不到一周我注意到一个很不对劲的现象明明每天对话轮次没增加多少token 用量却翻了好几倍。一开始我以为是模型本身的问题换了个更小的模型测试结果 token 消耗依然偏高。后来我把每一轮请求的原始 payload 抓出来逐条对比才发现问题根本不在模型而在 MCP 的工作机制本身。每一次对话MCP 都会把工具定义、工具描述、参数 schema 全部塞进上下文而这些内容在每一轮对话里都会重复发送。换句话说你以为只是问了一句话实际上背后附带了成百上千 token 的工具元数据。这个发现让我意识到很多人接 MCP 的时候只关注能不能跑通却忽略了跑通之后每轮对话的真实成本。这篇内容就是把我这段时间踩过的坑、抓包分析的过程、以及最终把 token 消耗压下来的具体做法完整梳理出来。如果你正在用 OpenClaw 接 MCP或者准备接那这些经验应该能帮你少走不少弯路。提示本文讨论的 token 消耗问题适用于所有基于 MCP 协议的客户端与工具集成场景不限于 OpenClaw 本身。2. MCP 到底往上下文里塞了什么2.1 工具定义是如何进入每一轮请求的要理解 token 为什么烧得快得先搞清楚 MCP 的通信结构。MCP 本质上是一套客户端与工具服务端之间的协议客户端启动时会向 MCP Server 请求可用工具列表拿到一份包含工具名称、功能描述、参数定义的清单。这份清单不会只存在本地而是会被注入到模型的系统提示或工具调用区域让模型知道现在有哪些工具可以用、每个工具接受什么参数。关键在于这份工具清单是随每一轮对话请求一起发送的。模型本身是无状态的它不记得上一轮你告诉过它什么工具所以客户端必须在每次请求时重新把工具定义带上。一个工具的描述加上参数 schema少则几十 token多则两三百 token。如果你接了五六个 MCP Server每个 Server 暴露十几个工具那光是工具定义就能轻松突破三千 token。我实测过一组数据接入一个包含 12 个工具的 MCP Server工具定义部分稳定占用约 1800 token。这意味着哪怕你只发一个你好实际请求也是 1800 token 起步。对话轮次越多这部分固定开销被重复计费的次数就越多。2.2 工具返回值与中间结果的累积效应除了工具定义第二个 token 黑洞是工具返回值。MCP 工具执行完会把结果返回给模型模型再基于结果继续推理。问题在于很多工具返回的内容非常冗长——比如读取一个文件返回全文、执行命令返回完整日志、抓网页返回整页 HTML。这些内容一旦进入对话历史就会在后续每一轮里被反复携带。我遇到过一个典型场景让 OpenClaw 读取一个 500 行的配置文件工具返回了完整内容大约 4000 token。之后我又追问了几个问题每一轮对话都带着这 4000 token 的历史。问了五轮光这一个文件就被重复计费了五次累计两万 token。而实际上后面几轮根本不需要完整文件内容只需要其中几个关键字段。2.3 系统提示与工具描述的叠加第三个容易被忽略的点是系统提示。OpenClaw 本身会带一段系统提示MCP 接入后又叠加了工具使用说明、调用规范、错误处理指引。这些内容同样是每轮必带。有些 MCP Server 的描述写得极其啰嗦一个工具的功能说明能写五六行参数描述逐条展开这些都会实打实变成 token。把这三部分加起来你就明白为什么每次对话都在偷偷烧 token了。工具定义是固定税工具返回值是浮动税系统提示是隐形税三者叠加token 消耗自然失控。3. 抓包实测一轮对话的真实 token 构成3.1 抓取原始请求的方法光靠猜没用得拿到真实数据。我的做法是在 OpenClaw 和模型 API 之间加一层日志代理把每次请求的完整 body 记录下来。具体操作是找到 OpenClaw 的模型配置项把 base URL 指向本地一个转发服务转发服务负责记录请求再原样转发出去。这样既不改变功能又能拿到最原始的 payload。记录下来的请求体是 JSON 格式里面 messages 数组包含系统提示、历史对话、当前输入tools 数组包含所有 MCP 工具定义。我用一个简单的脚本统计各部分字符数再按经验比例换算成 token英文约 4 字符 1 token中文约 1.5 字符 1 token。3.2 各部分占比的真实数据下面是我接入两个 MCP Server一个文件操作类一个命令执行类共 18 个工具后一轮简单对话的 token 构成实测组成部分估算 token占比系统提示6209%MCP 工具定义18 个264038%历史对话185027%工具返回结果142020%当前用户输入3806%合计6910100%这组数据很说明问题。用户实际输入只占 6%而工具定义加工具返回结果占了 58%。也就是说你花的钱里超过一半是在为 MCP 的元数据和中间结果买单真正用于对话的部分反而是小头。3.3 多轮对话下的放大效应单轮看可能还不觉得夸张但多轮对话会把这个效应放大。假设每轮固定开销系统提示工具定义是 3260 token历史对话每轮增长 500 token那么第 10 轮对话的请求量大约是 3260 500×10 8260 token。而如果这 10 轮里每轮都触发一次工具调用工具返回结果再叠加进去轻松破万。我统计过一整天的使用情况实际有效对话内容约 8000 token但总消耗接近 12 万 token放大倍数达到 15 倍。这个数字才是偷偷烧 token的真相。4. 把 token 压下来的六种实操手段4.1 精简工具集只挂当前任务需要的 Server最直接有效的一招是不要把所有 MCP Server 一直挂着。很多人图省事配置一次就全开结果每次对话都带着一堆根本用不到的工具定义。我的做法是按任务场景分组比如写代码时只挂文件操作和命令执行查资料时只挂网页抓取需要哪个开哪个。具体到 OpenClaw 的配置可以在启动参数或配置文件里控制启用哪些 MCP Server。如果你用的是支持动态加载的客户端甚至可以做到对话中途按需加载。实测下来把 18 个工具精简到 6 个工具定义部分从 2640 token 降到约 880 token单轮省下近 1800 token。4.2 压缩工具描述改写 schema 里的冗余文字MCP 工具的描述文字是可以改的。很多 Server 自带的描述写得非常啰嗦什么此工具用于在指定路径下执行文件读取操作支持多种编码格式返回文件完整内容……其实完全可以压缩成读取文件参数path。模型理解工具用途并不需要那么多修饰词。我把自己常用的几个 MCP Server 的工具描述全部重写了一遍保留功能说明和参数定义删掉所有客套话和重复解释。18 个工具的描述从平均 145 token 压到 55 token整体省下约 1600 token。这个改动是一次性的但收益是每轮对话都享受。注意改写工具描述时要保证参数名和类型准确否则模型可能调用失败。功能说明可以精简但参数 schema 不能动。4.3 截断工具返回值别把整个文件塞进上下文工具返回结果是浮动开销的大头必须控制。我的做法是在 MCP Server 侧或客户端侧加一层结果处理逻辑文件读取只返回前 N 行加总行数命令执行只返回最后 N 行加退出码网页抓取只返回正文文本去掉 HTML 标签。以文件读取为例我设置默认只返回前 100 行如果模型需要更多再让它显式请求指定行范围。这样一次读取从 4000 token 降到 400 token 左右。命令执行同理日志动辄几千行只保留尾部关键部分就够了。这一招对 token 的压缩效果最明显因为工具返回值往往是单次请求里最大的块。4.4 控制历史长度滑动窗口与摘要结合历史对话不能无限增长。我采用的是滑动窗口加摘要的策略保留最近 6 轮完整对话更早的内容用一段简短摘要替代。摘要是让模型自己生成的比如之前讨论了配置文件读取和命令执行问题已解决路径错误。这样既保留了上下文连贯性又不会让历史无限膨胀。OpenClaw 如果支持自定义上下文管理可以直接配置窗口大小。如果不支持可以在客户端侧做预处理把超出窗口的历史替换成摘要再发送。实测把历史从 20 轮压到 6 轮加摘要历史部分 token 从 8000 降到 2500 左右。4.5 关闭不必要的系统提示叠加系统提示部分也有压缩空间。检查一下 OpenClaw 的默认系统提示和 MCP 注入的说明是否有重复。有些客户端会把工具使用规范写两遍一遍在系统提示里一遍在工具定义区。删掉重复部分系统提示能从 620 token 降到 350 token 左右。另外如果你的使用场景比较固定可以把系统提示里那些通用助手式的客套描述删掉只保留必要的角色设定和输出格式要求。模型不需要你告诉它你是一个乐于助人的助手才能正常工作。4.6 用缓存机制避免重复计费部分模型 API 支持 prompt caching对重复出现的系统提示和工具定义部分可以命中缓存按更低的费率计费。如果你的模型服务支持这个特性务必开启。MCP 的工具定义恰好是高度重复的内容非常适合缓存。开启缓存后我实测工具定义部分的实际计费降到原来的 10% 左右。这一招不需要改任何业务逻辑只是配置层面的调整性价比极高。具体开启方式取决于你用的模型服务一般在请求头或请求体里加一个缓存标记即可。5. 配置层面的具体操作与参数5.1 OpenClaw 的 MCP 配置结构OpenClaw 的 MCP 配置通常放在一个 JSON 或 YAML 文件里结构大致如下{ mcpServers: { filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, /path/to/dir], enabled: true }, shell: { command: npx, args: [-y, modelcontextprotocol/server-shell], enabled: false } } }关键在enabled字段。我建议默认全部设为 false需要时再手动开启对应的 Server。这样启动时不会把所有工具定义都加载进来。5.2 工具描述改写的落点工具描述一般由 MCP Server 提供客户端只是转发。要改写有两个位置一是直接改 Server 源码里的工具注册部分二是客户端侧加一层拦截在发送请求前替换 tools 数组里的 description 字段。前者一劳永逸但需要维护 fork后者灵活但每次请求都要处理。我选的是客户端侧拦截因为改动可控且不影响 Server 升级。拦截逻辑很简单遍历 tools 数组对每个工具的 description 做字符串替换或直接覆盖成精简版。参数 schema 保持原样不动。5.3 返回值截断的实现返回值截断最好在 MCP Server 侧做因为客户端拿到结果时已经晚了。如果 Server 是开源的直接改它的返回逻辑。如果改不了就在客户端收到工具结果后、塞进对话历史前做截断。以文件读取为例客户端侧的处理逻辑def truncate_tool_result(result, max_lines100): lines result.split(\n) if len(lines) max_lines: return result head lines[:max_lines] return \n.join(head) f\n... (共 {len(lines)} 行已截断)这段逻辑放在工具结果进入 messages 之前执行即可。命令执行结果同理保留尾部而不是头部因为错误信息通常在最后。5.4 历史窗口的配置参数如果 OpenClaw 支持上下文窗口配置找到类似max_history_turns或context_window的参数设成 6 到 8 之间比较合适。太小会丢失上下文太大则 token 浪费。我实测 6 轮是个平衡点大部分任务在 6 轮内能完成超出部分用摘要衔接。摘要的生成可以复用模型本身在历史被截断时触发一次轻量调用让模型把被截断的部分总结成两三句话。这次调用的成本远低于保留完整历史。6. 那些文档里不会写的踩坑经验6.1 工具定义重复加载的隐蔽问题有一次我发现 token 消耗突然翻倍排查半天才发现是 MCP Server 被重复注册了。配置里同一个 Server 写了两遍客户端加载时把工具定义也加载了两遍模型看到的是两套一模一样的工具。这种问题不会报错功能也正常但 token 白白翻倍。建议定期检查配置确保没有重复项。6.2 工具调用失败后的重试放大MCP 工具调用失败时有些客户端会自动重试每次重试都把完整的工具定义和失败结果再发一遍。如果失败原因是参数错误重试三次就是三倍的 token 消耗。我的做法是关闭自动重试或者把重试次数限制在 1 次失败后直接把错误返回给模型让它自己调整参数。6.3 流式输出下的 token 统计偏差用流式输出时很多客户端的 token 统计是不准的因为它只统计了最终输出没算上工具定义和中间过程。我建议不要依赖客户端自带的统计而是以模型服务商后台的账单为准。我一开始就是被客户端的统计误导了以为消耗不高直到看账单才发现问题。6.4 不同模型对工具定义的 token 计算差异同一个工具定义在不同模型上的 token 数是不一样的。中文描述在中文优化模型上更省英文描述在英文模型上更省。如果你的工具描述是中英混杂的建议统一成一种语言并且和模型的主要语言一致。我实测把工具描述从中文改成英文后在英文模型上省了约 15% 的 token。6.5 缓存命中的条件与陷阱prompt caching 不是无条件命中的。它要求前缀部分完全一致包括系统提示、工具定义的顺序和内容。如果你每次请求的工具顺序不一样或者描述里有动态内容比如时间戳缓存就命中不了。我踩过的坑是在工具描述里加了当前日期导致缓存全部失效。后来把动态内容移到用户消息里缓存命中率才恢复正常。7. 一套可复用的 token 控制清单把上面的经验整理成一份可执行的清单每次接入新的 MCP Server 时对照检查工具集按需加载默认关闭不常用的 Server工具描述精简到功能加参数删掉所有修饰性文字工具返回值做截断文件读头部、日志读尾部、网页去标签历史对话用滑动窗口加摘要窗口控制在 6 到 8 轮检查系统提示是否有重复删掉冗余部分开启 prompt caching确保前缀内容稳定不变关闭或限制工具调用自动重试定期检查配置避免 Server 重复注册工具描述语言与模型主语言保持一致以服务商账单为准统计消耗不依赖客户端显示这份清单我贴在配置目录旁边每次改动配置都过一遍。坚持下来我的 token 消耗从最初的每天 12 万降到了 3 万左右功能没有任何损失。8. 关于成本与体验的取舍把 token 压下来之后我也重新思考了一个问题MCP 带来的便利和它带来的成本到底怎么平衡。我的结论是MCP 的价值在于自动化但自动化不等于无脑全开。真正高效的做法是让工具在需要的时候出现用完就收起来而不是一直挂在上下文里。我现在的工作流是日常对话不挂任何 MCP需要操作文件或执行命令时再临时开启对应的 Server任务完成后关闭。这样既享受了 MCP 的便利又不会为闲置的工具定义持续付费。听起来多了一步操作但省下来的成本和时间远比那一步操作值。另外工具返回值的截断策略也需要根据任务调整。有些任务确实需要完整文件内容这时候截断反而会导致模型反复请求总消耗更高。我的经验是对于探索性任务用截断对于精确编辑任务给完整内容根据任务类型切换策略而不是一刀切。最后分享一个小技巧如果你不确定某个 MCP Server 值不值得挂先单独挂它跑一天记录 token 消耗和实际使用次数。如果使用次数很少但消耗很高那它就不值得常驻。这个简单的评估方法帮我砍掉了好几个看起来有用但实际很少用的 Server。
返回列表