MCP 代码执行深度技术解析:让 Agent 写代码而非调工具

从直接工具调用到代码执行范式,拆解 Anthropic 如何让 Agent 以编程方式高效操控 MCP 服务器

Posted by iceyao on Tuesday, April 14, 2026

一、引言:Agent 的效率瓶颈,很多时候不在模型,而在工具调用方式

当我们讨论如何让 AI Agent 更高效时,注意力往往集中在模型能力、提示工程或上下文窗口大小上。但 Anthropic 在 Code execution with MCP 这篇工程文章里指出了一个更底层的问题:传统的 MCP 工具调用方式,本身就会制造大量不必要的上下文开销和交互延迟。

核心洞察很直接:与其让模型在每一步都"看见工具定义、发起调用、再接收中间结果",不如让模型写一小段代码,由代码在受控执行环境中完成多步编排。按照文中的量化示例,这种方式可以把工具定义相关的上下文开销从 150,000 tokens 压缩到 2,000 tokens,也就是 98.7% 的降幅。

但这里必须先厘清一个边界:代码执行不是 MCP 协议本身的新能力,而是构建在 MCP 之上的一种客户端实现模式。 协议没有变,变的是客户端如何把 MCP 服务器呈现给 Agent,以及 Agent 如何消费这些能力。

本文重点回答四个问题:

  • 传统 MCP 客户端到底慢在哪里?
  • 代码执行模式的核心架构是什么?
  • 为什么它既能提效,又能改善隐私保护?
  • 这类系统落地时,安全沙箱应该怎样设计?

二、先把边界说清楚:这不是"改造 MCP",而是"重构客户端"

如果只看标题,很多人会下意识以为"Code execution with MCP"意味着 MCP 协议新增了一种代码执行语义。其实不是。

Anthropic 文章讨论的是:客户端如何把一组 MCP 工具重新组织成适合代码消费的 API 视图。也就是说,原来 Agent 面对的是"工具列表 + 参数 Schema";现在 Agent 面对的是"文件树 + 类型化函数 + 代码运行时"。

可以把它理解为一层客户端侧适配器:

  1. 先连接原有的 MCP 服务器
  2. 获取这些服务器暴露出来的工具定义
  3. 自动生成一套可浏览、可导入、可调用的代码接口
  4. 把这套接口挂到安全的执行环境里供 Agent 使用

从这个角度看,代码执行模式的本质并不是"给协议加能力",而是给 Agent 加了一个更高效的编排层

下图展示了 MCP 协议层、客户端适配层和 Agent 执行层三者的分层关系——协议本身不变,变化只发生在客户端的呈现方式上:

MCP 协议层与客户端编排层的分层关系


三、问题诊断:传统 MCP 工具调用为什么昂贵

传统 MCP 客户端常见的做法是:连接服务器之后,把所有工具定义一次性注入上下文;之后每次需要做事,再由模型逐步决定调用哪个工具、传什么参数、如何处理返回值。

这套模式的主要问题有两个。

3.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.

当工具数量上千时,模型还没真正开始解决问题,上下文窗口就已经先被工具说明书占满了。这不是"推理成本高",而是准备阶段就已经浪费了大量令牌

3.2 中间结果被迫经过模型,上下文成了数据中转站

第二个问题更隐蔽,但也更昂贵。

假设你要把 Google Drive 里的会议纪要写进 Salesforce。传统模式下,流程通常是:

  1. 模型调用 gdrive.getDocument
  2. 完整文档内容回到上下文
  3. 模型再调用 salesforce.updateRecord
  4. 文档内容作为参数再次经过上下文传出去

也就是说,模型并不需要真正理解整份文档,却被迫充当了一次数据搬运的中转站。

如果文档很长,比如一份 2 小时会议纪要,那么这些内容会在上下文里往返两次,成本自然居高不下。

下图把传统模式和代码执行模式的差别并排画了出来:

传统工具调用 vs 代码执行模式数据流对比

可以把两种模式的差异压缩成一句话:

  • 传统工具调用:数据必须先经过模型,再流向另一个工具
  • 代码执行模式:数据可以在执行环境里直接流动,模型只看代码和必要摘要

四、核心架构:把 MCP 服务器呈现为代码 API,而不是直接工具列表

Anthropic 给出的核心做法,是为所有可用的 MCP 工具生成一套可浏览的文件树。Agent 不是直接面对"几千个工具定义",而是像开发者那样去浏览目录、打开文件、理解接口,然后按需写代码。

4.1 文件树是工具发现界面

典型结构如下:

servers/
├── google-drive/
│   ├── getDocument.ts
│   ├── getSheet.ts
│   ├── searchFiles.ts
│   └── index.ts
├── salesforce/
│   ├── updateRecord.ts
│   ├── query.ts
│   └── index.ts
├── slack/
│   ├── postMessage.ts
│   ├── getChannelHistory.ts
│   └── index.ts
└── ...

这里最关键的不是"用了 TypeScript",而是把工具发现改造成了渐进式信息暴露

  • 先看有哪些服务器
  • 再看某个服务器有哪些工具
  • 最后只读取当前任务需要的那几个函数定义

也正因为如此,工具定义不再需要一次性占满上下文。

4.2 每个工具都变成一个类型化函数

文章里给出的示例大致如下:

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

这段代码透露出几个很重要的设计点:

  • MCP 协议细节被收敛到底层的 callMCPTool
  • 上层暴露给 Agent 的是稳定的代码接口,而不是原始工具协议对象
  • TypeScript 类型信息本身就能帮助模型减少参数理解错误
  • 工具文件是独立的,天然支持按需加载

4.3 index.ts 让工具既能细粒度读取,也能模块化导入

一个目录下的 index.ts 通常承担聚合导出的角色。这样做的好处是,Agent 既可以:

  • 打开单个工具文件,查看精确签名
  • 也可以直接 import * as gdrive from './servers/google-drive'

这就把"工具系统"变成了"可编程 SDK"。

4.4 渐进式工具发现,才是令牌下降的关键原因

很多人会把注意力放在"模型写代码"这件事本身,但从工程角度看,真正带来显著令牌下降的,是工具定义改成了按需读取

可以把这个过程理解成下面三步:

Step 1: 只列出 servers/ 下有哪些服务器
Step 2: 只查看目标服务器有哪些导出函数
Step 3: 只读取当前要用的工具文件

这就是文中提到的 progressive disclosure(渐进式披露)——Agent 只拿到当前任务真正需要的信息,而不是先吞下一整本工具手册。

原文还提到一个补充思路:如果不想完全依赖文件遍历,也可以提供一个 search_tools 能力,让 Agent 先按名称、描述或完整定义级别去检索工具,再决定读取多少细节。这本质上仍然是在做同一件事——把工具发现做成按需展开,而不是全量注入。

下图直观展示了这种"信息漏斗"式的三步发现流程,以及与传统全量加载的巨大差异:

渐进式工具发现流程


五、工作原理:代码执行模式下,Agent 实际是怎么做事的

如果把整套系统合在一起,它的工作方式可以概括为:模型只负责编写和理解代码,真正的数据搬运、过滤、轮询、聚合,都发生在安全的执行环境里。

下面这张图可以先帮助你建立整体心智模型:

MCP 代码执行系统架构

从交互路径上看,完整流程大致是:

  1. 用户提出任务
  2. Agent 浏览 servers/ 文件树
  3. Agent 读取少量目标工具文件
  4. Agent 写出一段 TypeScript 代码
  5. 代码在受控沙箱中运行
  6. 运行时代码通过 callMCPTool() 和各个 MCP 服务器交互
  7. 最终只把摘要、日志或显式结果返回给模型

也就是说,模型不再需要"亲自参与"每一跳数据传输,它只需要:

  • 理解要做什么
  • 写出怎样的程序
  • 根据输出决定下一步是否继续

这是一种非常接近真实工程开发的工作方式。


六、为什么代码执行模式更强:它不是少调几次工具,而是换了执行位置

代码执行模式的优势,不只是"更省 tokens",更重要的是它把很多本来要靠模型一轮轮思考的事情,搬到了执行环境里直接完成

6.1 跨系统数据编排可以在环境内直接完成

例如从 Google Drive 中读取会议纪要,再写入 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 }
});

这里最重要的不是"代码比较简洁",而是 transcript 这份正文内容只在执行环境里存在。它不需要回到模型上下文里,也不需要被模型重新"看到"一遍。

下图展示了数据在沙箱内直接从源系统流向目标系统、而模型只接收代码和摘要的完整路径:

跨系统数据编排数据流

6.2 数据过滤和聚合发生在执行环境,而不是上下文窗口里

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));

这个例子非常典型:

  • 原始表格可能很大
  • 模型并不需要看完整表格
  • 它只需要一个过滤后的摘要和少量样本

于是,数据处理发生在代码里,模型拿到的是计算后的结果,而不是待计算的原始材料

6.3 循环、轮询、重试等控制流不再消耗多轮 LLM 推理

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');

在传统模式下,这类逻辑会变成多轮"推理 → 调工具 → 再推理"的循环;而在代码执行模式下,循环本身就在运行时完成了。

这还会带来一个经常被忽略的收益:更低的 time to first token 延迟。 因为很多 if/while 判断不再需要等模型重新思考一轮,而是直接由执行环境完成。

6.4 状态可以落盘,技能可以复用

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');

一旦执行环境带有工作目录,很多一次性操作就可以被积累成可复用资产。文章里还展示了把常见任务封装成 skill 的思路,例如把 Google Sheet 导出为 CSV:

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 不只是"临时写一段脚本",而是开始形成自己的可复用执行资产。

Anthropic 原文进一步把这件事和 Skills 机制联系了起来:如果把这些可复用函数连同说明文档一起沉淀下来,例如补上一份 SKILL.md,它们就不再只是零散脚本,而会演化成 Agent 可长期复用的高阶能力模块。


七、隐私保护:真实数据在环境内流动,模型只看脱敏视图

这是整篇文章里我认为最容易被低估的一点。

传统工具调用模式下,只要模型要决定下一步,它通常就得先看到上一步的结果。于是敏感数据很容易进入上下文。代码执行模式则提供了另一种可能:让真实数据在执行环境内部完成读、写、转换,而模型只看到必要的日志和摘要。

例如下面这段代码,会把表格里的联系人数据直接写入 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`);

如果这时 Agent 尝试把原始数据打印出来,客户端可以在输出层做拦截和令牌化,让模型看到的不是原值,而是类似下面的占位视图:

[
  { salesforceId: '00Q...', email: '[EMAIL_1]', phone: '[PHONE_1]', name: '[NAME_1]' },
  { salesforceId: '00Q...', email: '[EMAIL_2]', phone: '[PHONE_2]', name: '[NAME_2]' }
]

也就是说:

  • 真实 PII 数据:在执行环境和目标系统之间流动
  • 模型可见内容:是经过脱敏处理的日志或摘要

下面这张图把这层关系画得更直观:

隐私保护数据流

这并不意味着"模型绝不可能接触敏感数据",而是意味着系统终于有机会通过确定性的输出控制,把敏感信息默认拦在上下文之外。对于企业场景,这一点非常关键。


八、安全沙箱:代码执行带来效率,也带来新的基础设施义务

Anthropic 在文章结尾明确提醒:运行 Agent 生成的代码,会引入新的复杂度。原文点名了三类基础要求:

  • Sandboxing
  • Resource limits
  • Monitoring

这三个词很短,但背后的工程含义并不轻。下图展示了一个安全沙箱从外到内的多层防护结构:

安全沙箱分层架构

一个真正可用的代码执行系统,至少要补齐下面这些控制面:

安全维度 工程做法 主要目的
执行隔离 使用受限运行时或容器化沙箱 防止代码逃逸影响宿主系统
资源限制 CPU、内存、磁盘、执行时长配额 防止资源耗尽或死循环拖垮系统
行为监控 执行日志、异常检测、审计记录 及时发现异常模式
网络控制 仅允许通过受控出口访问指定能力 防止任意对外访问
输出控制 对日志、返回值、错误信息做脱敏或裁剪 防止敏感数据进入模型上下文

从这里也能看出,代码执行并不是"白拿性能红利"。它的代价是:把复杂度从上下文管理,转移到了运行时治理。

8.1 为什么直接工具调用依然有存在价值

这也是这篇文章非常诚实的地方。它没有说"代码执行一定全面替代工具调用",而是明确承认:直接工具调用规避了运行时代码的很多安全和运维负担。

所以两者更像是一个权衡:

  • 直接工具调用:简单、直接、实现成本低,但上下文成本高
  • 代码执行模式:令牌和延迟表现更好,但需要更强的基础设施兜底

如果任务只是一步查询、一步提交,那么直接工具调用依然很合理;但如果任务本质上是一个多步程序,那么让 Agent 写代码,往往更符合问题本身的结构。


九、量化视角:哪些数据来自原文,哪些是工程上的延伸估算

这里需要把口径分清楚。

9.1 原文明确给出的数据

Anthropic 原文明确给出的关键数字,是渐进式工具发现带来的上下文节省:

指标 数值
工具定义相关上下文开销(传统方式) 150,000 tokens
工具定义相关上下文开销(代码执行模式) 2,000 tokens
节省比例 98.7%

这组数据对应的是:Agent 不再一次性读取全部工具定义,而是只按需读取当前任务需要的函数接口。

9.2 工程上的延伸估算

如果把数据搬运、轮询控制流、日志摘要等因素也纳入考虑,在复杂多步任务里,整体收益通常还会继续扩大。为了帮助理解,可以用一个典型场景做示例性估算

指标 传统工具调用 代码执行 说明
工具定义加载 ~150,000 tokens ~2,000 tokens 直接对应原文结论
中间结果传递 数据可留在执行环境内
轮询 / 重试 多轮推理 单次代码执行 控制流在运行时完成
长链路编排 逐步决策 程序化执行 更适合多步工作流

这部分表格不是原文逐项给出的 benchmark,而是基于文章机制做的工程推演。把这层区分说清楚,文章会更严谨。


十、实现启示:为什么说这是一种更像"软件工程"的 Agent 架构

MCP 代码执行模式真正有意思的地方,不只是它更快,而是它让 Agent 的工作方式更接近开发者本身。

传统模式下,Agent 像一个不断口头指挥的调度员:

  • 看工具说明
  • 决定下一步调哪个
  • 读结果
  • 再决定下一步

而代码执行模式下,Agent 更像一个真正写程序的工程师:

  • 先发现可用接口
  • 再写出一个小程序
  • 把过滤、循环、聚合、错误处理写在代码里
  • 最后只把结果摘要拿回来

下图把两种工作模式并排放在一起,可以直观看到从"调度员"到"工程师"的角色转变:

传统调度员模式 vs 代码执行工程师模式

这种变化的意义在于:Agent 的"思考对象"从单步工具调用,变成了更高层的程序结构。 这正是复杂任务所需要的抽象层级。

从落地角度看,这种架构尤其适合下面几类任务:

  • 需要跨多个系统搬运和转换数据
  • 需要过滤大结果集,只回传摘要
  • 需要轮询、等待、重试等控制流
  • 需要在多次执行之间保存中间状态
  • 对隐私隔离和上下文成本都较敏感

十一、与 Cloudflare “Code Mode” 的呼应

Anthropic 在文中还专门提到,Cloudflare 也发布了类似结论,并把这类"让 LLM 通过写代码来使用 MCP"的模式称为 Code Mode

这个呼应很重要,因为它说明这不是某一篇文章里的孤例,而是行业正在逐步收敛出的共同认知:

  1. LLM 很擅长写小段程序
  2. 程序比逐轮工具调用更适合表达多步任务
  3. 文件系统和代码 API 是比大工具列表更自然的 Agent 交互界面

换句话说,代码执行并不是 MCP 体系里的偶发技巧,而是在 Agent 工程化过程中越来越自然的一条路线。


十二、总结:代码执行不是替代 MCP,而是在 MCP 之上增加一个更高效的编排层

如果要用一句话概括 Anthropic 这篇文章的价值,我会这样说:

它不是在教我们如何"多接几个工具",而是在提醒我们:当任务本质上是一个程序时,就应该让 Agent 以程序的方式完成它。

最后把全文收束成几个结论:

  • 代码执行不是 MCP 协议改造,而是客户端实现升级
  • 真正带来大幅节省的关键,是工具定义的渐进式披露
  • 数据处理、控制流和状态管理被搬到了执行环境中
  • 隐私保护因此有了"默认不进上下文"的可能性
  • 与此同时,系统必须承担沙箱、资源限制和监控的基础设施成本

所以,代码执行模式并不是对所有场景都更好。对于简单的一步式操作,直接工具调用仍然高效而实用;但对于复杂的、多阶段的、跨系统的数据编排任务,它通常更贴近问题本身的真实结构。

从这个意义上说,Agent 不再只是"工具调用者",而是在逐步成为一个能写程序、能复用技能、能在受控环境中执行任务的软件型执行体


参考资料

  1. Code execution with MCP: Building more efficient agents - Anthropic Engineering
  2. Model Context Protocol - Official Documentation
  3. Cloudflare Code Mode

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付