缓存时间:
2026/05/08 09:33
# 通过 MCP 执行代码:构建更高效的 AI Agent
来源:https://www.anthropic.com/engineering/code-execution-with-mcp
Model Context Protocol(MCP)(https://modelcontextprotocol.io/)是一项用于连接 AI Agent 与外部系统的开放标准。传统上,将 Agent 与工具和数据连接需要为每一对组合定制集成,这造成了碎片化并重复劳动,使得真正可扩展的连接系统难以实现。MCP 提供了一种通用协议——开发者在 Agent 中实现一次 MCP,即可解锁整个集成生态系统。自 2024 年 11 月推出 MCP 以来,采用速度非常迅猛:社区已构建数千个 MCP 服务器(https://github.com/modelcontextprotocol/servers),所有主流编程语言都有可用的 SDK(https://modelcontextprotocol.io/docs/sdk),并且业界已将 MCP 采纳为连接 Agent 与工具和数据的事实标准。
如今,开发者 routinely 构建能够访问数十个 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)
```
然后每个工具对应一个文件,例如:
```typescript
// ./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('google_drive__get_document', input);
}
```
我们上面从 Google Drive 到 Salesforce 的例子变成了代码:
```typescript
// 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-drive` 和 `salesforce`),然后读取所需的具体工具文件(如 `getDocument.ts` 和 `updateRecord.ts`)以了解每个工具的接口。这让 Agent 只加载当前任务所需的定义。这将 token 使用量从 150,000 个减少到 2,000 个——节省了 **98.7%** 的时间和成本。
Cloudflare 发表了类似的发现(https://blog.cloudflare.com/code-mode/),将使用 MCP 执行代码称为"Code Mode"。核心洞察是相同的:LLM 擅长编写代码,开发者应该利用这一优势来构建更高效地与 MCP 服务器交互的 Agent。
## **通过 MCP 执行代码的优势**
通过 MCP 执行代码使 Agent 能够更高效地使用上下文,按需加载工具,在数据到达模型之前进行过滤,并一步执行复杂逻辑。这种方法还有安全性和状态管理方面的好处。
### 渐进式披露
模型擅长浏览文件系统。将工具作为文件系统上的代码呈现,允许模型按需读取工具定义,而不是预先全部读取。或者,可以在服务器中添加 `search_tools` 工具来查找相关定义。
例如,当使用上面假设的 Salesforce 服务器时,Agent 搜索"salesforce"并只加载当前任务所需的那些工具。在 `search_tools` 工具中包含详细程度参数,允许 Agent 选择所需的详细级别(如仅名称、名称和描述,或包含 schema 的完整定义),也有助于 Agent 节省上下文并高效找到工具。
### 上下文高效的工具结果
处理大型数据集时,Agent 可以在返回结果之前用代码过滤和转换结果。考虑获取一个 10,000 行的电子表格:
```typescript
// Without code execution - all rows flow through context
TOOL CALL: gdrive.getSheet(sheetId: 'abc123')
→ returns 10,000 rows in context to filter manually
```
```typescript
// 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 看到的是五行而不是 10,000 行。类似的模式适用于聚合、跨多个数据源的连接,或提取特定字段——所有这些都不会膨胀上下文窗口。
#### **更强大且上下文高效的控制流**
循环、条件判断和错误处理可以用熟悉的代码模式完成,而不是链式调用单个工具。例如,如果你需要 Slack 中的部署通知,Agent 可以编写:
```typescript
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 工具调用和睡眠命令更高效。此外,能够编写出被执行的条件树也节省了"首 token 时间"延迟:不必等待模型评估 if 语句,Agent 可以让代码执行环境来完成。
### 保护隐私的操作
当 Agent 使用 MCP 执行代码时,中间结果默认保留在执行环境中。这样,Agent 只看到你明确记录或返回的内容,意味着你不希望与模型共享的数据可以在工作流中流动,而永远不会进入模型的上下文。
对于更敏感的工作负载,Agent 框架可以自动对敏感数据进行 tokenize。例如,假设你需要将电子表格中的客户联系信息导入 Salesforce。Agent 编写:
```typescript
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 进行 tokenize:
```typescript
// 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 客户端中的查找进行去 tokenize。真实的电子邮件地址、电话号码和姓名从 Google Sheets 流向 Salesforce,但永远不会经过模型。这防止了 Agent 意外记录或处理敏感数据。你还可以用它来定义确定性的安全规则,选择数据可以流向哪里。
### 状态持久化和技能
具有文件系统访问权限的代码执行允许 Agent 在操作之间保持状态。Agent 可以将中间结果写入文件,使其能够恢复工作和跟踪进度:
```typescript
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 为某个任务开发出可用的代码,它可以保存该实现以供将来使用:
```typescript
// 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(https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)的概念密切相关,即用于模型在专门任务上提高性能的可重用指令、脚本和资源文件夹。向这些保存的函数添加 SKILL.md 文件会创建一个结构化的技能,模型可以引用和使用。随着时间的推移,这允许你的 Agent 构建更高级能力的工具箱,进化其最有效地工作所需的脚手架。
请注意,代码执行引入了自身的复杂性。运行 Agent 生成的代码需要具有适当沙箱化(https://www.anthropic.com/engineering/claude-code-sandboxing)、资源限制和监控的安全执行环境。这些基础设施要求增加了直接工具调用所避免的操作开销和安全考虑。代码执行的优势——减少 token 成本、降低延迟和改进工具组合——应与这些实现成本相权衡。
## **总结**
MCP 为 Agent 连接许多工具和系统提供了基础协议。然而,一旦连接了太多服务器,工具定义和结果可能会消耗过多的 token,降低 Agent 效率。尽管这里的许多问题看起来是新的——上下文管理、工具组合、状态持久化——但它们有来自软件工程的已知解决方案。代码执行将这些已建立的模式应用于 Agent,让它们使用熟悉的编程结构更高效地与 MCP 服务器交互。
如果你实现了这种方法,我们鼓励你与 MCP 社区(https://modelcontextprotocol.io/community/communication)分享你的发现。
### 致谢
*本文由 Adam Jones 和 Conor Kelly 撰写。感谢 Jeremy Fox、Jerome Swannack、Stuart Ritchie、Molly Vorwerck、Matt Samuels 和 Maggie Vo 对本帖草稿的反馈。*