☰
使用MCP进行代码执行:构建更高效的智能体——TaoToken统一Key接入实战
2026/10/9 12:52:43 网站建设 项目流程

1. 为什么你的 MCP 智能体越跑越贵:从工具定义膨胀说起

如果你最近在折腾 Anthropic 的 MCP(Model Context Protocol),大概率会遇到一个很反直觉的现象:工具接得越多,智能体反而越笨、越慢、越烧钱。我一开始也以为是模型能力问题,后来把请求日志拉出来一看,才发现真正的元凶是上下文窗口被工具定义和中间结果塞爆了。

MCP 是 Anthropic 在 2024 年 11 月推出的开放标准,目标是让智能体用一套通用协议连接外部系统,不用再为每个工具写一遍胶水代码。这个愿景很好,社区也确实建了成千上万个 MCP Server,主流语言都有 SDK。但问题在于,大多数 MCP 客户端的默认行为是:在对话开始前,把所有已连接 Server 的工具定义一次性预加载进上下文。你连了 5 个 Server、每个 Server 20 个工具,那就是 100 份工具描述,光这些描述就可能吃掉几万 token,模型还没开始读你的问题,上下文已经用掉一大半。

更隐蔽的坑是中间结果。举个典型场景:你让智能体“把 Google Drive 里的会议纪要读出来,写进 Salesforce 的潜在客户记录”。传统直接调用模式下,模型会先调gdrive.getDocument,返回的完整纪要文本进入上下文;然后模型再调salesforce.updateRecord,把这段完整文本又写一遍进上下文。一份两小时的会议纪要可能 5 万 token,等于同一份数据在上下文里流了两遍。文档再大一点,直接超上下文窗口,工作流当场断掉。

Anthropic 那篇《Code execution with MCP: Building more efficient agents》给出的解法很工程化:别让模型直接调工具,让模型写代码去调工具。把 MCP Server 包装成代码 API,工具定义以文件树形式存在文件系统里,模型按需读取它当前任务真正需要的那几个文件,中间数据在执行环境里先过滤、聚合、裁剪,只把最终需要的那几行结果返回给模型。官方给的数字是从 15 万 token 降到 2000 token,省了 98.7% 的成本和时间。

这篇就沿着这个思路,用一个 TypeScript 项目 Demo,把代码执行型 MCP 智能体从零跑通。中间会用到 TaoToken 的统一 Key 和 API 通道来接入 Anthropic 模型,这样你不用在多个平台之间来回切 Key,一个通道就能把 MCP 工具链和模型调用串起来。适合已经了解 MCP 基本概念、想把它真正落到代码执行场景的开发者。

2. TaoToken 统一 Key 接入:把 Anthropic 模型通道先打通

在写 MCP Server 和智能体代码之前,得先把模型通道准备好。代码执行型智能体的核心是“模型生成代码 → 执行环境跑代码 → 结果回传模型”,这个循环里模型调用会非常频繁,如果 Key 管理混乱、通道不稳定,调试成本会成倍上升。我用 TaoToken 的统一 Key 来收口这件事,一个 Key 走通 Anthropic 模型调用,省去多平台切换的麻烦。

先说清楚它在这里扮演的角色:TaoToken 提供统一的 API 通道,你拿到的 Key 可以用于调用 Anthropic 系列模型,Base URL 指向https://taotoken.net/api。注意 API 地址不带任何查询参数,保持干净。官网入口是https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,注册和查看文档都从这里进。

拿 Key 的路径很直接:进官网后找到控制台,在 API Keys 页面创建一个新 Key。建议按项目维度建 Key,比如这个 MCP Demo 单独一个,方便后面排查问题时定位是哪个项目在消耗额度。创建完把 Key 复制出来,形如sk-开头的一串字符,先存到环境变量里,别硬编码进代码。

# .env 文件,放在项目根目录 TAOTOKEN_API_KEY=sk-你的实际Key TAOTOKEN_BASE_URL=https://taotoken.net/api

这里有个容易踩的坑:Base URL 到底要不要带/v1。不同 SDK 对路径的处理不一样,Anthropic 官方 SDK 默认会在 Base URL 后面拼/v1/messages,所以你的 Base URL 填到https://taotoken.net/api就行,不要再手动加/v1,否则会变成/api/v1/v1/messages,直接 404。我第一次配的时候就栽在这,报错信息还比较隐晦,排查了半天。

模型 ID 这块,代码执行场景建议用 Claude 系列里支持工具调用和长上下文能力较好的型号。具体可用型号以 TaoToken 控制台或文档里列出的为准,因为模型列表会更新,我不在这里写死。你在控制台能看到当前可用的 Model ID,复制那个字符串填到配置里。

如果你同时还在用 Claude Code 做日常编码,TaoToken 的 Coding Plan 可以把编码场景和这个 MCP Demo 的调用分开管理,额度互不干扰。接入文档在官网的 doc 页面,里面有各语言 SDK 的配置示例,遇到路径或鉴权问题时对着文档核对一遍最快。

把 Key 和 Base URL 准备好之后,先别急着写 MCP 逻辑,用一段最小代码验证通道是否通。这一步很重要,因为后面 MCP 报错时,你得能区分是模型通道的问题还是 MCP 配置的问题。验证代码用 Anthropic 官方 SDK:

// verify-channel.ts import Anthropic from '@anthropic-ai/sdk'; import 'dotenv/config'; const client = new Anthropic({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, }); async function main() { const msg = await client.messages.create({ model: '你的Model ID', max_tokens: 128, messages: [{ role: 'user', content: '只回复两个字:通了' }], }); console.log(msg.content); } main().catch(console.error);

跑npx tsx verify-channel.ts,如果输出里能看到模型返回的内容,说明通道没问题。如果报 401,检查 Key 是否复制完整、有没有多余空格;如果报连接错误,检查 Base URL 是否写成了带/v1的形式。这一步过了,再往下搭 MCP 才有意义。

3. 可复制的 MCP Server 配置与代码执行环境搭建

通道验证通过后,进入核心部分:把 MCP Server 包装成代码 API,并搭好代码执行环境。这一节会给出可直接复制的配置文件片段和 TypeScript 代码,路径和原文保持一致,你照着建目录就行。

先规划项目结构。核心思路是:每个 MCP Server 对应一个目录,每个工具对应一个.ts文件,文件里导出一个函数,函数内部通过统一的callMCPTool去真正调用 MCP 工具。模型通过浏览文件系统来发现工具,只读它需要的文件。

mcp-code-agent/ ├── .env ├── package.json ├── tsconfig.json ├── client.ts # MCP 客户端封装,提供 callMCPTool ├── servers/ │ ├── google-drive/ │ │ ├── getDocument.ts │ │ ├── getSheet.ts │ │ └── index.ts │ └── salesforce/ │ ├── updateRecord.ts │ ├── query.ts │ └── index.ts ├── skills/ │ └── save-sheet-as-csv.ts └── agent.ts # 智能体主循环

client.ts是整个方案的地基,它负责和 MCP Server 建立连接,并把工具调用封装成一个泛型函数。这里用 MCP 官方 SDK 的客户端能力,连接方式支持 stdio 和 SSE,Demo 里用 stdio 最省事。

// client.ts import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; const clients = new Map<string, Client>(); export async function getClient(serverName: string): Promise<Client> { if (clients.has(serverName)) return clients.get(serverName)!; const transport = new StdioClientTransport({ command: 'npx', args: ['-y', `@modelcontextprotocol/server-${serverName}`], }); const client = new Client( { name: 'code-exec-agent', version: '1.0.0' }, { capabilities: {} } ); await client.connect(transport); clients.set(serverName, client); return client; } export async function callMCPTool<T>( toolName: string, input: Record<string, unknown> ): Promise<T> { // toolName 形如 google_drive__get_document const [serverPart, ...rest] = toolName.split('__'); const serverName = serverPart.replace(/_/g, '-'); const client = await getClient(serverName); const result = await client.callTool({ name: rest.join('__'), arguments: input, }); return result.content as T; }

然后是具体工具文件。以servers/google-drive/getDocument.ts为例,它只做一件事:声明输入输出类型,然后转调callMCPTool。模型读这个文件就能知道工具怎么用,不需要预加载全部工具定义。

// servers/google-drive/getDocument.ts import { callMCPTool } from '../../client.js'; interface GetDocumentInput { documentId: string; } interface GetDocumentResponse { content: string; } /* 从 Google Drive 读取文档内容 */ export async function getDocument( input: GetDocumentInput ): Promise<GetDocumentResponse> { return callMCPTool<GetDocumentResponse>( 'google_drive__get_document', input ); }

servers/google-drive/index.ts做统一导出,方便模型用import * as gdrive from './servers/google-drive'这种方式引用:

// servers/google-drive/index.ts export { getDocument } from './getDocument.js'; export { getSheet } from './getSheet.js';

Salesforce 那边同理,updateRecord.ts和query.ts各管一个工具。这里不重复贴,结构完全一致,你照着改工具名和参数类型即可。

接下来是 MCP Server 的配置文件。如果你用的是支持 MCP 配置的客户端(比如 Claude Desktop 或 Cline),配置片段长这样,注意路径要换成你本地的绝对路径:

{ "mcpServers": { "google-drive": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-google-drive"], "env": { "GOOGLE_DRIVE_CREDENTIALS": "/path/to/credentials.json" } }, "salesforce": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-salesforce"], "env": { "SALESFORCE_TOKEN": "your-token" } } } }

如果你用的是 Cline 的 MCP 配置,格式类似,但字段名可能略有差异,以 Cline 文档为准。关键点在于:每个 Server 的command和args要能独立跑起来,你可以先在终端手动执行npx -y @modelcontextprotocol/server-google-drive确认它能启动,再写进配置。

代码执行环境这块,Demo 里用 Node.js 的child_process起一个受限的沙箱进程来跑模型生成的代码。生产环境建议上更严格的沙箱方案,比如容器隔离或isolated-vm,Demo 为了跑通流程先用简单方式。

// executor.ts import { execFile } from 'child_process'; import { promisify } from 'util'; const execFileAsync = promisify(execFile); export async function runCode(code: string): Promise<string> { const { stdout, stderr } = await execFileAsync( 'npx', ['tsx', '-e', code], { timeout: 30000, maxBuffer: 1024 * 1024 * 10 } ); return stderr ? `${stdout}\n[stderr] ${stderr}` : stdout; }

到这里,MCP Server 配置、工具文件、执行环境三件套就齐了。下一节把智能体主循环串起来,跑一个端到端的真实任务。

4. 端到端验证:让智能体写代码完成 Drive 到 Salesforce 的数据流转

环境搭好后,最关键的一步是验证整个链路真的能跑通。这一节用一个具体任务走完全流程:从 Google Drive 读一份表格,过滤出待处理订单,写进 Salesforce。你会看到模型如何生成代码、代码如何调用 MCP 工具、结果如何回传。

先写智能体主循环agent.ts。它的职责是:把系统提示词和用户任务发给模型,模型返回代码,执行代码,把执行结果回传模型,循环直到模型给出最终答复。系统提示词里要明确告诉模型:你可以通过写 TypeScript 代码来调用工具,工具定义在./servers/目录下,用import引入即可。

// agent.ts import Anthropic from '@anthropic-ai/sdk'; import 'dotenv/config'; import { runCode } from './executor.js'; const client = new Anthropic({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, }); const SYSTEM_PROMPT = ` 你是一个代码执行型智能体。你可以通过编写 TypeScript 代码来调用 MCP 工具。 工具定义位于 ./servers/ 目录,每个工具是一个导出函数。 写代码时用 import 引入需要的工具,例如: import * as gdrive from './servers/google-drive'; import * as salesforce from './servers/salesforce'; 执行环境会运行你的代码并把 stdout 返回给你。 只写代码,不要写解释。代码要能直接执行。 `; async function runAgent(task: string) { const messages: Anthropic.MessageParam[] = [ { role: 'user', content: task }, ]; for (let i = 0; i < 10; i++) { const resp = await client.messages.create({ model: '你的Model ID', max_tokens: 4096, system: SYSTEM_PROMPT, messages, }); const textBlock = resp.content.find((b) => b.type === 'text'); if (!textBlock || textBlock.type !== 'text') break; const code = textBlock.text; console.log(`--- 第 ${i + 1} 轮生成的代码 ---\n${code}`); const output = await runCode(code); console.log(`--- 执行结果 ---\n${output}`); messages.push({ role: 'assistant', content: code }); messages.push({ role: 'user', content: `执行结果:\n${output}\n如果任务完成,回复 DONE。`, }); if (output.includes('DONE')) break; } } runAgent('读取 Google Drive 表格 abc123,找出 Status 为 pending 的订单,只打印前 5 条。');

跑起来后,模型第一轮大概率会生成类似这样的代码:

import * as gdrive from './servers/google-drive'; 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));

注意这里的关键差异:传统直接调用模式下,getSheet返回的 10000 行会全部进入模型上下文;而代码执行模式下,10000 行只在执行环境里存在,模型最终看到的只有console.log输出的那 5 行。这就是 token 节省的来源。

执行结果回传后,模型看到“找到了 N 个待处理订单”和 5 行样本,会判断任务是否完成。如果任务要求写入 Salesforce,它会生成第二轮代码:

import * as gdrive from './servers/google-drive'; import * as salesforce from './servers/salesforce'; const allRows = await gdrive.getSheet({ sheetId: 'abc123' }); const pendingOrders = allRows.filter((row) => row['Status'] === 'pending'); for (const row of pendingOrders) { await salesforce.updateRecord({ objectType: 'Order', recordId: row.salesforceId, data: { Status: 'processing', Notes: row.notes }, }); } console.log(`DONE 更新了 ${pendingOrders.length} 条订单`);

看到DONE后主循环退出。整个过程中,模型上下文里只有代码和精简后的执行结果,没有原始的大数据集。实测下来,一个万行表格的任务,token 消耗从直接调用模式的十几万降到几千,响应速度也快了一个数量级。

验证时建议先用小数据集跑通,确认callMCPTool能正确连上 Server、工具函数能正常返回数据,再换大数据集测 token 节省效果。如果第一轮代码就报错,把错误信息回传给模型,它通常能自己修正,这也是代码执行模式的一个好处:错误处理逻辑可以写在代码里,不用模型反复介入。

5. 常见报错排查:401、local proxy failed 与 reading choices

跑通之后,你可能会在不同环节遇到报错。这一节把几个高频错误和排查路径列出来,都是我在实际调试中踩过的。

401 Unauthorized:最常见,基本是 Key 或 Base URL 的问题。先确认.env里的TAOTOKEN_API_KEY没有多余空格或换行,复制时容易带上。再确认TAOTOKEN_BASE_URL是https://taotoken.net/api,没有手动加/v1。如果 Key 是从控制台新创建的,确认它处于启用状态。还有一种情况是环境变量没被正确加载,dotenv/config要在文件顶部第一行 import,晚于 SDK 初始化就会读到 undefined。

local proxy failed / connection refused:这个报错通常出现在 MCP Server 启动阶段,说明StdioClientTransport没能拉起子进程。排查顺序是:先在终端手动执行配置里的command和args,看能不能启动;如果手动能启动但代码里不行,检查command是不是用了相对路径,改成绝对路径或确保npx在 PATH 里。另外,某些 Server 需要额外的环境变量(比如凭证文件路径),漏配会导致启动即退出,表现为连接失败。

reading 'choices' of undefined:这个报错一般出现在模型响应解析环节,说明返回结构不符合预期。可能原因是 Model ID 填错了,或者 Base URL 路径不对导致请求打到了非预期端点。先确认 Model ID 是从 TaoToken 控制台复制的当前可用型号,再确认 Base URL 没有多余路径。如果用的是 OpenAI 兼容格式的 SDK 去调 Anthropic 模型,响应结构会不一样,注意 SDK 和模型要匹配。

OAuth 相关报错:如果你接的 MCP Server 需要 OAuth 授权(比如某些 Google 服务),报错信息里会出现OAuth、token expired、invalid_grant等关键词。这类问题不在模型通道侧,而在 MCP Server 的授权配置。检查凭证文件是否过期、授权范围是否包含所需 API、回调地址是否配置正确。Demo 里为了简化用了 token 方式,生产环境建议走完整的 OAuth 流程。

工具调用返回空结果:代码执行成功但callMCPTool返回空,先确认工具名拼写。toolName的格式是server_name__tool_name,中间是双下划线,Server 名里的连字符要转成下划线。比如google-drive对应google_drive。这个转换在callMCPTool里做了,但如果你手动传工具名,容易漏掉。

排查时有个通用技巧:把callMCPTool的原始返回打出来看,不要只看封装后的结果。很多时候问题出在数据格式和预期不一致,比如返回的是{ content: [...] }而不是直接的数组,加一行console.log(JSON.stringify(result, null, 2))就能看清。

6. 把代码执行型智能体接到你的工作流里

跑通 Demo 只是起点,真正有价值的是把它接到日常开发流程里。这里说几个我实际用下来觉得值得做的方向。

第一,把常用操作沉淀成skills/目录下的可复用函数。比如“把表格导出成 CSV”这个操作,第一次让模型写代码实现后,把代码保存到skills/save-sheet-as-csv.ts,下次直接 import 调用,不用模型重新生成。时间长了,你会积累一个自己的工具库,模型的能力边界也随之扩展。这跟 Anthropic 提的 Skills 概念是一致的:可复用的指令、脚本和资源文件夹。

第二,给代码执行环境加上资源限制和监控。Demo 里用了 30 秒超时和 10MB 输出上限,生产环境还要加内存限制、网络访问控制、文件系统读写范围限制。模型生成的代码不可全信,沙箱是必须的。如果任务涉及敏感数据,可以在执行环境里做 token 化,让真实数据不进入模型上下文,只让模型看到占位符。

第三,把 MCP 通道和模型通道的额度分开管理。TaoToken 的 Coding Plan 适合长期编码和 Agent 场景,和按量调用的 API Key 分开,这样你能清楚知道每个项目消耗了多少。控制台里可以按 Key 维度看用量,排查异常消耗时很方便。

第四,渐进式披露工具定义。当你的servers/目录下工具数量超过几十个时,可以考虑加一个search_tools工具,让模型先搜索再加载具体工具文件,而不是遍历整个目录。这样即使工具规模继续增长,上下文消耗也能保持可控。

最后说一个实际经验:代码执行模式不是银弹,它引入了沙箱、监控、错误处理这些额外复杂度。如果你的智能体只连两三个工具、数据量也不大,直接调用模式反而更简单。但当工具数量上到几十个、单次任务涉及大数据集流转时,代码执行带来的 token 节省和延迟改善是实打实的。判断标准很简单:看你的上下文窗口里,工具定义和中间结果占了多少比例,超过三成,就该考虑切到代码执行模式了。

接入文档和 API Keys 都在 TaoToken 官网可以找到,模型对话入口适合先验证通道,Coding Plan 适合把编码类 Agent 长期跑起来。先把 Demo 跑通,再按自己的场景逐步替换工具和数据源,这条路走下来比一上来就搭大框架要稳得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询