通过代码执行构建更高效的 MCP Agent¶
原文发布于 2025 年 11 月 4 日,作者 Adam Jones 和 Conor Kelly
引言¶
直接调用工具会让每个工具定义和结果都占用上下文,Agent 通过编写代码来调用工具可以更好地扩展。
MCP 是一个用于连接 AI Agent 与外部系统的开放标准。自 2024 年 11 月发布以来,社区已构建了数千个 MCP 服务器,各主流编程语言都有对应的 SDK。
然而,随着连接的工具越来越多,一个核心问题浮现:预先加载所有工具定义,并将中间结果传递到上下文窗口中,会拖慢 Agent 并增加成本。
工具的过度 Token 消耗让 Agent 效率下降¶
工具定义和中间结果是两个主要的 token 消耗来源,严重制约 Agent 的扩展能力。
问题一:工具定义挤占上下文窗口¶
大多数 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 的工具定义。
问题二:中间工具结果消耗额外 token¶
以"转移会议记录"为例,展示中间结果如何膨胀上下文:
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 服务器呈现为代码 API,让 Agent 编写代码来调用工具,而非直接进行工具调用。
一种实现方式是为所有可用工具生成文件树:
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 通过探索文件系统来发现可用工具。这将 token 消耗从 150,000 降低到 2,000——节省了 98.7% 的时间和成本。
Cloudflare 也发布了类似的发现,将这种方式称为"Code Mode"。
代码执行带来的五大优势¶
一、按需渐进式发现工具(Progressive Disclosure)¶
模型通过浏览文件系统按需读取工具定义,无需预先加载所有内容。
另一种替代实现是提供一个 search_tools 工具,可按不同详细程度查找相关定义:
| 详细程度 | 返回内容 |
|---|---|
| 仅名称 | 工具名称列表 |
| 名称 + 描述 | 工具名称及功能简介 |
| 完整定义 | 包含参数 schema 的完整定义 |
二、上下文高效的工具结果处理¶
Agent 在代码执行环境中过滤和转换结果,只将必要信息返回上下文。
// 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 行。
三、更强大且上下文高效的控制流¶
循环、条件判断和错误处理使用熟悉的代码模式,无需每一步都等待模型推理。
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');
这也降低了"首 token 延迟"(time to first token),因为代码执行环境在不等待模型评估的情况下处理条件判断。
四、隐私保护操作¶
中间结果默认留在执行环境中,敏感数据在系统间流转但不经过模型。
例如,批量导入客户信息时无需将 PII 暴露给模型:
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 进行脱敏处理(tokenization):
// 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]' },
...
]
真实数据在系统间流动,但绝不经过模型。开发者可以定义确定性的安全规则,决定数据可以从哪里流向哪里。
五、状态持久化与技能复用(State Persistence and Skills)¶
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');
技能复用示例:
// 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 文件可以创建结构化的技能供模型引用。
权衡取舍¶
代码执行带来了额外的工程复杂度,需要安全执行环境、资源限制和监控体系。
| 方面 | 直接工具调用 | 代码执行 |
|---|---|---|
| 实现复杂度 | 低 | 高(需要沙箱、资源限制、监控) |
| Token 效率 | 差(随工具数量线性增长) | 好(按需发现,中间结果不入上下文) |
| 隐私保护 | 弱(数据经过模型) | 强(数据留在执行环境) |
| 扩展性 | 差 | 好 |
| 状态管理 | 无 | 支持持久化和复用 |
应当权衡代码执行的收益与实现成本——并非所有场景都需要这种方案。
总结¶
MCP 提供了基础协议,但随着服务器规模增长,工具定义和结果会消耗过多 token。代码执行将软件工程中的成熟模式应用于 Agent,解决了上下文管理、工具组合和状态持久化问题。
核心问题都有软件工程领域的已知解决方案:
| 问题 | 代码执行的解决方式 |
|---|---|
| 上下文管理 | 按需发现工具,中间结果不入上下文 |
| 工具组合 | 代码中自由组合调用 |
| 状态持久化 | 文件系统存储中间状态 |
欢迎开发者将实践经验分享给 MCP 社区。
致谢¶
感谢 Jeremy Fox、Jerome Swannack、Stuart Ritchie、Molly Vorwerck、Matt Samuels 和 Maggie Vo 对本文的反馈。