MCP

AI Context Management 实战:用 MCP 管理 Agent 上下文窗口

深入探讨如何利用 Model Context Protocol 有效管理 AI Agent 的上下文窗口,避免信息丢失和上下文溢出问题。

上下文窗口是 LLM 最宝贵的资源。每个 Token 都有成本,每次对话都有上限。当 AI Agent 需要处理大量外部信息时,如何高效管理上下文窗口成为关键挑战。MCP 的设计为这个问题提供了优雅的解决方案。

上下文窗口的挑战

LLM 的上下文窗口虽然在不断扩大(Claude 支持 200K Token),但在实际 Agent 场景中仍然经常遇到限制:

信息膨胀——当 Agent 调用多个工具时,每个工具返回的结果都会占用上下文空间。一次数据库查询可能返回数千行数据,一个文件读取可能返回数万字内容。

噪音干扰——过多的上下文不仅浪费 Token,还会降低 LLM 的推理质量。研究表明,当上下文过长时,LLM 对中间信息的关注度会显著下降(“迷失在中间”问题)。

成本累积——在 API 调用中,输入 Token 的费用与数量成正比。不必要的上下文直接增加运营成本。

MCP 的上下文友好设计

MCP 的架构天然有利于上下文管理:

按需获取

MCP 的工具调用是按需进行的——LLM 只在需要时才调用工具获取信息,而不是预先加载所有数据。这避免了不必要的上下文占用。

传统方式:预加载所有数据 → 上下文爆炸
MCP 方式:按需调用工具 → 精准获取所需信息

结构化返回

MCP 工具返回结构化的内容,Client 可以对返回结果进行后处理——截断、摘要或过滤——再注入到上下文中。

Resource 缓存

MCP 的 Resource 支持订阅和缓存机制。当 Resource 内容变化时,Server 会通知 Client,Client 可以只保留最新的版本。

上下文管理策略

策略一:结果截断

对工具返回的结果进行长度限制:

server.tool('search', '搜索文档', {
  query: z.string(),
  maxResults: z.number().default(5),
}, async ({ query, maxResults }) => {
  const results = await doSearch(query);
  const limited = results.slice(0, maxResults);

  return {
    content: [{
      type: 'text',
      text: limited.map(r => `${r.title}: ${r.summary}`).join('\n'),
    }],
  };
});

策略二:渐进式获取

先返回摘要,LLM 需要时再获取详情:

// 第一步:返回摘要列表
server.tool('list_documents', '列出文档摘要', {
  directory: z.string(),
}, async ({ directory }) => {
  const docs = await listDocs(directory);
  return {
    content: [{
      type: 'text',
      text: docs.map(d => `[${d.id}] ${d.title} - ${d.preview}`).join('\n'),
    }],
  };
});

// 第二步:按需获取详情
server.tool('read_document', '读取指定文档的完整内容', {
  docId: z.string(),
}, async ({ docId }) => {
  const doc = await getDoc(docId);
  return {
    content: [{ type: 'text', text: doc.content }],
  };
});

策略三:上下文压缩

在 Client 端实现上下文压缩:

class ContextManager {
  private maxTokens: number;
  private messages: Message[] = [];

  constructor(maxTokens: number = 100000) {
    this.maxTokens = maxTokens;
  }

  addMessage(message: Message) {
    this.messages.push(message);
    this.trim();
  }

  private trim() {
    let totalTokens = this.estimateTokens();

    while (totalTokens > this.maxTokens && this.messages.length > 2) {
      // 移除最早的工具调用结果
      const idx = this.messages.findIndex(
        m => m.role === 'tool'
      );
      if (idx >= 0) {
        this.messages.splice(idx, 1);
      } else {
        break;
      }
      totalTokens = this.estimateTokens();
    }
  }

  private estimateTokens(): number {
    return this.messages.reduce(
      (sum, m) => sum + (m.content?.length || 0) / 4,
      0
    );
  }
}

策略四:智能摘要

当上下文接近限制时,用 LLM 对历史对话进行摘要:

async function summarizeContext(messages: Message[]): Promise<Message[]> {
  const claude = new Anthropic();

  const response = await claude.messages.create({
    model: 'claude-haiku-4-20250414',  // 使用小模型降低成本
    max_tokens: 1000,
    messages: [{
      role: 'user',
      content: `请将以下对话历史压缩为简明摘要,保留关键信息:\n\n${JSON.stringify(messages)}`
    }],
  });

  return [{
    role: 'user',
    content: `对话摘要:${response.content[0].text}`
  }];
}

MCP 工具设计中的上下文考量

返回精简数据

工具应该返回 LLM 真正需要的信息,而不是原始数据的全部:

// 差:返回所有字段
server.tool('get_user', {}, async ({ id }) => {
  const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  return { content: [{ type: 'text', text: JSON.stringify(user) }] };
});

// 好:只返回相关字段
server.tool('get_user', {}, async ({ id }) => {
  const user = await db.query(
    'SELECT name, email, role FROM users WHERE id = ?', [id]
  );
  return { content: [{ type: 'text', text: JSON.stringify(user) }] };
});

支持分页

对于可能返回大量数据的工具,实现分页机制:

server.tool('list_issues', '列出项目 Issues', {
  projectId: z.string(),
  page: z.number().default(1),
  pageSize: z.number().default(10),
}, async ({ projectId, page, pageSize }) => {
  const issues = await getIssues(projectId, page, pageSize);
  const total = await getIssueCount(projectId);

  return {
    content: [{
      type: 'text',
      text: JSON.stringify({
        issues,
        pagination: {
          page,
          pageSize,
          total,
          hasMore: page * pageSize < total,
        },
      }),
    }],
  };
});

常见问题(FAQ)

上下文窗口大小对 MCP 有影响吗?

有。上下文窗口越大,MCP 工具可以返回的数据越多。但在实际使用中,仍然建议控制返回数据量,因为过多信息会降低 LLM 推理质量。

如何监控上下文使用量?

在 Client 端统计每次请求的 Token 数量。Claude API 的响应中包含 usage 字段,显示输入和输出的 Token 数。

Resource 和 Tool 在上下文管理上有什么区别?

Resource 的内容可以被 Client 缓存和管理,适合静态或低频变化的数据。Tool 的结果是临时的,每次调用都会占用新的上下文空间。

总结

上下文管理是 MCP Agent 开发中的关键技能。通过按需获取、结果截断、渐进式加载和智能摘要等策略,你可以在有限的上下文窗口中最大化 Agent 的能力。

MCP 的架构设计为此提供了良好的基础——工具的按需调用、结构化返回和 Resource 缓存机制,都是为高效的上下文管理而生的。