通过 MCP 执行代码:构建更高效的智能体
直接工具调用会为每个定义和结果消耗上下文。让智能体编写代码来调用工具,扩展性会更好。下面介绍它如何与 MCP 配合工作。
来源:https://www.anthropic.com/engineering/code-execution-with-mcp
发布日期:2025-11-04
模型上下文协议(MCP)是一个开放标准,用于将 AI 智能体连接到外部系统。传统上,把智能体连接到工具和数据需要为每一组配对构建自定义集成,这会造成碎片化和重复劳动,使真正互联的系统难以扩展。MCP 提供了一个通用协议:开发者只需在自己的智能体中实现一次 MCP,就能解锁完整的集成生态。
自 2024 年 11 月发布 MCP 以来,采用速度一直很快:社区已经构建了数千个 MCP 服务器,所有主流编程语言都有可用的 SDK,行业也已将 MCP 作为连接智能体与工具、数据的事实标准。
如今,开发者经常构建能够访问数十个 MCP 服务器上数百甚至数千个工具的智能体。然而,随着连接工具数量增长,预先加载所有工具定义,并通过上下文窗口传递中间结果,会拖慢智能体速度并增加成本。
在这篇博客中,我们将探讨代码执行如何让智能体更高效地与 MCP 服务器交互,在使用更少 token 的同时处理更多工具。
工具导致的过度 token 消耗会降低智能体效率
随着 MCP 使用规模扩大,有两种常见模式会增加智能体的成本和延迟:
- 工具定义使上下文窗口过载;
- 中间工具结果消耗额外 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
复制
工具描述会占用更多上下文窗口空间,增加响应时间和成本。当智能体连接到数千个工具时,它们可能需要先处理数十万 token,才能读取用户请求。
2. 中间工具结果消耗额外 token
大多数 MCP 客户端允许模型直接调用 MCP 工具。例如,你可能会要求智能体:“从 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 执行代码可提升上下文效率
随着代码执行环境在智能体中越来越常见,一种解决方案是将 MCP 服务器呈现为代码 API,而不是直接工具调用。随后,智能体可以编写代码与 MCP 服务器交互。这种方法同时解决了两个挑战:智能体可以只加载所需工具,并在执行环境中处理数据,然后再把结果传回模型。
有很多方式可以做到这一点。一种方法是根据已连接 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 }
});
复制
智能体通过探索文件系统来发现工具:列出 ./servers/ 目录以找到可用服务器(如 google-drive 和 salesforce),然后读取它需要的具体工具文件(如 getDocument.ts 和 updateRecord.ts)来理解每个工具的接口。这让智能体只加载当前任务所需的定义。它把 token 使用量从 150,000 个 token 降低到 2,000 个 token,节省了 98.7% 的时间和成本**。**
Cloudflare 发布了类似发现,并将通过 MCP 执行代码称为 “Code Mode”。核心洞见相同:LLM 擅长编写代码,开发者应该利用这一优势,构建能更高效与 MCP 服务器交互的智能体。
通过 MCP 执行代码的好处
通过 MCP 执行代码,可以让智能体按需加载工具、在数据到达模型之前进行过滤,并在单个步骤中执行复杂逻辑,从而更高效地使用上下文。采用这种方法还会带来安全和状态管理方面的好处。
渐进式披露
模型非常擅长浏览文件系统。把工具以文件系统中的代码形式呈现给模型,可以让模型按需读取工具定义,而不是预先读取全部定义。
另一种做法是在服务器中添加 search_tools 工具,用于查找相关定义。例如,在使用上文假设的 Salesforce 服务器时,智能体会搜索 “salesforce”,并只加载当前任务所需的工具。在 search_tools 工具中加入一个细节级别参数,让智能体可以选择所需的细节级别(例如仅名称、名称和描述,或包含 schema 的完整定义),也有助于智能体节省上下文并高效查找工具。
上下文高效的工具结果
在处理大型数据集时,智能体可以先在代码中筛选和转换结果,再返回它们。考虑获取一个包含 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
复制
智能体看到的是五行,而不是 10,000 行。类似模式也适用于聚合、跨多个数据源的 join,或提取特定字段,所有这些都不会让上下文窗口膨胀。
更强大且上下文更高效的控制流
循环、条件判断和错误处理可以使用熟悉的代码模式来完成,而不是串联一个个单独的工具调用。例如,如果你需要 Slack 中的一条部署通知,智能体可以编写:
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');
复制
这种方法比通过智能体循环在 MCP 工具调用和 sleep 命令之间交替更高效。
此外,能够写出被执行的条件树,也可以节省首 token 延迟:智能体无需等待模型评估 if 语句,而是可以让代码执行环境完成这件事。
保护隐私的操作
当智能体结合 MCP 使用代码执行时,中间结果默认会留在执行环境中。这样,智能体只会看到你明确 log 或 return 的内容,也就是说,你不希望与模型共享的数据可以流经工作流,却永远不进入模型上下文。
对于更敏感的工作负载,智能体运行框架可以自动对敏感数据进行令牌化。例如,假设你需要把客户联系方式从电子表格导入 Salesforce。智能体会编写:
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,但绝不会经过模型。这可以防止智能体意外记录或处理敏感数据。你也可以利用这一点定义确定性的安全规则,选择数据可以流向何处、来自何处。
状态持久化与 skills
具备文件系统访问能力的代码执行,允许智能体跨操作维护状态。智能体可以把中间结果写入文件,从而恢复工作并跟踪进度:
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');
复制
智能体还可以把自己的代码持久化为可复用函数。一旦智能体为某个任务开发出可工作的代码,它就可以保存该实现,供未来使用:
// 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,供模型引用和使用。随着时间推移,这让你的智能体能够构建一个更高层能力的工具箱,并演化出最能支持其高效工作的脚手架。
请注意,代码执行也会引入自身的复杂性。运行智能体生成的代码需要一个安全的执行环境,配有适当的沙箱、资源限制和监控。这些基础设施要求会增加运维开销和安全考虑,而直接工具调用可以避免这些问题。代码执行带来的好处,包括降低 token 成本、减少延迟、改善工具组合能力,应当与这些实现成本一起权衡。
总结
MCP 为智能体连接众多工具和系统提供了基础协议。然而,一旦连接了过多服务器,工具定义和结果就可能消耗过多 token,降低智能体效率。
尽管这里的许多问题感觉很新,比如上下文管理、工具组合、状态持久化,但它们在软件工程中已有成熟解决方案。代码执行把这些既有模式应用到智能体上,让它们使用熟悉的编程结构,更高效地与 MCP 服务器交互。如果你实现了这种方法,我们鼓励你与 MCP 社区分享你的发现。
致谢
本文由 Adam Jones 和 Conor Kelly 撰写。感谢 Jeremy Fox、Jerome Swannack、Stuart Ritchie、Molly Vorwerck、Matt Samuels 和 Maggie Vo 对本文草稿提出反馈。