一声棒喝,本不立文字
偏要著録,已是二义

Anthropic · 工程博客

使用 MCP 执行代码:构建更高效的 AI agents

Code execution with MCP: building more efficient AI agents

二〇二六年五月八日 · 英文原文

本文介绍 MCP 作为连接 AI agent 与外部工具、数据的开放 protocol,并说明自 2024 年 11 月发布后已有数千个 MCP server 和多语言 SDK。文章提出用 code execution 将 MCP server 呈现为 code API,按需加载工具、在 execution environment 处理中间数据,可将示例 token 从 150,000 降至 2,000,并讨论隐私、状态持久化、Skills 与 sandboxing 要求。

是一个用于将 AI agent 连接到外部系统的开放标准。传统上,将 agent 连接到工具和数据需要为每一组配对进行自定义集成,这会造成碎片化和重复劳动,使真正互联的系统难以扩展。MCP 提供了一种通用 protocol——开发者只需在自己的 agent 中实现一次 MCP,就能接入整个集成生态。

自 2024 年 11 月 MCP 发布以来,其采用速度很快:社区已经构建了数千个 MCP serverSDK 已覆盖所有主流编程语言,行业也已将 MCP 采纳为连接 agent 与工具、数据的事实标准。

如今,开发者经常构建可访问数百甚至数千个工具、跨数十个 MCP server 的 agent。然而,随着连接的工具数量增加,预先加载所有工具定义并通过 context window 传递中间结果,会拖慢 agent 并增加成本。

在本文中,我们将探讨 code execution 如何让 agent 更高效地与 MCP server 交互,在使用更少 token 的同时处理更多工具。

工具带来的过量 token 消耗会降低 agent 效率

随着 MCP 使用规模扩大,有两种常见模式会增加 agent 的成本和延迟:

  1. 工具定义使 context window 过载;
  2. 中间工具结果消耗额外 token。

1. 工具定义使 context window 过载

大多数 MCP client 会预先将所有工具定义直接加载到 context 中,并使用直接 tool-calling 语法将它们暴露给模型。这些工具定义可能如下所示:

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

工具描述会占用更多 context window 空间,增加响应时间和成本。当 agent 连接到数千个工具时,它们可能需要在读取请求之前先处理数十万 token。

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

大多数 MCP client 允许模型直接调用 MCP 工具。例如,你可能会要求 agent:“从 Google Drive 下载我的会议转录文本,并将其附加到 Salesforce lead。”

模型会发起类似这样的调用:

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。更大的文档甚至可能超过 context window 限制,导致工作流中断。

对于大型文档或复杂数据结构,模型在工具调用之间复制数据时也更容易出错。

图 1:MCP client 如何与 MCP server 和 LLM 协作的示意图。

MCP client 将工具定义加载到模型的 context window 中,并编排一个消息循环,使每次工具调用及其结果都在操作之间经过模型。

结合 MCP 的 code execution 可提升 context 效率

随着 code execution 环境在 agent 中变得越来越常见,一种解决方案是将 MCP server 呈现为 code API,而不是直接的工具调用。随后,agent 可以编写代码来与 MCP server 交互。这种方式同时解决了两个挑战:agent 可以只加载所需工具,并在 execution environment 中处理数据,然后再将结果传回模型。

实现方式有很多。一种做法是根据已连接的 MCP server 生成包含所有可用工具的文件树。下面是一个使用 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/ 目录以找到可用 server(如 google-drivesalesforce),然后读取它需要的具体工具文件(如 getDocument.tsupdateRecord.ts),以理解每个工具的接口。这样,agent 只需为当前任务加载所需定义。这会将 token 使用量从 150,000 个 token 降至 2,000 个 token——节省 98.7% 的时间和成本**。**

Cloudflare 发布了类似发现,并将结合 MCP 的 code execution 称为 “Code Mode”。核心观点相同:LLM 擅长编写代码,开发者应利用这一优势来构建能更高效地与 MCP server 交互的 agent。

结合 MCP 的 code execution 的优势

结合 MCP 的 code execution 让 agent 能够按需加载工具、在数据到达模型前进行过滤,并在单个步骤中执行复杂逻辑,从而更高效地使用 context。采用这种方式还带来安全和状态管理方面的优势。

Progressive disclosure

模型很擅长浏览文件系统。将工具以文件系统上的代码形式呈现,允许模型按需读取工具定义,而不是一开始就读取全部定义。

另一种做法是在 server 中添加一个 search_tools 工具,用于查找相关定义。例如,在使用上面假设的 Salesforce server 时,agent 搜索 “salesforce”,并只加载当前任务所需的工具。还可以在 search_tools 工具中加入 detail level 参数,使 agent 能选择所需的详细程度(例如仅名称、名称和描述,或包含 schema 的完整定义),这也有助于 agent 节省 context 并高效查找工具。

具备 context 效率的工具结果

处理大型数据集时,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 行。类似模式也适用于聚合、跨多个数据源的 join,或提取特定字段——这一切都不会使 context window 膨胀。

更强大且更具 context 效率的控制流

循环、条件判断和错误处理可以用熟悉的代码模式完成,而不是串联一个个工具调用。例如,如果你需要在 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 loop 在 MCP 工具调用和 sleep 命令之间来回切换更高效。

此外,能够写出一个会被执行的条件树,也能减少 “time to first token” 延迟:agent 不必等待模型评估 if 语句,而是可以让 code execution environment 来完成。

保护隐私的操作

当 agent 将 code execution 与 MCP 结合使用时,中间结果默认留在 execution environment 中。这样,agent 只会看到你明确 log 或 return 的内容,这意味着你不希望与模型共享的数据可以在工作流中流转,而从不进入模型的 context。

对于更敏感的工作负载,agent harness 可以自动 tokenize 敏感数据。例如,假设你需要将电子表格中的客户联系方式导入 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 client 会拦截数据,并在 PII 到达模型之前对其进行 tokenize:

// 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 client 中的查找表将其 untokenize。真实的电子邮件地址、电话号码和姓名会从 Google Sheets 流向 Salesforce,但不会经过模型。这可以防止 agent 意外记录或处理敏感数据。你也可以借此定义确定性的安全规则,选择数据可以流向哪里、从哪里流出。

状态持久化和 Skills

具备文件系统访问能力的 code execution 允许 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 文件,就能创建一个结构化 skill,供模型引用和使用。随着时间推移,这让你的 agent 能够构建一个更高层次能力的工具箱,并演进其最高效工作所需的 scaffolding。

需要注意的是,code execution 也会引入自身的复杂性。运行 agent 生成的代码需要安全的 execution environment,并配备适当的 sandboxing、资源限制和监控。这些基础设施要求会带来额外的运维开销和安全考量,而直接工具调用可以避免这些问题。code execution 的收益——降低 token 成本、降低延迟、改善工具组合能力——应与这些实现成本一起权衡。

总结

MCP 为 agent 连接到大量工具和系统提供了基础 protocol。然而,一旦连接的 server 过多,工具定义和结果就可能消耗过多 token,降低 agent 效率。

尽管这里的许多问题看起来很新——context 管理、工具组合、状态持久化——但它们在软件工程中已有成熟解法。code execution 将这些既有模式应用到 agent,让它们使用熟悉的编程结构,更高效地与 MCP server 交互。如果你实现了这种方式,我们鼓励你将发现分享给 MCP 社区

致谢

本文由 Adam Jones 和 Conor Kelly 撰写。感谢 Jeremy Fox、Jerome Swannack、Stuart Ritchie、Molly Vorwerck、Matt Samuels 和 Maggie Vo 对本文草稿提供反馈。

译自 Anthropic · 工程博客 · 录于 二〇二六年五月八日