MCP代码执行:构建高效Agent的新范式

如果你在构建 Agent 或处理上下文工程,这是一篇必读的博客。

Anthropic 虽然封锁国内厂商不允许使用他们的 Claude 模型,但不得不说,他们在 Agent 方面的实践都值得借鉴。

他们的工程团队发现,当 Agent 连接上千个工具时,传统的直接工具调用会导致上下文爆炸——光加载工具定义就可能需要数十万个 token。而解法也很简单:让 Agent 写代码调用工具。这么做可以把 token 消耗从 15 万降到 2 千,效率提升 98.7%。

可以说,这个解法不仅诞生了 Claude Code 的 Skills 功能,并且已经成为一种继 MCP 之后另一个 Agent 编程范式,Cloudflare 把其称为"Code Mode"。它不止解决了性能优化的问题,还顺带修复了 PII 自动脱敏、状态持久化、技能复用等问题。


模型上下文协议(MCP) 是一种连接 AI Agent 与外部系统的开放标准。传统上,Agent 需要为每个工具进行定制化集成,导致集成工作碎片化、重复且难以扩展,难以实现真正的系统互联。MCP 提供了统一的协议,开发者只需在 Agent 中实现一次 MCP,即可接入整个集成生态系统。

自 2024 年 11 月 MCP 推出以来,社区的采用速度非常快:已经构建了数千个 MCP 服务器,所有主流编程语言也都拥有了 SDK。业界已将 MCP 视为连接 Agent 与工具和数据的事实标准。

如今,开发者常常构建能够访问数十个 MCP 服务器、集成数百甚至上千个工具的 Agent。然而,随着连接的工具数量增加,预加载所有工具定义并通过上下文窗口传递中间结果,会导致 Agent 运行变慢并增加成本。

在本文中,我们将探讨代码执行如何帮助 Agent 更高效地与 MCP 服务器交互,从而在使用更少 token 的情况下处理更多工具。

工具过度消耗 token,导致 Agent 效率下降

随着 MCP 使用规模的扩大,有两种常见情况会导致 Agent 成本和延迟增加:

  1. 工具定义导致上下文窗口超载;

  2. 中间工具结果消耗了额外的 token。

1. 工具定义导致上下文窗口超载

大多数 MCP 客户端会将所有工具定义预先加载到上下文中,并通过直接的工具调用语法向模型暴露这些工具。这些工具定义可能如下所示:

gdrive.getDocument
     Description: Retrieves a document from Google Drive
     Parameters:
                documentId (required, string): The ID of the document to retrieve
                fields (optional, string): Specific fields to return
     Returns: Document object with title, body content, metadata, permissions, etc.
salesforce.updateRecord
    Description: Updates a record in Salesforce
    Parameters:
               objectType (required, string): Type of Salesforce object (Lead, Contact, Account, etc.)
               recordId (required, string): The ID of the record to update
               data (required, object): Fields to update with their new values
     Returns: Updated record object with confirmation

工具描述占用了更多的上下文窗口,导致响应时间和成本增加。当 Agent 连接数千个工具时,在读取请求前就可能需要处理数十万个 token。

2. 中间工具结果消耗了额外的 token

大多数 MCP 客户端允许模型直接调用 MCP 工具。例如,你可以让你的 Agent 执行以下操作:“从 Google Drive 下载我的会议记录,并将其附加到 Salesforce 线索中。”

模型将会发起如下调用:

TOOL CALL: gdrive.getDocument(documentId: "abc123")
        → returns "Discussed Q4 goals...\n[full transcript text]"
           (loaded into model context)

TOOL CALL: salesforce.updateRecord(
   objectType: "SalesMeeting",
   recordId: "00Q5f000001abcXYZ",
     data: { "Notes": "Discussed Q4 goals...\n[full transcript text written out]" }
  )
  (model needs to write entire transcript into context again)

每个中间结果都必须通过模型传递。在这个例子中,完整的通话记录需要传递两次。对于一次 2 小时的销售会议,这可能意味着需要额外处理 50,000 个 token。更大的文档甚至可能超出上下文窗口的限制,从而影响工作流程。

对于大型文档或复杂数据结构,模型在工具调用之间复制数据时,出错的概率也会增加。

MCP 客户端如何与 MCP 服务器和 LLM 协作的图像

MCP 客户端将工具定义加载到模型的上下文窗口中,并编排一个消息循环,其中每个工具调用和结果在操作之间通过模型传递。

使用 MCP 代码执行提升上下文处理效率

随着代码执行环境对 Agent 的应用越来越普遍,一种解决方案是将 MCP 服务器作为代码 API 提供,而不是直接调用工具。这样,Agent 可以通过编写代码与 MCP 服务器进行交互。这种方式解决了两个问题:一是 Agent 只需加载所需的工具,二是在将结果返回给模型之前,可以在执行环境中对数据进行处理。

实现这一目标有多种方法。其中一种方法是从已连接的 MCP 服务器生成所有可用工具的文件树。以下是使用 TypeScript 的实现示例:

servers
├── google-drive
│   ├── getDocument.ts
│   ├── ... (other tools)
│   └── index.ts
├── salesforce
│   ├── updateRecord.ts
│   ├── ... (other tools)
│   └── index.ts
└── ... (other servers)

然后每个工具对应一个文件,类似于:

// ./servers/google-drive/getDocument.ts
import { callMCPTool } from "../../../client.js";

interface GetDocumentInput {
  documentId: string;
}

interface GetDocumentResponse {
  content: string;
}

/* Read a document from Google Drive */
export async function getDocument(input: GetDocumentInput): Promise<GetDocumentResponse> {
  return callMCPTool<GetDocumentResponse>('google_drive__get_document', input);
}

我们前面提到的 Google Drive 到 Salesforce 的示例就变成了下面这样的代码:

// Read transcript from Google Docs and add to Salesforce prospect
import * as gdrive from './servers/google-drive';
import * as salesforce from './servers/salesforce';

const transcript = (await gdrive.getDocument({ documentId: 'abc123' })).content;
await salesforce.updateRecord({
  objectType: 'SalesMeeting',
  recordId: '00Q5f000001abcXYZ',
  data: { Notes: transcript }
});

Agent 通过探索文件系统来发现工具:它会列出 ./servers/ 目录,查找可用服务器(如 google-drivesalesforce),然后读取所需的工具文件(如 getDocument.tsupdateRecord.ts),以了解每个工具的接口。这样,Agent 只加载当前任务所需的定义,将 token 使用量从 150,000 降至 2,000,节省了 98.7% 的时间和成本。

Cloudflare 也有类似发现,将 MCP 的代码执行称为“代码模式”。他们的核心观点跟我们是一致的:LLM 擅长编写代码,开发者应利用这一优势,构建更高效与 MCP 服务器交互的 Agent。

MCP 代码执行的好处

MCP 的代码执行使 Agent 能够按需加载工具、在数据进入模型前进行过滤,并在单一步骤中执行复杂逻辑,从而更高效地利用上下文。这种方法还带来了安全性和状态管理方面的优势。

渐进式披露

大模型非常擅长导航文件系统。将工具以文件系统中的代码形式呈现,可以让模型按需读取工具定义,而无需预先加载所有定义。

另外,也可以为服务器添加 search_tools 工具,用于查找相关的工具定义。例如,在使用前述 Salesforce 服务器时,Agent 可以搜索“salesforce”,并仅加载当前任务所需的工具。在 search_tools 工具中加入详细级别参数,允许 Agent 根据需要选择信息的详细程度(如仅显示名称、名称和描述,或包含模式的完整定义),这有助于节省上下文空间并高效查找工具。

上下文高效的工具结果

在处理大型数据集时,Agent 可以在返回结果前,通过代码对结果进行筛选和转换。例如,处理一个包含 10,000 行的电子表格时:

// Without code execution - all rows flow through context
TOOL CALL: gdrive.getSheet(sheetId: 'abc123')
         returns 10,000 rows in context to filter manually

// With code execution - filter in the execution environment
const allRows = await gdrive.getSheet({ sheetId: 'abc123' });
const pendingOrders = allRows.filter(row =>
  row["Status"] === 'pending'
);
console.log(`Found ${pendingOrders.length} pending orders`);
console.log(pendingOrders.slice(0, 5)); // Only log first 5 for review

Agent 只看到 5 行数据,而不是 10,000 行。类似的模式也适用于聚合、跨多个数据源的连接或提取特定字段——这些操作都不会导致上下文窗口膨胀。

更强大且上下文高效的控制流

循环、条件和错误处理可以通过熟悉的编程模式实现,而无需单独调用每个工具。例如,如果你需要在 Slack 中发送部署通知,Agent 可以这样编写:

let found = false;
while (!found) {
  const messages = await slack.getChannelHistory({ channel: 'C123456' });
  found = messages.some(m => m.text.includes('deployment complete'));
  if (!found) await new Promise(r => setTimeout(r, 5000));
}
console.log('Deployment notification received');

这种方法比 Agent 在 MCP 工具调用和 sleep 命令之间循环切换更加高效。

此外,能够直接生成可执行的条件树,还能节省“第一个 token 的时间”延迟:与其让模型评估 if 语句,不如让代码执行环境来完成这一任务。

隐私保护操作

当 Agent 使用 MCP 代码执行任务时,中间结果默认保留在执行环境中。这样,Agent 只能访问你明确记录或返回的信息,这意味着你可以通过工作流程处理不希望与模型共享的数据,而这些数据不会进入模型的上下文。

对于更敏感的任务,Agent 控制器还可以自动对敏感数据进行 token 化。例如,假设你需要将客户联系信息从电子表格导入到 Salesforce。Agent 可以这样编写:

const sheet = await gdrive.getSheet({ sheetId: 'abc123' });
for (const row of sheet.rows) {
  await salesforce.updateRecord({
    objectType: 'Lead',
    recordId: row.salesforceId,
    data: {
      Email: row.email,
      Phone: row.phone,
      Name: row.name
    }
  });
}
console.log(`Updated ${sheet.rows.length} leads`);

MCP 客户端在数据到达模型前拦截并对 PII 进行脱敏处理。

// What the agent would see, if it logged the sheet.rows:
[
  { salesforceId: '00Q...', email: '[EMAIL_1]', phone: '[PHONE_1]', name: '[NAME_1]' },
  { salesforceId: '00Q...', email: '[EMAIL_2]', phone: '[PHONE_2]', name: '[NAME_2]' },
  ...
]

然后,当数据在另一个 MCP 工具调用中被共享时,会通过 MCP 客户端的查找功能进行去标记化处理。真实的电子邮件地址、电话号码和姓名会从 Google Sheets 流向 Salesforce,但不会经过模型,从而防止 Agent 意外记录或处理敏感数据。你还可以利用该功能定义确定性的安全规则,灵活控制数据的流向。

状态持久化和技能

具有文件系统访问权限的代码执行,使 Agent 能够在操作过程中维护状态。Agent 可以将中间结果写入文件,从而恢复工作并跟踪进度。

const leads = await salesforce.query({
  query: 'SELECT Id, Email FROM Lead LIMIT 1000'
});
const csvData = leads.map(l => `${l.Id},${l.Email}`).join('\n');
await fs.writeFile('./workspace/leads.csv', csvData);

// Later execution picks up where it left off
const saved = await fs.readFile('./workspace/leads.csv', 'utf-8');

Agent 还可以将自己的代码作为可重用函数进行持久化。一旦 Agent 为某个任务开发出可用的代码实现,就可以将其保存,方便今后重复使用。

// In ./skills/save-sheet-as-csv.ts
import * as gdrive from './servers/google-drive';
export async function saveSheetAsCsv(sheetId: string) {
  const data = await gdrive.getSheet({ sheetId });
  const csv = data.map(row => row.join(',')).join('\n');
  await fs.writeFile(`./workspace/sheet-${sheetId}.csv`, csv);
  return `./workspace/sheet-${sheetId}.csv`;
}

// Later, in any agent execution:
import { saveSheetAsCsv } from './skills/save-sheet-as-csv';
const csvPath = await saveSheetAsCsv('abc123');

这与 Skills 的概念密切相关。Skills 是可复用的指令、脚本和资源文件夹,模型可以利用它们提升专业任务的表现。将 SKILL.md 文件添加到这些已保存的函数中,可以为模型创建结构化的技能,便于引用和使用。随着时间推移,这将帮助你的 Agent 构建更强大的工具箱,逐步完善其高效工作的基础架构。

需要注意的是,代码执行本身会带来一定的复杂性。运行 Agent 生成的代码,需要在具备合适沙箱、资源限制和监控的安全环境中进行。这些基础设施的要求,会带来额外的操作开销和安全风险,而直接调用工具则可以避免这些问题。代码执行的优势——如降低 token 成本、减少延迟和更灵活的工具组合——需要与这些实施成本进行权衡。

总结

MCP 为 Agent 连接多种工具和系统提供了基础协议。然而,当连接的服务器数量过多时,工具定义和结果会占用大量 token,影响 Agent 的效率。

尽管上下文管理、工具组合和状态持久化等问题看似新颖,但在软件工程领域已有成熟的解决方案。代码执行将这些成熟模式应用于 Agent,使其能够通过熟悉的编程结构更高效地与 MCP 服务器交互。如果你采用了这种方法,欢迎将你的经验和发现分享给 MCP 社区

正文翻译自 Anthropic 官方博客,原文链接:https://www.anthropic.com/engineering/code-execution-with-mcp


欢迎长按下面的二维码关注 Feisky 公众号,了解更多云原生和 AI 知识。

comments powered by Disqus