MCP使用新范式:通过生成代码调用工具,打造更高效Agents

大模型开发与运维数据库
目录
  • 引言
  • 什么是MCP
  • 传统MCP的不足
  • MCP的代码执行范式:让 agent 写代码
  • 新范式的优点
引言

最近anthropic公司写了一篇博客介绍如何解决 MCP 工具太多的问题,本质上也是通过上下文工程实现上下文管理,最终实现上下文的精简。

你有没有想过,当你的 AI agent 连接了上千个工具之后,它到底在干什么?

可能正在费力地读取几十万个 token 的工具定义,然后把一个 2 小时会议的转录文本在系统里传来传去——每传一次就要重新"吃"一遍。这就像让一个人背着一本电话黄页去办事,每次需要一个电话号码都要从头翻一遍。

问题的本质很简单 :传统的 MCP 使用方式太"笨"了。

先说说 MCP 是什么

Model Context Protocol 听起来很学术,但它解决的是个很实际的问题:怎么让 AI agent 连接外部系统?

以前你想让 agent 接入一个新工具,就得专门写一套集成代码。100 个工具就要写 100 次。这种碎片化让人抓狂。MCP 的思路是提供一个通用协议——就像 USB 接口一样,插上就能用。

从 2024 年 11 月发布到现在,社区已经造了几千个 MCP 服务器。但随之而来的问题是:当你的 agent 能调用上千个工具时,它反而变慢了。

为什么?

两个要命的浪费

第一个浪费:工具定义塞爆上下文

大多数 MCP 客户端的做法是:把所有工具定义一股脑塞进模型的上下文窗口。

想象一下,你要从 Google Drive 下载个文档,模型要先看一遍这样的定义:

  
gdrive.getDocument  
    描述:从 Google Drive 获取文档  
    参数:documentId(必填)、fields(可选)...  
    返回:包含标题、正文、元数据、权限的 Document 对象  

如果你想要把这些表单更新到Salesforce,则还需要Salesforce 的定义:

  
salesforce.updateRecord  
    描述:在 Salesforce 中更新记录  
    参数:objectType、recordId、data...  

工具多了之后,光是读取这些定义,模型可能就要先处理几十万 token。你还没开始干活,钱就已经花出去了。

第二个浪费:中间结果反复传递

更离谱的是数据传递的方式。

你让 agent "把 Google Drive 里的会议记录附加到 Salesforce 的销售线索上",它会这么做:

  1. 调用 gdrive.getDocument ,拿到一个 5 万字的转录文本
  2. 把这 5 万字加载到模型上下文
  3. 调用 salesforce.updateRecord再把这 5 万字写一遍

模型会发起如下2次的Tool调用:

  
TOOL CALL: gdrive.getDocument(documentId: "abc123")  
        → 返回 "Discussed Q4 goals...\n[full transcript text]"  
           (载入到模型上下文)  
  
TOOL CALL: salesforce.updateRecord(  
   objectType: "SalesMeeting",  
   recordId: "00Q5f000001abcXYZ",  
     data: { "Notes": "Discussed Q4 goals...\n[full transcript text written out]" }  
  )  
  (模型需要再次将整段转录文本写入上下文)  

同一份数据,在系统里传了两次。如果是个 2 小时的会议转录,你可能要为此多付 50,000 token 的钱。

更要命的是 :如果文档太大,可能直接超过上下文窗口限制,整个工作流就崩了。

原文见于:https://www.anthropic.com/engineering/code-execution-with-mcp

换个思路:让 agent 写代码

既然问题出在"所有东西都要过一遍模型",那解决方案也很直白:别让模型当传话筒,让它写代码来处理这些事。

这不是什么新鲜想法。LLM 本来就很会写代码,为什么不用呢?

具体怎么做?把 MCP 服务器呈现成代码 API,而不是工具列表。

你可以生成这样的文件结构:

  
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;  
}  
  
/* Read a document from Google Drive */  
exportasyncfunction getDocument(input: GetDocumentInput): Promise<GetDocumentResponse> {  
return callMCPTool<GetDocumentResponse>('google\_drive\_\_get\_document', input);  
}  
  

然后 agent 可以写这样的代码:

  
// Read transcript from Google Docs and add to Salesforce prospects  
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/ 目录,找到需要的服务器,再读取具体的工具文件。 按需加载,而不是一开始就把所有定义读一遍。

Token 使用量能从 150,000 降到 2,000——节省 98.7%

Cloudflare 也独立发现了这个思路,他们叫它 "Code Mode"。核心洞见是一样的:LLM 擅长写代码,那就让它多写代码。

这种方式到底好在哪?
  1. 按需加载工具

模型很会浏览文件系统。它可以先看看有哪些服务器可用,再根据任务需要去读具体的工具定义。

或者你可以提供一个 search\_tools 工具:agent 搜索 "salesforce",只加载相关的工具。你甚至可以让它选择需要多少细节——只要名称?还是要完整的 schema?

这就像去图书馆,你不需要把所有书都搬回家,只借你要看的那几本。

  1. 在代码里过滤数据

面对大数据集时,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)); // 只输出前 5 行  

模型只会看到 5 行,而不是 10,000 行。 聚合、连接、提取字段——这些操作都可以在代码里完成,不会膨胀上下文窗口。

  1. 更自然的控制流

循环、条件判断、错误处理——用代码写起来多自然:

  
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 循环里来回调用工具,这种方式效率高多了。而且能一次性执行完整的逻辑分支,降低"首 token 时延"——不用等模型逐条评估 if 语句。

  1. 保护隐私

中间结果默认留在执行环境里。只有你显式输出的内容才会被模型看到。

想象一下这个场景 :你要把客户联系信息从表格导入 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 客户端可以在数据到达模型之前对个人信息进行 token 化。真实的邮箱、电话、姓名从 Google Sheets 流向 Salesforce,但不会流经模型 。agent 看到的只是 [EMAIL\_1][PHONE\_1] 这样的占位符。

  1. 状态持久化和"技能"积累

agent 可以把中间结果写入文件,在之后恢复工作:

  
const leads = await salesforce.query({  
  query: 'SELECT Id, Email FROM Lead LIMIT 1000'  
});  
await fs.writeFile('./workspace/leads.csv', csvData);  
  
// 之后可以继续  
const saved = await fs.readFile('./workspace/leads.csv', 'utf-8');  

更有意思的是,agent 可以把自己写的代码保存成可复用函数

  
// ./skills/save-sheet-as-csv.ts  
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`;  
}  

这就是 Skills 的概念:随着时间推移,agent 逐步积累起一个工具箱,学会更高效地工作。

就像人类会总结经验、形成习惯一样。

但这不是免费的午餐

代码执行需要基础设施:安全的沙箱环境、资源限制、监控机制。这些都有运营成本和安全风险。

直接工具调用虽然低效,但实现起来更简单。

你需要权衡 :更低的 token 成本、更快的响应速度、更强的工具组合能力——值不值得这些基础设施投入?

对于连接几十上百个工具的 agent 来说,答案可能是肯定的。

最后说两句

MCP 提供了协议,但协议只是基础设施。怎么用它,决定了你的 agent 是聪明还是笨拙。

这里讨论的许多问题——上下文管理、工具组合、状态持久化——在软件工程里早就有成熟解法。代码执行只是把这些既有模式应用到 agent 上,让它用熟悉的编程方式更高效地工作。

说到底,LLM 本来就会写代码。让它多写点,没什么不好。

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动 XR 技术的探索与实践
火山引擎开发者社区技术大讲堂第二期邀请到了火山引擎 XR 技术负责人和火山引擎创作 CV 技术负责人,为大家分享字节跳动积累的前沿视觉技术及内外部的应用实践,揭秘现代炫酷的视觉效果背后的技术实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论