
先聊个几乎每个做过大模型应用的人都会撞上的事你辛辛苦苦调通了流程结果一查账单发现单轮对话轻松烧掉几万 Token多轮聊下来模型没怎么累钱包先瘦了一圈。前几天有个朋友跟我吐槽他的智能客服机器人一次请求的上下文里塞了近两万字的历史记录加搜索结果实际上模型真正需要用到的核心信息不到一千字剩下全是在“陪跑”。这其实不是个例而是当前大模型应用里最普遍也最容易被忽略的隐性浪费——高 Token 消耗。解决这个问题的关键不完全在 Prompt 怎么写而在你怎么管理和组织送进上下文窗口里的每一段内容。我这一年多实践下来最有效的一套思路就是基于 MCP 协议做上下文工程把“什么都往里塞”改成“需要什么才拿什么”成本能压下去一半甚至更多。这篇文章我就把自己踩过的坑、梳理出的方案和实际代码片段完整分享出来希望能帮正在被 Token 账单折磨的你打开一条新路。1. 高 Token 消耗到底消耗在哪——先搞清楚钱花在哪了很多人一看到 Token 消耗高第一反应是模型太贵或者 Prompt 写得太啰嗦。但真把一次请求拆开看你会发现大头往往不在系统提示词里而在上下文窗口被塞进了太多“看起来有用、实际没用”的内容。想省钱先得学会给上下文做体检。1.1 上下文窗口挤满了什么我接手过一个某电商平台的售后助手项目原始实现非常粗暴每次用户提问系统就把这个用户过去 30 天的全部订单记录、最近 20 轮对话、商品详情页的完整字段、售后政策全文一股脑塞给模型。看起来“信息齐全”实际上分析下来问题出在三类内容上。第一类是冗余历史。多轮对话里用户说过的“你好”“谢谢”这种寒暄模型根本不需要再读一遍。但要命的是很多应用把整段对话历史原封不动地拼进上下文轮次一多光历史就占了几千 Token。第二类是超长工具返回。比如调用订单查询接口时后端把订单的 ID、创建时间、支付渠道、收货地址、备注等几十个字段全部返回模型真正需要判断的可能只有“订单状态是否已发货”。第三类是重复指令。每轮请求都携带完整的系统提示词、角色设定、输出格式约束这部分虽然必要但往往写得过于冗长动不动一两千字里面大量描述是模型早就“知道”的常识。如果给这些内容做个归类你会发现高 Token 消耗的根源不是某一处特别夸张而是处处都在“顺手塞一点”积少成多。等单次请求涨到几万 Token你再想定位是哪一段撑爆的就得靠逐段计算了。网上有很多 Token 计算工具把每段文本单独算一遍你很快就能看出来真正有效的核心指令可能只占 5%剩下 95% 都是可优化空间。1.2 传统 RAG 和完整上下文检索的缺陷第一版优化方案很多人会想到做 RAG检索增强生成也就是先检索再拼接。但传统的 RAG 思路直接套进来还是会踩坑。常见做法是把用户问题拿去向量库召回 top-K 条文档然后整段整段拼进上下文。问题在于召回的是“整个文档块”不是“文档里恰好有用的那几句话”。一个文档块可能在讲十个知识点模型只关心其中两个剩余八个全成了上下文里的无效负载。更麻烦的是RAG 的召回结果通常不带“字段级筛选”。比如你明明只需要一个商品的库存状态检索系统却返回了包含标题、描述、价格、参数、评价、售后政策在内的整个商品文档白白多烧几百甚至上千 Token。如果检索链路再设计得粗糙一些比如同时召回 5 个文档块、每个块 2000 字单次请求光检索结果就吃掉上万 Token。还有一类情况是“上下文风暴”。也就是用户连续追问时每轮都把新的检索结果追加到旧历史后面旧的临时结论永远不会被清理。对话超过十轮之后前面几轮检索出来的过期信息仍然占着位置模型生成时还容易受这些过期信息的干扰既费钱又降低准确性。面对这种局面简单靠约束 Prompt 已经不管用了必须从架构层面重新设计“什么能进上下文、什么能出上下文”。2. MCP 协议在上下文工程中的角色定位聊 MCP 之前先说一个容易混淆的点MCP 不是一个“把内容塞给模型”的工具它是一个连接模型和外部数据/工具的标准协议。它的全称是 Model Context Protocol核心价值在于让模型应用用一套统一的方式去发现、调用、获取外部资源和工具。很多人第一次听到这个协议以为它是给 AI Agent 用的远程调用框架这没错但它和上下文工程的关系比表面看起来要深得多。2.1 MCP 到底解决什么问题在没有 MCP 之前你的应用要接一个数据库、一个搜索引擎、一个内部 API通常得为每个数据源写一套自定义接入层再在业务代码里手动拼上下文。每换一个数据源上下文拼接逻辑就要跟着改一遍。MCP 把这种“点对点对接”改成了“标准对接”模型应用作为 MCP 客户端数据源通过 MCP Server 暴露能力两边遵循统一协议通信。放到上下文工程的语境里MCP 解决的最大问题是“按需获取”的标准化。你可以把 MCP Server 想象成一个带菜单的餐厅服务窗口模型端想知道“这个订单能不能退款”就去调用订单查询工具工具返回的字段是后端在 MCP Server 里定义好的而不是整个数据表倒出来。前面说到的字段级筛选、结构化裁剪在 MCP 里天然就有实现载体。MCP 的工具定义里可以声明参数和返回结构模型调用时按需传参返回结果又可以按 schema 精简这就从协议层面逼着你做上下文的最小化获取。还有一个常被忽略的点MCP 的资源模型支持“分层暴露”。你可以把同一个数据源拆成多个资源比如“订单摘要”“订单详情”“订单物流轨迹”模型按当前任务只请求摘要层就不需要把详情层拉进来。这种资源粒度控制是普通 RAG 链路很难做到的但对 Token 消耗的削减立竿见影。2.2 为什么说它是上下文工程的“管道”上下文工程的核心是回答三个问题当前任务需要什么信息、这些信息从哪里拿、拿到之后怎么组织。MCP 协议正好覆盖了第二个问题的标准化同时给第三个问题提供了结构化基础。我自己习惯把 MCP 在上下文工程里的角色理解为“感知层管道”。感知层决定模型能看到哪一层业务现实而 MCP 是感知层和模型之间的统一通道。通过 MCP 工具暴露出来的数据天然是分束的——每次调用返回的是一束有结构的字段而不是一整块语义模糊的文本。模型拿到这束数据后可以直接嵌入上下文的指定位置形成“当前事实区”而不是把原始文档块堆在历史区里。这个“分束结构”对 Token 优化的贡献特别大。我实测过一个场景传统 RAG 方式召回一份十页商品文档拼进上下文大概 3000 Token改成 MCP 工具方式查询库存状态和物流状态返回 JSON 只有 200 Token 出头。效果一样成本差了一个数量级。更关键的是MCP 可以承载上下文工程里的“动态裁剪策略”比如在工具调用链路上加摘要层、缓存层、预算控制层这些我在下一个章节详细展开。3. 基于 MCP 的上下文工程实战方案纸上谈兵聊完了下面给一套可以落地到代码里的实践方案。这套方案我在两个不同场景的项目里验证过——一个是对接内部订单系统的售前咨询助手一个是知识库问答机器人。整体效果是 Token 单次请求平均下降 45% 到 60%响应延迟也明显降低因为模型每次需要阅读的内容变少了。3.1 架构设计与分层上下文整个方案的架构我称之为“三层上下文 MCP 通道”。三层上下文分别是核心指令层、任务工作集、参考知识区。核心指令层放系统提示词、角色设定、输出约束这部分每一轮都需要但会被压到最精简任务工作集放当前对话正在处理的业务事实例如订单状态、用户意图、临时计算结果这层数据通过 MCP 工具实时拉取用完即弃参考知识区放和当前任务强相关的知识库内容这部分必须有严格的 Token 预算上限。MCP 在这里的角色是任务工作层和参考知识层的统一获取通道。举个例子用户问“我上周买的东西到哪了”传统做法是把用户历史订单全查出来拼进去但按这套架构你的 MCP Server 会暴露一个get_order_status(order_id)工具模型先通过一个轻量级意图识别可以是一次小模型调用或者规则匹配拿到订单号再只调用这个工具返回结果是一个高度精简的 JSON{status:in_transit,estimated_delivery:2025-06-20}。这 100 个 Token 不到的返回结果就构成了任务工作集的核心内容。设计时有一个容易踩的坑想着把所有工具都做一个“精简返回版”结果每个工具定义一大堆参数模型反而要在“如何选择参数”上消耗思维 Token。我的经验是工具越聚焦越好宁可拆成多个单一职责的工具也不要做一个参数十几项的“瑞士军刀”。实测下来单一职责工具不仅上下文更小模型调用准确率也更高。3.2 动态上下文裁剪策略三层上下文分好之后接下来要处理的是“每一层放多少”。这里我采用预算分配制每次请求前先定一个总预算比如 4000 Token然后按比例分配到各层。我常用的一组比例是核心指令层不超过 15%任务工作集不超过 35%参考知识区不超过 30%剩下 20% 留给模型输出和缓冲。动态裁剪的核心是“按当前任务动态缩放”而不是固定死每个层的大小。这里有两个关键操作。第一个是历史摘要化。多轮对话超过六轮之后不再把原始历史全部拼进上下文而是每两轮生成一个摘要塞回历史区。这个摘要不用很复杂把“用户问过什么、系统答了什么结论、有没有遗留事项”压缩成两三句话即可。我一般让模型单独跑一次摘要生成虽然这本身也要花一点 Token但和保存完整历史相比长期看能省 70% 以上。第二个是参考知识区的“按需裁剪”。MCP 工具可以暴露一个search_knowledge(keywords, max_chars)接口内部做两件事先做向量检索召回候选文档再按关键词相关性把候选文档切成片段只返回最相关且总长度不超过max_chars的文本。这样模型拿到的不是一整块文档而是从若干文档里摘出来的高相关片段拼成的“压缩知识包”。为了防止片段切得支离破碎我会在切分时保留句子边界并且每段前面加一个来源标识方便模型知道这段信息出自哪里。3.3 缓存与预热的组合拳如果你以为裁剪完就到底了那还没完。真正把 Token 消耗打下来还要配合“缓存与预热”的组合拳。MCP 虽然本身不提供缓存能力但你可以把缓存逻辑嵌在 MCP Server 内部把高频查询的结果给缓存下来。比如前面那个get_order_status接口同一个订单短时间内被反复查询的概率不低第一次查完之后把返回结果缓存 60 秒后面直接复用既省了后端查询压力也省了模型反复调用工具的消耗。更实用的一个技巧叫“上下文预热”。针对那些用户一定会问的高频问题比如售后政策、退款流程可以在系统启动时就把这些问题的标准答案压缩好放在一个常驻的预热资源区。当用户问的问题命中预热区关键词时MCP Server 直接返回压缩版标准答案跳过整个检索链路。这个预热区的更新频率很低一周更新一次就够而每次命中都能省掉几百到几千 Token 的检索拼接开销。我还在项目里试过更深一层的做法把“用户会话状态”也做缓存。比如用户已经确认过收货地址后续对话里地址信息就不需要重复拉取直接从会话级缓存里读。这个功能用 MCP 的上下文原语实现时需要额外设计我的做法是在 MCP Server 里维护一个 session-scoped 缓存池工具调用时先查缓存命中就返回缓存的精简数据没命中再走完整查询。3.4 核心实现片段理论说再多不如直接看实现。下面我给一个精简但可运行的 MCP Server 示例展示如何通过工具返回结构化精简数据。环境假设是 Node.js使用官方 MCP SDK。先看服务端定义import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { z } from zod; const server new McpServer({ name: order-context-server, version: 1.0.0, }); // 模拟订单数据库 const orderDB { A1001: { status: in_transit, items: [{ name: 无线鼠标, qty: 1 }], address: 某市某区某街道, estimated_delivery: 2025-06-20, rawDetail: 这里是一大段详细订单描述正常场景可能有几百上千 Token 的内容... } }; server.registerTool( get_order_status, 获取订单物流状态和预计送达时间返回精简摘要, { orderId: z.string().describe(订单号) }, async ({ orderId }) { const order orderDB[orderId]; if (!order) { return { content: [{ type: text, text: JSON.stringify({ error: order_not_found }) }] }; } // 核心只返回模型真正需要的几个字段而不是整个订单对象 const slimPayload { orderId, status: order.status, estimated_delivery: order.estimated_delivery, }; return { content: [{ type: text, text: JSON.stringify(slimPayload) }], }; } );再看客户端接入侧核心是在请求上下文前预留一个“工具结果缓冲”。比如用 LangChain.js 或者直接手写时把工具返回的结果按 token 数检查超过阈值就只取 JSON 里的核心字段// 伪代码将MCP工具结果纳入上下文 const toolResult await mcpClient.callTool(get_order_status, { orderId }); const parsed JSON.parse(toolResult); // 对结果做token预算检查 const estimatedTokens estimateTokens(JSON.stringify(parsed)); if (estimatedTokens 300) { // 只保留关键字段其他裁剪 const { orderId, status, estimated_delivery } parsed; compactResult { orderId, status, estimated_delivery }; } const contextBlock 当前订单状态${compactResult.status}预计送达${compactResult.estimated_delivery};这套实现里真正起作用的不是哪一行代码特别高明而是“返回结果的结构”在高控制度。只要后端 MCP Server 坚持返回精简字段上游不管怎么调用天然就不会把整个订单详情倒进上下文。这也是为什么我说 MCP 会在架构层面帮你守住 Token 预算的底线。如果要接入知识库检索同样的思路把search_knowledge的返回实现为“只返回片段数组 来源标签”而不是直接返回整份文档效果立竿见影。4. 常见问题与排查技巧实录方案落地过程中我遇到过不少奇怪的问题有些问题不实测根本想象不到。这里挑几个典型的记录一下做成一目了然的排查表再分享一些在常规文档里翻不到的经验技巧。4.1 问题速查表现象可能原因解决方案调用 MCP 工具后上下文反而更大工具返回了整表数据而非精简字段检查 MCP Server 返回结构改成只暴露必要字段必要时新增精简版工具多轮对话后期 Token 持续飙升历史记录从未做摘要化超过 6 轮后启用历史摘要用摘要替换原始历史检索结果相关但模型答非所问知识片段切碎后丢失上下文因果切片段时保留句子边界每个片段添加来源标识和标题前缀工具调用准确率低工具参数过多、职责过重拆分成单一职责工具让模型少做选择缓存命中率低缓存粒度不匹配真实调用模式按订单号/会话ID等稳定维度缓存不要缓存一次性的随机参数查询模型总在反复调用同一工具返回结果里缺少模型判断所需的最终信息在工具返回里补充结论性字段例如把多步判断结果直接返回这张表基本覆盖了我踩过的大多数坑。真要排查我的建议是先记录一次完整请求的 Token 分布看哪一层占比最大再针对性优化顺序一般是“历史区 参考知识区 任务工作集 核心指令层”。4.2 避坑技巧与实操心得几个特别值得分享的实操心得一条一条说。第一不要一开始就追求“完美精简”。我有一次为了把某条返回压到极致把字段名都改成了单字母缩写比如st代表 status。结果模型对着st猜了半天反而在回复里给出了不准确的解读。后来我把字段名保持成有意义的短词比如status多花几个 Token 但准确率大幅提升。这属于典型的“省小钱亏大钱”精简要有度保留语义完整性是第一位的。第二MCP Server 的缓存一定要设失效时间。我之前图省事把订单状态缓存设成了 10 分钟结果用户催单时系统还返回上一轮查询的“已发货”客户直接炸了。后来统一改成“物流类 60 秒政策类 30 分钟地址类不缓存”的分类策略才平衡了成本和准确性。第三上下文工程的优化最好做成日志可观测。每次请求结束后把各层 Token 消耗、工具调用次数、模型回复长度记录到日志里。这个习惯前期看起来费事但等系统上线跑两天你一眼就能看出哪个环节在悄悄浪费。有个项目靠日志发现某工具被莫名调用了 7 次每次返回 250 Token相当于光这一个工具就吃掉 1750 Token最后排查发现是模型意图识别不准导致反复试探调用。由此可见成本问题本质上是一个可观测性问题看不见就优化不了。第四给摘要生成单独设置一个轻量模型。我在做历史摘要时没有用主模型而是接了一个更小、更便宜的摘要模型。虽然要额外调一次接口但摘要模型成本远低于主模型整体算下来仍然是省的。如果你用的是统一模型服务也可以在系统提示词里明确要求摘要要写成“不超过 50 字的要点式描述”实测比自由发挥式摘要省 Token 且更稳定。第五也是我觉得最重要的心得上下文工程的终点不是省 Token而是提升信息密度。同样 1000 Token 的上下文如果装的是高密度相关信息模型输出质量会比装着 5000 Token 的杂乱信息更好。所以每次你想往上下文里塞新内容之前先问一句这段信息对当前回复是“必要”还是“锦上添花”只有在必要信息都已放进来之后再考虑把省下来的预算拿去补充背景或示例。5. 这套方案还能怎么扩展前面讲的都是单请求层面的上下文优化但实际生产里高 Token 消耗往往不只是单次请求的问题更是“调用架构”的问题。基于 MCP 的上下文工程后续还能往几个方向延伸。一个是多 Agent 协作场景的上下文共享。多个 Agent 各跑各的上下文又互相传递结果传递过程中经常出现把整段思考过程都搬过去的情况。这种情况下可以基于 MCP 定义一个“结论接口”只允许传递结构化结论不允许传原始思考链。本质上和精简工具返回是同一套思路只是应用对象从工具变成了 Agent 间通信。另一个是面向长会话的上下文滚动压缩。当用户持续聊了很久比如客服场景里一次会话几十轮单靠摘要已经不够了。可以把会话按“意图段落”切割每个意图段生成一个“段落级摘要 关键事实卡片”后续新问题来了只加载与当前意图最相关的段落卡片。MCP 资源模型恰好能表达这种段落卡片——每个卡片是一个独立资源模型按需请求即可。还有一个是成本预算的自动控制。我设想把 Token 预算也做成一个 MCP 工具模型每次组织上下文前先查询一下剩余预算超预算就触发强制压缩。这个想法我还没在正式项目里跑过但结合前面说的日志可观测性完全可以通过服务端拦截器实现请求进入前检查预算不足时自动降级比如把参考知识区整个丢弃只保留任务工作集。说白了MCP 和上下文工程的搭配最大的想象空间在于“把上下文变成可度量的、可编程的资源”而不是越堆越厚的黑盒。说回最让我有成就感的一个改造案例之前那个电商售后助手改造成三层上下文加 MCP 精简工具之后单次请求 Token 从平均 6800 降到了 2600 左右月成本直接砍掉六成。而且因为模型每次读到的都是高密度相关信息客户回复的满意度反而提升了。技术方案的乐趣就在这里省钱的路上顺手把质量也做上去了。你在自己项目里动手改造的时候不用追求一步到位先从“哪个层最大先从哪砍”砍完看效果再决定要不要继续动其他层。只要别把上下文当成无限大的口袋随便塞高 Token 消耗这个问题其实是有解且可以稳定复现的解法。