通过 MCP 的代码执行:构建更高效的代理¶
- 原文链接:https://www.anthropic.com/engineering/code-execution-with-mcp [59]
- 发布时间:2025-11-04
- 作者:Adam Jones、Conor Kelly
直接工具调用会把定义与中间结果都塞进上下文;用代码执行来调工具可以大幅节省 token。[59]
Model Context Protocol (MCP) 是连接 AI 代理与外部系统的开放标准。传统方式往往需要为每个工具做定制集成,导致重复与碎片化。MCP 通过统一协议解决这一问题:只需实现一次,即可接入整个生态。[59]
自 2024 年 11 月发布以来,MCP 发展迅速:社区已构建数千个 MCP servers,SDK 覆盖主流语言,行业也逐渐将 MCP 视为连接工具与数据的事实标准。[59]
如今开发者常构建拥有数百、数千工具的代理系统,但随着工具量增长,把所有工具定义与中间结果都塞进上下文会显著降低效率、增加成本。[59]
本文将展示:如何通过“代码执行”让代理更高效地与 MCP 交互,在工具更多的情况下使用更少 token。[59]
工具导致的过度 token 消耗会降低效率¶
随着 MCP 规模化,两种模式会显著提高成本与延迟:[59]
- 工具定义占满上下文窗口
- 工具中间结果消耗额外 token
1. 工具定义占满上下文¶
多数 MCP client 会在开始时加载所有工具定义,并通过“直接工具调用”暴露给模型。例如:[59]
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。[59]
2. 中间结果消耗额外 token¶
多数 MCP client 允许模型直接调用工具。例如:“从 Google Drive 下载会议记录并附加到 Salesforce 线索”。模型会像这样调用:[59]
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;更大的文档甚至会超出上下文窗口,流程直接失败。[59]
在处理大型文档或复杂数据结构时,模型在复制数据时也更容易出错。[59]

图注:MCP client 把工具定义加载进上下文,并在工具调用与结果之间让模型反复中转消息。[59]
通过 MCP 的代码执行提升上下文效率¶
随着代码执行环境变得普及,把 MCP server 作为 代码 API 而不是直接工具调用会更高效。代理可以写代码调用 MCP 工具,只加载必要工具,并在执行环境中处理数据,再把必要结果返回模型。[59]
一种做法是为所有 MCP 工具生成文件树。例如:[59]
servers
├── google-drive
│ ├── getDocument.ts
│ ├── ... (other tools)
│ └── index.ts
├── salesforce
│ ├── updateRecord.ts
│ ├── ... (other tools)
│ └── index.ts
└── ... (other servers)
每个工具对应一个文件,如:[59]
// ./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 的例子可以写成:[59]
// 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/ 找到 server,再读取对应文件理解接口。这样只加载当前任务需要的定义,token 使用可从 150,000 降到 2,000,节省 98.7%。[59]
Cloudflare 也发表了类似结论,将其称为 “Code Mode”。核心观点一致:LLM 擅长写代码,开发者应利用这一点,让代理更高效地与 MCP 交互。[59]
MCP 代码执行的优势¶
代码执行让代理按需加载工具、在进入模型前过滤数据、用一次代码完成复杂逻辑,同时带来安全与状态管理优势。[59]
渐进式披露(Progressive disclosure)¶
模型擅长浏览文件系统。把工具以代码文件呈现,可以按需读取定义,而不是一次性加载。[59]
也可提供 search_tools 工具查找定义。例如搜索 “salesforce”,只加载相关工具。若 search_tools 支持“细节级别”(仅名称/名称+描述/完整定义),还可进一步节省上下文并提升效率。[59]
上下文更高效的工具结果¶
面对大数据,代理可以先在执行环境中过滤/变换,再返回必要结果。比如拉取 1 万行表格:[59]
// 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
这样模型只看到 5 行而不是 10,000 行。对聚合、跨源 join、字段抽取等也同理,避免膨胀上下文。[59]
更强且更省上下文的控制流¶
循环、条件与错误处理可以直接用代码完成,而不是让模型在工具调用之间来回切换。例如监听 Slack 部署通知:[59]
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,这种方式更高效。[59]
同时,能直接执行条件树,也能降低“首 token 延迟”:不必等模型判断 if,直接交给执行环境。[59]
隐私保护操作¶
使用 MCP 代码执行时,中间结果默认留在执行环境中,只有显式 log/return 的内容才会进入模型。这让你可以让敏感数据“流经系统但不进上下文”。[59]
对更敏感的任务,可在 agent harness 中自动做脱敏 tokenization。例如将客户联系信息从表格导入 Salesforce:[59]
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 进行 tokenization:[59]
// 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 反向去 tokenization。真实的邮箱/手机号/姓名从 Google Sheets 到 Salesforce 的流转过程不经过模型,避免误记录或泄露。[59]
状态持久化与 Skills¶
代码执行 + 文件系统访问让代理跨操作维护状态,例如把中间结果写入文件并继续: [59]
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');
代理还能把自己的代码保存成可复用函数: [59]
// 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 的概念紧密相关:通过 SKILL.md 把可复用脚本与说明封装成技能,让模型形成高阶能力工具箱。[59]
需要注意的是,代码执行本身也引入复杂度:必须提供安全的执行环境、沙箱、资源限制与监控。这些运维与安全成本是直接工具调用所不需要的。应权衡其 token 成本、延迟与组合能力的收益。[59]
总结¶
MCP 提供了连接工具与系统的基础协议,但当 server 过多时,工具定义与结果会消耗大量 token,降低效率。[59]
虽然这类问题看起来“新”,但上下文管理、工具组合、状态持久化等在软件工程中已有成熟方案。代码执行把这些成熟模式引入代理,让它们用熟悉的编程构造高效地与 MCP 交互。我们鼓励你实践后与 MCP 社区 分享经验。[59]
致谢¶
本文由 Adam Jones 与 Conor Kelly 撰写,感谢 Jeremy Fox、Jerome Swannack、Stuart Ritchie、Molly Vorwerck、Matt Samuels 与 Maggie Vo 对草稿的反馈。[59]