使用MCP执行代码:构建更高效的AI Agent | Anthropic


核心速览

AI Agent 判断需要使用哪些工具 → 编写对应的代码(调用这些工具)→ 将代码交给 MCP 提供的代码运行沙箱执行 → 在沙箱执行过程中,代码会调用 MCP 中定义好的函数/接口(这些函数背后真正连接并调用外部工具)→ 最终,AI Agent 只需要传递代码本身,并接收代码执行后返回的最终结果,而不需要关心工具细节、中间数据或加载大量上下文。

AI Agent
    │
    ├─ 1. 分析任务 → 判断需要调用哪些工具(如 Google Drive、Salesforce)
    │
    ├─ 2. 编写代码(如 import 工具函数、调用 getDocument、updateRecord)
    │
    ├─ 3. 将代码交给 MCP 提供的「代码执行沙箱」
    │
    ├─ 4. 沙箱执行代码:
    │       └─ 调用 MCP 客户端提供的“工具函数”(如 getDocument)
    │       └─ MCP 客户端通过 MCP 协议 调用真正的 MCP 服务器(如 google-drive)
    │       └─ MCP 服务器与真实外部系统交互(如 Google Drive API)
    │
    └─ 5. 沙箱返回最终结果给 AI Agent(如文档内容、更新状态等)

The Model Context Protocol (MCP) 是一种用于连接AI Agent与外部系统的开放标准。传统上,将Agent与工具、数据连接起来需要为每一对组合进行定制集成,这导致了碎片化和重复工作,使得真正可扩展的连接系统难以实现。MCP提供了一个通用协议——开发者只需在Agent中实现一次MCP,就能解锁一整个生态系统的集成。

自2024年11月推出以来,MCP的采用速度非常快:社区已经构建了数千个MCP服务器,所有主要编程语言都有可用的SDK,行业也采纳MCP作为连接Agent与工具和数据的实际标准。如今,开发者通常会构建能够访问数十个MCP服务器上数百或数千种工具的Agent。

然而,随着连接的工具数量增加,预先加载所有工具定义并通过上下文窗口传递中间结果会使Agent变慢并增加成本。在本文中,我们将探讨如何通过代码执行使Agent能够更高效地与MCP服务器交互,在使用更少token的同时处理更多工具。工具产生的过多token消耗会降低Agent效率。

随着MCP使用规模的扩大,有两种常见模式会增加Agent的成本和延迟:

  1. 工具定义过载上下文窗口;
  2. 中间工具结果消耗额外token。

工具过度消耗token会降低AI Agent效率

1. 工具定义挤占上下文窗口

大多数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。

2. 工具过程数据消耗额外token

大多数MCP客户端允许模型直接调用MCP工具。例如,你可能会要求你的Agent:“从Google Drive下载我的会议记录并将其附加到Salesforce潜在客户。”模型将进行如下调用,在下述例子中,完整的通话记录通过模型两次且每次都会占用其上下文窗口。

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执行代码提高上下文效率

随着代码执行环境在Agent中变得越来越普遍,一种解决方案是将MCP服务器呈现为代码API,而不是直接的工具调用。然后,Agent可以编写代码与MCP服务器交互。这种方法完美的解决了工具定义挤占上下文窗口和工具执行过程数据庞大的问题。

有多种方法可以实现这一点。一种方法是生成一个包含所有可用工具的文件树,这些工具来自连接的MCP服务器。以下是使用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);
}

Agent可以生成以下代码,然后交给MPC代码执行沙箱来完成期望工作:

// 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/目录以找到可用的服务器(如google-drive和salesforce),然后读取它需要的特定工具文件(如getDocument.ts和updateRecord.ts)以了解每个工具的接口。这使Agent能够仅加载当前任务所需的定义。

这将token使用量从150,000个token减少到2,000个token——节省了98.7%的时间和成本。Cloudflare发布了类似的研究结果,将使用MCP的代码执行称为“代码模式”。核心见解是相同的:大语言模型擅长编写代码,开发者应该利用这一优势来构建能够更高效地与MCP服务器交互的Agent。

使用MCP执行代码的优势

使用MCP执行代码使Agent能够通过按需加载工具、在数据到达模型之前过滤数据以及在单一步骤中执行复杂逻辑来更高效地使用上下文。使用这种方法还有安全和状态管理方面的好处。

渐进式披露

模型擅长导航文件系统。将工具作为文件系统上的代码呈现,允许模型按需读取工具定义,而不是预先读取所有定义。或者,可以向服务器添加一个search_tools工具来查找相关定义。可参考:Equipping agents for the real world with Agent Skills

例如,在使用上面假设的Salesforce服务器时,Agent搜索“salesforce”并仅加载当前任务所需的那些工具。在search_tools工具中包含一个详细级别参数,允许Agent选择所需的详细程度(如仅名称、名称和描述,或带有模式的完整定义)也有助于Agent节省上下文并高效地找到工具。

上下文高效工具结果

在处理大型数据集时,Agent可以在代码中过滤和转换结果,然后再返回它们。考虑获取一个10,000行的电子表格:

// 没有代码执行 - 所有行都通过上下文流动
工具调用:gdrive.getSheet(sheetId: 'abc123') → 返回10,000行在上下文中手动过滤
// 使用代码执行 - 在执行环境中过滤
const allRows = await gdrive.getSheet({ sheetId: 'abc123' });
const pendingOrders = allRows.filter(row => row["Status"] === 'pending');
console.log(`找到 ${pendingOrders.length} 个待处理订单`);
console.log(pendingOrders.slice(0, 5)); // 仅记录前5行以供审查

Agent看到的是5行而不是10,000行。类似的模式适用于聚合、跨多个数据源的连接或提取特定字段——所有这些都不会膨胀上下文窗口。

基于上下文的流程控制

可以使用熟悉的代码模式(而不是链接单个工具调用)来实现循环、条件语句和错误处理。例如,如果你需要在Slack中收到部署通知,Agent可以编写:

let found = false;
while (!found) {
  const messages = await slack.getChannelHistory({ channel: 'C123456' });
  found = messages.some(m => m.text.includes('部署完成'));
  if (!found) await new Promise(r => setTimeout(r, 5000));
}
console.log('收到部署通知');

这种方法比通过Agent循环交替进行MCP工具调用和睡眠命令更高效。此外,能够编写一个被执行的条件下树也可以节省“首次token时间”延迟:Agent不必等待模型评估一个if语句,而是可以让代码执行环境来完成这个。

隐私保护操作

当Agent使用MCP执行代码时,中间结果默认保留在执行环境中。这样,Agent只看到你明确记录或返回的内容,意味着你不希望与模型共享的数据可以在不进入模型上下文的情况下流经你的工作流程。对于更敏感的工作负载,Agent可以利用程序自动对敏感数据进行标记化。例如,假设你需要从电子表格中将客户联系信息导入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(`更新了 ${sheet.rows.length} 个潜在客户`);

MCP客户端拦截数据并在数据到达模型之前对PII进行标记化:

// 如果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客户端中的查找解除标记化。真实的电子邮件地址、电话号码和姓名从Google Sheets流向Salesforce,但从未通过模型。这防止了Agent意外记录或处理敏感数据。你还可以使用此功能定义确定性的安全规则,选择数据可以流向和来自哪里。

状态保持与断点

具有文件系统访问权限的代码执行允许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);
// 后续执行从上次停止的地方继续
const saved = await fs.readFile('./workspace/leads.csv', 'utf-8');

Agent还可以将其自己的代码保存为可重用的函数。一旦Agent为某项任务开发了有效的代码,它可以保存该实现以供将来使用:

// 在 ./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');

这与技能的概念紧密相关,技能是模型用于提高在专业任务上性能的可重用指令、脚本和资源的文件夹。在这些保存的函数中添加一个SKILL.md文件,创建一个结构化的技能,模型可以引用和使用。随着时间的推移,这允许你的Agent建立一个更高层次能力的工具箱,演进其最有效工作所需的脚手架。

请注意,代码执行引入了自身的复杂性。运行Agent生成的代码需要一个具有适当沙盒、资源限制和监控的安全执行环境。这些基础设施要求增加了操作开销和安全考虑,而直接工具调用则避免了这些。

总结

MCP为Agent连接许多工具和系统提供了基础协议。然而,一旦连接了太多服务器,工具定义和结果可能会消耗过多的token,降低Agent效率。尽管这里的许多问题感觉新颖——上下文管理、工具组合、状态持久性——但它们都有来自软件工程的已知解决方案。代码执行将这些既定模式应用于Agent,让它们使用熟悉的编程构造更高效地与MCP服务器交互。

原文地址:https://www.anthropic.com/engineering/code-execution-with-mcp


一键三连,让我的信心像气球一样膨胀!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐