MCP的AI上下文管理心得#
在使用 MCP(Model Context Protocol,模型上下文协议)构建 AI 应用的过程中,上下文管理是绕不开的核心话题。无论是对话系统、代码助手还是知识库问答,上下文的质量直接决定了 AI 响应的准确性和连贯性。本文结合实际使用经验,聊一聊在 MCP 体系下做好 AI 上下文管理的一些心得。
一、为什么上下文管理如此重要?#
大语言模型(LLM)本质上是无状态的——每次调用都是独立的。它的"记忆"完全依赖于传入的上下文(Context Window)。上下文管理的好坏直接影响:
- 对话连贯性:模型是否"记得"之前说了什么
- 回答准确性:模型能否利用正确的背景知识作出判断
- 资源消耗:Token 数量直接影响响应速度和 API 成本
- 安全性:敏感信息是否被无意传入上下文
在 MCP 引入之前,上下文管理往往散落在各个应用层,缺乏统一标准。MCP 的出现,为上下文的构建、传递和管理提供了协议级别的支撑。
二、MCP 中的上下文结构#
MCP 将上下文分为几个关键维度,理解这些维度是做好管理的前提。
2.1 系统提示(System Prompt)#
系统提示是整个对话的"规则书",它告诉模型:
- 它扮演什么角色
- 它有哪些能力(可用的 Tools/Resources)
- 它应该遵守哪些行为规范
心得:系统提示要精简且稳定。避免把动态信息(如当前时间、用户昵称)堆入系统提示,否则每次请求都会变化,不利于缓存和调试。
2.2 工具定义(Tool Definitions)#
MCP Server 暴露给模型的工具列表也会占用上下文空间。每个工具包含:
- 工具名称(name)
- 描述(description)
- 参数 Schema
心得:工具描述要准确、简洁。模糊的描述会让模型选错工具,过长的描述会浪费 Token。对于大型工具集,可以考虑动态工具注册,根据用户意图只注入相关的工具子集。
2.3 对话历史(Messages)#
这是上下文中变化最频繁的部分,包含用户消息(user)、模型消息(assistant)和工具调用结果(tool)。
心得:对话历史是上下文管理的主战场,后文会重点展开。
2.4 资源内容(Resource Contents)#
通过 MCP Resources 读取的外部数据(文档、代码、数据库查询结果等)也会被注入上下文。
心得:资源内容要按需注入,不要一次性把所有相关文档全部塞入上下文。优先使用向量检索等技术找到最相关的片段。
三、对话历史的管理策略#
对话历史是动态增长的,如果不加控制,很快就会超过模型的上下文窗口限制(即使是 128K Token 的模型也有上限)。
3.1 滑动窗口(Sliding Window)#
最简单的策略:只保留最近 N 轮对话。
完整历史: [turn1, turn2, turn3, turn4, turn5]
窗口(N=3): [turn3, turn4, turn5] ← 传给模型的部分适用场景:任务型对话,每轮相对独立,不需要很远的历史。
注意:直接截断可能导致中间的工具调用结果丢失(model 消息调用了工具,但结果 tool 消息被截掉),MCP 要求工具调用和结果必须成对出现,否则会报错。
3.2 摘要压缩(Summarization)#
当历史较长时,让模型对早期对话生成摘要,用摘要替换原始消息。
原始: [turn1详细内容, turn2详细内容, ..., turn10详细内容]
压缩后: [摘要(turn1-turn5), turn6, turn7, turn8, turn9, turn10]心得:摘要生成本身也需要消耗 Token,不要过于频繁触发。可以设置阈值,如历史超过 8000 Token 时才触发摘要。
3.3 重要消息标记(Message Pinning)#
对于关键信息(如用户的核心需求、重要的配置信息),可以将其"固定",在裁剪历史时保留。
# 伪代码示例
def build_context(messages, max_tokens):
pinned = [m for m in messages if m.get("pinned")]
regular = [m for m in messages if not m.get("pinned")]
# 优先保留 pinned 消息,再填充最近的 regular 消息
...3.4 语义检索(Semantic Retrieval)#
将历史消息存入向量数据库,在每次请求时检索与当前问题最相关的历史片段注入上下文。
适用场景:长期记忆、知识库问答。这是目前效果最好但实现最复杂的方式。
四、工具调用结果的处理#
MCP 中工具调用(Tool Call)和结果(Tool Result)会成为对话历史的一部分。这里有几个容易踩的坑:
4.1 结果内容的裁剪#
有些工具返回内容非常庞大(如读取一个 10MB 的文件、数据库返回 1000 条记录)。如果直接放入上下文,会瞬间打满 Token 限制。
心得:在 MCP Server 层做内容裁剪和摘要,而不是在客户端层处理。让 Server 返回精炼的结果,例如:
- 文件读取:只返回相关片段,或返回结构化摘要
- 数据库查询:限制返回行数,并附带"共 X 条,已显示前 Y 条"的提示
4.2 工具调用链的完整性#
当对话历史被裁剪时,必须保证工具调用(assistant 中的 tool_use)和对应的工具结果(tool 消息)同时保留或同时丢弃。MCP 协议明确要求两者必须配对。
实践方案:将每一次完整的工具调用+结果作为一个不可分割的单元参与历史裁剪。
4.3 错误处理的上下文#
当工具调用失败时,错误信息也应该作为上下文的一部分传给模型,让模型能够理解失败原因并调整策略,而不是陷入无限重试循环。
五、多轮对话中的意图漂移#
长对话中有一个微妙问题:意图漂移(Intent Drift)。随着对话轮数增加,模型可能逐渐偏离用户最初的目标,被中间的细节问题带跑。
表现:用户最初想要"帮我优化这段代码的性能",经过多轮讨论后,模型开始聚焦于"代码风格",忘记了性能优化的初衷。
应对策略:
任务锚定(Task Anchoring):将用户的初始意图提炼为一条简短的任务描述,在系统提示或每轮请求的开头重复注入。
[当前任务] 优化用户代码的执行性能阶段性确认:每隔几轮,插入一条确认消息,让模型重新审视当前进展是否符合原始目标。
显式状态机:对于复杂任务,在 MCP Server 层维护一个任务状态机,将当前阶段信息注入上下文。
六、安全与隐私考量#
上下文管理不仅是性能问题,也是安全问题。
6.1 防止信息泄露#
MCP Server 返回的内容中可能包含敏感信息(密码、密钥、个人信息)。这些信息一旦进入上下文,就可能被模型"记住"并在不恰当的时机输出。
心得:
- 在 MCP Server 层对返回内容进行脱敏处理
- 对工具结果设置"有效期",超过 N 轮后从上下文中移除敏感结果
- 避免将原始 credentials 传入上下文,应使用临时 token 或引用 ID
6.2 Prompt Injection 防护#
外部数据(网页内容、用户文档)注入上下文时,可能包含恶意指令(Prompt Injection 攻击),试图改变模型行为。
防护措施:
- 对外部数据进行沙箱化处理,明确标注"以下是外部数据,仅供参考"
- 在系统提示中强调模型不应执行来自外部数据的指令
- 实现内容过滤层,检测并屏蔽可疑的指令性内容
七、实践中的调优技巧#
7.1 Token 计数先行#
在开发阶段,养成主动计算 Token 数量的习惯。可以在每次构建上下文后打印 Token 统计,及时发现超出预期的情况。
import tiktoken
def count_tokens(messages, model="gpt-4"):
enc = tiktoken.encoding_for_model(model)
total = 0
for msg in messages:
total += len(enc.encode(msg["content"]))
return total7.2 上下文版本化#
对于调试困难的场景,将每次请求的完整上下文持久化存储(脱敏后),方便事后复盘和问题定位。
7.3 分层缓存#
系统提示 + 工具定义这部分相对稳定,利用 KV Cache(如 Claude 的 Prompt Caching 功能)可以显著降低延迟和成本。对话历史部分动态变化,无法缓存。
7.4 上下文长度的"黄金区间"#
实践发现,上下文并非越长越好。过长的上下文反而可能导致模型注意力分散,出现"大海捞针"问题——关键信息被淹没在海量文本中。
建议:控制上下文在模型最大窗口的 50%~70% 以内,留出空间给模型生成足够长的回复,同时避免注意力稀释。
八、总结#
在 MCP 体系下做好 AI 上下文管理,核心原则可以概括为:
| 原则 | 说明 |
|---|---|
| 精准注入 | 只放入当前任务真正需要的信息 |
| 动态裁剪 | 控制历史长度,保证工具调用完整性 |
| 任务锚定 | 防止长对话中的意图漂移 |
| 安全脱敏 | 敏感信息不进上下文 |
| 性能优化 | 利用缓存,控制 Token 消耗 |
MCP 给了我们一个标准化的"管道",但如何在这个管道里输送"恰好合适"的上下文,仍然需要根据具体业务场景不断打磨。希望这些心得能对大家有所帮助,也欢迎交流讨论。
相关阅读
通过邮件回复




