你是否想过,为什么接入几十个工具的AI智能体,响应会越来越慢,成本还居高不下?这背后的“罪魁祸首”之一,正是上下文中堆积如山的工具描述与中间结果。本文基于Anthropic官方工程博客的深度解析,为你揭开如何用“代码执行”的思路,让智能体在与MCP服务器交互时,化繁为简,效率倍增。
在2026世界读书日:不荐书,只荐这一份「Agent智能体圣经」清单中,我们提到了一份博客清单,本篇即为其一。本文为中文读者提供一个了解Agent智能体系统底层设计逻辑的入口,帮助你更好地理解并使用诸如Claude Code这样的产品。英文能力强的同学,建议直接阅读原文以获取一手信息。
核心摘要:直接调用工具会为每个定义和结果消耗大量的上下文。通过编写代码来调用工具,智能体可以更好地扩展。以下是其工作原理。
模型上下文协议(Model Context Protocol,MCP) 是一项开放标准,旨在将AI智能体连接到外部系统。过去,为智能体接入工具和数据,需要为每一对组合进行定制开发,这导致了碎片化和大量重复劳动,使真正互联的系统难以扩展。
MCP提供了一个通用协议——开发者只需在智能体中实现一次MCP,即可解锁整个集成生态。自2024年11月推出以来,其采用速度迅猛:社区已构建了数千个MCP服务器,所有主流编程语言都有了相应的SDK,业界也已将MCP视为连接智能体与工具和数据的事实标准。
如今,开发者通常会构建能访问数十个MCP服务器上成百上千个工具的智能体。然而,随着工具数量增长,预先加载所有工具定义并在上下文窗口中传递中间结果的做法,会拖慢智能体速度并增加成本。在本文中,我们将探讨代码执行如何让智能体更高效地与MCP服务器交互,从而在使用更少token的同时处理更多工具。
工具导致的过量Token消耗
随着MCP使用规模的增长,有两种常见模式会增加智能体的成本和延迟:
- 工具定义使上下文窗口过载
- 中间工具结果消耗额外token
1. 工具定义使上下文窗口过载
大多数MCP客户端会预先将所有工具定义直接加载到上下文中,并使用直接工具调用语法暴露给模型。这些工具定义可能如下所示:
gdrive.getDocument
Description: 从 Google Drive 检索文档
Parameters:
documentId(必需,字符串):要检索的文档 ID
fields(可选,字符串):要返回的特定字段
Returns: 包含标题、正文内容、元数据、权限等的 Document 对象
salesforce.updateRecord
Description: 更新 Salesforce 中的记录
Parameters:
objectType(必需,字符串):Salesforce 对象类型(如 Lead、Contact、Account)
recordId(必需,字符串):要更新的记录 ID
data(必需,对象):要更新的字段及其新值
Returns: 包含确认信息的 Updated record 对象
工具描述占据了更多的上下文窗口空间,从而增加了响应时间和成本。当智能体连接到数千个工具时,它们在读取请求之前就需要处理数十万个token。
2. 中间工具结果消耗额外Token
大多数MCP客户端允许模型直接调用MCP工具。例如,你可能会要求智能体:“从Google Drive下载我的会议记录,并将其附加到Salesforce线索中。”模型将发起如下调用:
TOOL CALL: gdrive.getDocument(documentId: "abc123")
→ 返回 "讨论了第四季度目标...\n[完整转录文本]"(加载到模型上下文中)
TOOL CALL: salesforce.updateRecord(
objectType: "SalesMeeting",
recordId: "00Q5f000001abcXYZ",
data: { "Notes": "讨论了第四季度目标...\n[完整转录文本]" }
)
(模型需要再次将整个转录文本写入上下文)
每个中间结果都必须经过模型处理。在上面的例子中,完整的通话转录文本流经了两次。对于一场两小时的销售会议,这可能意味着需要额外处理50,000个token。更大的文档甚至可能超出上下文窗口限制,导致工作流中断。在处理大型文档或复杂数据结构时,模型在工具调用之间复制数据也更容易出错。
基于MCP的代码执行方案
随着代码执行环境在智能体中日益普及,一种解决方案是将MCP服务器呈现为代码API,而非直接工具调用。智能体可以编写代码来与MCP服务器交互。这种方法同时解决了上述两个挑战:智能体可以只加载所需的工具,并在将结果传回模型之前,在代码执行环境中处理数据。
实现方式多种多样。一种方法是生成一个包含所有已连接MCP服务器可用工具的文件树。以下是一个使用TypeScript的实现:
servers
├── google-drive
│ ├── getDocument.ts
│ ├── ...(其他工具)
│ └── index.ts
├── salesforce
│ ├── updateRecord.ts
│ ├── ...(其他工具)
│ └── index.ts
└── ...(其他服务器)
每个工具对应一个文件,大致如下:
// ./servers/google-drive/getDocument.ts
import { callMCPTool } from "../../../client.js";
interface GetDocumentInput {
documentId: string;
}
interface GetDocumentResponse {
content: string;
}
/* 从 Google Drive 读取文档 */
export async function getDocument(input: GetDocumentInput): Promise<GetDocumentResponse> {
return callMCPTool('google_drive__get_document', input);
}
前述的Google Drive到Salesforce示例就变成了以下代码:
// 从 Google Docs 读取转录文本,并添加到 Salesforce 潜在客户中
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个减少到2,000个——节省了98.7%的时间和成本。
Cloudflare也发表了类似的发现,将这种基于MCP的代码执行方式称为“代码模式”。其核心见解是一致的:LLM擅长编写代码,开发者应该利用这一优势来构建能更高效地与MCP服务器交互的智能体。
基于MCP的代码执行的收益
基于MCP的代码执行使智能体能够通过按需加载工具、在数据到达模型之前进行过滤以及单步执行复杂逻辑,从而更高效地使用上下文。此外,这种方法在安全性和状态管理方面也有优势。
渐进式信息披露
模型擅长浏览文件系统。将工具以代码形式呈现在文件系统上,使模型能够按需读取工具定义,而非一开始就全部加载。或者,也可以向服务器添加一个 search_tools 工具,用于查找相关定义。
例如,在使用上述假想的Salesforce服务器时,智能体可以搜索“salesforce”并仅加载当前任务所需的工具。在 search_tools 工具中加入一个详细程度参数,让智能体能够选择所需的详细级别(如仅名称、名称和描述,或包含模式的完整定义),也有助于智能体节省上下文并高效地查找工具。
上下文高效的工具结果
在处理大型数据集时,智能体可以在返回结果之前用代码过滤和转换结果。考虑抓取一个包含10,000行的电子表格:
// 不使用代码执行——所有行都流经上下文
TOOL CALL: gdrive.getSheet(sheetId: 'abc123')
→ 返回 10,000 行进入上下文,再手动过滤
// 使用代码执行——在代码执行环境中进行过滤
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)); // 仅输出前 5 条以供查看
智能体只看到5行,而不是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工具调用和休眠命令更高效。此外,能够编写出被一次性执行的条件树,也能节省“首token延迟”:智能体无需等待模型评估if语句,而是让代码执行环境来处理。
保护隐私的操作
当智能体使用基于MCP的代码执行时,中间结果默认保留在代码执行环境中。这样,智能体只能看到你显式输出或返回的内容,这意味着你不希望与模型共享的数据可以在工作流中流转,而永远不会进入模型的上下文。
对于更敏感的工作负载,智能体框架可以自动对敏感数据进行标记化处理。例如,假设你需要将客户联系方式从电子表格导入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):
// 若智能体输出了 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,但永远不会流经模型。这可以防止智能体意外记录或处理敏感数据。你还可以利用这一点来定义确定性的安全规则,选择数据可以流入和流出的路径。
状态持久化与技能
带有文件系统访问能力的代码执行环境使智能体能够在多次操作之间维护状态。智能体可以将中间结果写入文件,从而能够恢复工作并跟踪进度:
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);
// 后续代码执行可以接着上次的地方继续
const saved = await fs.readFile('./workspace/leads.csv', 'utf-8');
智能体还可以将自己的代码持久化为可复用的函数。一旦智能体为某项任务开发出可运行的代码,就可以将该实现保存下来供将来使用:
// 位于 ./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`;
}
// 之后,在任何智能体代码执行中:
import { saveSheetAsCsv } from './skills/save-sheet-as-csv';
const csvPath = await saveSheetAsCsv('abc123');
这与“技能(Skills)”的概念密切相关——技能是包含可复用指令、脚本和资源的文件夹,供模型用来提高在特定任务上的表现。为这些保存的函数添加一个 SKILL.md 文件,就能创建一个模型可以引用和使用的结构化技能。
随着时间的推移,这将使你的智能体逐步构建出一个高级能力工具箱,不断演进其高效工作所需的脚手架。
需要注意的是,代码执行也带来了其自身的复杂性。运行智能体生成的代码需要一个安全的执行环境,并配备适当的沙箱、资源限制和监控措施。这些基础设施要求增加了运维开销和安全考量,而直接工具调用则不存在这些问题。代码执行的收益——降低的token成本、更低的延迟和更好的工具组合能力——需要与这些实施成本进行权衡。
总结
MCP为智能体连接众多工具和系统提供了一个基础协议。然而,一旦连接了过多的服务器,工具定义和结果可能消耗过多token,从而降低智能体效率。
尽管这里面临的许多问题——上下文管理、工具组合、状态持久化——看起来新颖,但它们其实都有来自软件工程领域的已知解决方案。代码执行将这些成熟的模式应用到智能体上,让它们能够使用熟悉的编程结构,更高效地与MCP服务器交互。如果你实现了这种方法,我们鼓励你在MCP社区中分享你的成果。
致谢
本文由Adam Jones和Conor Kelly撰写。感谢Jeremy Fox、Jerome Swannack、Stuart Ritchie、Molly Vorwerck、Matt Samuels和Maggie Vo对本文草稿的反馈。
扩展阅读: