使用 MCP 进行代码执行:构建更高效的 AI Agent
模型上下文协议(Model Context Protocol,MCP)是一项用于将 AI Agent 连接到外部系统的开放标准。
传统上,如果要把 Agent 连接到不同的工具和数据源,开发者往往需要为每一对组合单独编写集成。这种方式会造成碎片化和大量重复工作,也很难扩展到真正大规模、彼此连接的系统。
MCP 提供了一套统一协议:开发者只需要在自己的 Agent 中实现一次 MCP,就能够接入整个 MCP 集成生态。
自 2024 年 11 月发布 MCP 以来,它的采用速度非常快。社区已经构建了数千个 MCP Server,几乎所有主流编程语言都已经拥有 SDK,而整个行业也逐渐把 MCP 作为连接 Agent、工具和数据的事实标准。
如今,开发者经常会构建能够访问数百甚至数千个工具的 Agent,而这些工具分布在几十个 MCP Server 上。
但随着连接的工具数量增加,新的问题也出现了:
-
如果一开始就把所有工具定义全部加载进上下文,会拖慢 Agent,并增加成本;
-
如果所有中间工具结果都必须经过模型上下文,也会进一步增加 Token 消耗。
本文将讨论如何通过“代码执行”让 Agent 更高效地与 MCP Server 交互,从而在使用更少 Token 的同时处理更多工具。
工具带来的过度 Token 消耗会降低 Agent 效率
随着 MCP 使用规模扩大,有两种常见模式会明显增加 Agent 的成本和延迟:
-
工具定义占满上下文窗口;
-
中间工具结果额外消耗大量 Token。
1. 工具定义占满上下文窗口
大多数 MCP Client 会在一开始就把所有工具定义直接加载到上下文中,并通过直接工具调用语法暴露给模型。
这些工具定义可能类似下面这样:
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 Client 允许模型直接调用 MCP 工具。
例如,你可能对 Agent 说:
从 Google Drive 下载我的会议转录,然后把它附加到 Salesforce 的潜在客户记录中。
模型可能会执行类似这样的调用:
TOOL CALL:
gdrive.getDocument(documentId: "abc123")
→ returns
"Discussed Q4 goals...
[full transcript text]"
(loaded into model context)
TOOL CALL:
salesforce.updateRecord(
objectType: "SalesMeeting",
recordId: "00Q5f000001abcXYZ",
data: {
"Notes": "Discussed Q4 goals...
[full transcript text written out]"
}
)
(model needs to write entire transcript into context again)
每一个中间结果都必须经过模型。
在这个例子中,完整会议转录需要流经上下文两次。
如果这是一场 2 小时的销售会议,就可能额外产生 50,000 Token 的处理量。
对于更大的文档,内容甚至可能直接超过上下文窗口上限,从而让整个工作流失败。
而且,当中间内容是大型文档或复杂数据结构时,模型在不同工具调用之间复制数据时也更容易出错。
使用 MCP 进行代码执行可以提高上下文效率
随着 Agent 使用代码执行环境逐渐成为常见做法,一种解决方案是:
不要把 MCP Server 暴露成“直接工具调用”,而是把它们作为代码 API 提供给 Agent。
这样,Agent 可以通过编写代码来与 MCP Server 交互。
这种方式能够同时解决前面提到的两个问题:
-
Agent 只加载当前真正需要的工具;
-
Agent 可以先在执行环境中处理数据,再把必要结果返回给模型。
实现这一方案的方法有很多。
一种做法,是把所有已连接 MCP Server 中可用的工具生成为一棵文件树。
例如,可以使用 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
}
});
Agent 可以通过探索文件系统来发现工具。
例如,它先列出:
./servers/
从而看到有哪些可用 Server,例如:
google-drive
salesforce
然后只读取当前任务需要的工具文件,例如:
getDocument.ts
updateRecord.ts
这样,Agent 只会加载真正需要的工具定义。
在我们的示例中,这种方式把 Token 使用量从 150,000 降低到了 2,000,节省了 98.7% 的时间与成本。
Cloudflare 也发布了类似结果,并把这种使用 MCP 进行代码执行的方式称为“Code Mode”。
其核心洞察是一致的:
LLM 本身非常擅长编写代码,因此开发者应该充分利用这种能力,让 Agent 通过代码更加高效地与 MCP Server 交互。
使用 MCP 进行代码执行的优势
通过代码执行与 MCP 配合,Agent 可以:
-
按需加载工具;
-
在数据进入模型前进行过滤;
-
在一次代码执行中完成复杂逻辑;
-
更高效地使用上下文。
除此之外,这种方式在安全性和状态管理方面也存在额外优势。
渐进式披露
模型非常擅长浏览文件系统。
因此,如果把工具以代码文件的形式放在文件系统里,模型就可以按需读取工具定义,而不是一开始就把所有定义全部塞进上下文。
另一种方式,是在 Server 中添加一个 search_tools 工具,用来搜索相关工具定义。
例如,当 Agent 使用前面假设的 Salesforce Server 时,它可以先搜索:
salesforce
然后只加载当前任务所需要的工具。
如果 search_tools 还支持一个详细程度参数,那么 Agent 可以自行决定需要获取多少信息,例如:
-
只返回工具名称;
-
返回工具名称与描述;
-
返回包含 Schema 在内的完整定义。
这样也能够进一步减少上下文消耗。
更节省上下文的工具结果
当处理大型数据集时,Agent 可以先在代码环境中筛选和转换数据,然后只把必要结果返回模型。
假设需要读取一份包含 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
此时,Agent 只需要看到 5 行数据,而不是 10,000 行。
相同模式也可以用于:
-
聚合;
-
跨多个数据源进行 Join;
-
提取特定字段;
-
过滤大型结果。
这些操作都不需要把整个数据集塞入上下文窗口。
循环、条件判断和错误处理也可以直接通过熟悉的编程结构完成,而不必让 Agent 一个接一个地链式调用工具。
例如,如果你需要等待 Slack 中出现一条部署完成通知,Agent 可以写:
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 Loop 中反复执行:
-
MCP 工具调用;
-
Sleep;
-
再调用工具;
-
再 Sleep;
更加高效。
此外,如果能够直接把条件分支写进代码并交给执行环境运行,还可以减少“首 Token 延迟”。
因为模型不需要每次都亲自判断一个 if 条件,而是可以让代码执行环境直接完成。
隐私保护型操作
当 Agent 通过代码执行与 MCP 交互时,中间结果默认会停留在执行环境中。
只有被显式 log 或 return 的内容,才会进入模型上下文。
因此,那些你不希望模型看到的数据,也可以在整个工作流中流转,而无需进入模型。
对于更加敏感的工作负载,Agent Harness 还可以自动对敏感数据进行 Tokenization。
例如,假设你需要把电子表格里的客户联系信息导入 Salesforce。
Agent 可以写:
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 可以在数据真正到达模型之前拦截这些内容,并把个人身份信息替换为 Token。
例如,如果 Agent 打印了 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 可以通过本地映射把 Token 还原成真实值。
这样,真实的:
-
邮箱地址;
-
电话号码;
-
姓名;
可以从 Google Sheets 流向 Salesforce,但始终不会经过模型上下文。
这样可以防止 Agent 意外记录或处理敏感数据。
同样的机制还可以用于定义确定性的安全规则,精确控制数据允许从哪里流向哪里。
状态持久化与 Skills
如果代码执行环境允许访问文件系统,那么 Agent 还可以在不同操作之间持久化状态。
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'
);
Agent 还可以把自己写过的代码保存成可重复使用的函数。
当 Agent 为某个任务开发出一套可用代码之后,就可以保存下来,在后续任务中复用。
例如:
// 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`;
}
之后,任何一次 Agent 执行都可以直接复用:
import {
saveSheetAsCsv
} from './skills/save-sheet-as-csv';
const csvPath = await saveSheetAsCsv(
'abc123'
);
这与 Skills 的概念非常接近。
Skill 本质上是由可复用的:
-
指令;
-
脚本;
-
资源;
组成的文件夹,可以帮助模型在某个专业任务上获得更稳定的表现。
如果再为这些保存下来的函数添加一个 SKILL.md 文件,就可以把它们正式组织成结构化 Skill,让模型能够读取和使用。
随着时间推移,Agent 可以逐渐为自己构建出一套更高层级的工具箱。
也就是说,它不只是完成任务,还可以不断进化出一套最适合自己工作的脚手架。
需要注意的是,代码执行本身也会引入额外复杂度。
运行 Agent 生成的代码,需要一个安全的执行环境,并配套:
-
Sandbox;
-
资源限制;
-
监控机制。
这些基础设施需求会带来额外运维成本,也会增加安全考虑,而直接工具调用则不存在这些问题。
因此,在决定是否使用代码执行时,需要权衡它带来的收益与实现成本。
主要收益包括:
-
Token 成本更低;
-
延迟更低;
-
工具组合能力更强。
总结
MCP 为 Agent 连接大量工具和外部系统提供了基础协议。
但是,当连接的 Server 数量变得过多之后,工具定义和工具结果会消耗大量 Token,从而降低 Agent 效率。
本文讨论的一些问题看起来很新,例如:
-
上下文管理;
-
工具组合;
-
状态持久化。
但它们其实在传统软件工程中已经存在成熟的解决方案。
代码执行的意义,就是把这些已经验证过的软件工程模式应用到 Agent 系统中。
通过代码,Agent 可以使用熟悉的编程结构,更高效地与 MCP Server 交互。
如果你实现了这种方法,也可以把自己的经验分享给 MCP 社区。
- 点赞
- 收藏
- 关注作者
评论(0)