社区已经构建了上千个 MCP 服务器;
各大编程语言都有相应 SDK;
行业普遍将 MCP 作为连接智能体与外部系统的事实标准。
这篇文章将探讨如何通过代码执行(Code Execution)让智能体更高效地与 MCP 服务器交互,在处理更多工具的同时,使用更少的 tokens。
MCP 使用扩大后,主要有两种常见模式导致智能体成本上升、响应变慢:
1)工具定义(Tool Definitions)塞满上下文窗口;
2) 中间结果(Intermediate Results)反复占用上下文空间。
大多数 MCP 客户端会在启动时把所有工具定义一次性加载进上下文,例如:
gdrive.getDocumentDescription:RetrievesadocumentfromGoogleDriveParameters:documentId(required,string):TheIDofthedocumenttoretrievefields(optional,string):SpecificfieldstoreturnReturnsocumentobjectwithtitle,bodycontent,metadata,permissions,etc.
salesforce.updateRecordDescription:UpdatesarecordinSalesforceParametersbjectType(required,string):TypeofSalesforceobject(Lead,Contact,Account,etc.)recordId(required,string):TheIDoftherecordtoupdatedata(required,object):FieldstoupdatewiththeirnewvaluesReturns:Updatedrecordobjectwithconfirmation
工具描述会大量占用上下文窗口的 token 空间,从而增加响应时间和使用成本。当智能体连接几千个工具时,可能在理解请求之前就得处理几十万 token。
大多数MCP客户端允许模型直接调用MCP工具。例如,你让智能体执行任务:“从 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 needstowrite entire transcript into context again)
每一个中间结果都必须经过模型传递。在上面的例子中,整份会议记录内容在流程中被传递了两次。
如果这是一次持续 2 小时的销售会议,记录文本可能就会额外占用5 万个 token。
而对于更大的文档,这种数据传递甚至可能超出模型的上下文窗口限制,导致整个工作流中断。
此外,当文档体量大或数据结构复杂时,模型在不同工具调用之间复制这些数据时,也更容易出现错误,比如遗漏内容、格式混乱或数据不匹配等问题。
MCP 客户端会将工具定义加载到模型的上下文窗口中,并在模型与工具之间协调一套消息循环机制(message loop)。在这个循环中,每一次工具调用及其返回结果,都需要经过模型本身来传递和处理,
这就导致上下文在多次交互中不断被占用和膨胀。
随着代码执行环境在智能体中越来越普及,一种更高效的解决方案是:
👉将 MCP 服务器以代码 API 的形式呈现,而不是直接调用工具。
这样,智能体就可以直接编写代码来与 MCP 服务器交互。这种方法能同时解决前面提到的两大问题:
智能体可以只加载所需的工具定义;
并能在执行环境中先处理数据,再将结果传回模型,从而显著节省上下文空间并提升性能。
实现方式有多种,其中一种做法是:从已连接的 MCP 服务器生成一个包含所有可用工具的文件树结构(file tree)。
下面是一个使用TypeScript的示例:
servers├──google-drive│├──getDocument.ts│├──...(othertools)│└──index.ts├──salesforce│├──updateRecord.ts│├──...(othertools)│└──index.ts└──...(otherservers)
每个工具对应一个文件,例如:
// ./servers/google-drive/getDocument.tsimport{ callMCPTool }from"../../../client.js";interfaceGetDocumentInput{documentId:string;}interfaceGetDocumentResponse{content:string;}/* Read a document from Google Drive */exportasyncfunctiongetDocument(input:GetDocumentInput)romise<GetDocumentResponse> {
returncallMCPTool<GetDocumentResponse>('google_drive__get_document', input);}
上文的 Google Drive 到 Salesforce 的示例可以写成如下代码:
// Read transcript from Google Docs and add to Salesforce prospectimport *asgdrivefrom'./servers/google-drive';import *assalesforcefrom'./servers/salesforce';consttranscript= (await gdrive.getDocument({documentId:'abc123'})).content;await salesforce.updateRecord({objectType:'SalesMeeting',recordId:'00Q5f000001abcXYZ',data: {Notes: transcript }});
智能体通过遍历文件系统来发现可用工具:
1. 先列出./servers/目录,找到已连接的服务器(例如google-drive、salesforce);
2. 再读取其中具体的工具文件(如getDocument.ts、updateRecord.ts),以了解每个工具的接口定义。
这样,智能体只需加载当前任务所需的工具定义即可,无需一次性载入全部工具。
这种方式能将 token 使用量从15 万减少到2000个,实现98.7% 的时间与成本节省。
Cloudflare 也发表了类似的研究成果,他们将这种基于 MCP 的代码执行方式称为「Code Mode」。
其核心思想是一致的:大语言模型擅长编写代码,开发者应充分利用这一优势,让智能体以更高效、更可扩展的方式与 MCP 服务器进行交互。
通过 MCP 实现的代码执行,让智能体能够更高效地利用上下文。
它可以按需加载工具、在数据进入模型前先进行过滤和处理,并能在一次执行中完成复杂逻辑。此外,这种方式在安全性和状态管理方面也带来了额外的好处。
模型非常擅长浏览文件系统。当工具以代码文件的形式呈现在文件系统中时,模型就可以按需读取工具定义,而不是一开始就加载所有内容。
例如,在前文提到的 Salesforce 服务器场景中,可以为服务器添加一个search_tools工具,用于搜索相关的工具定义。
当智能体搜索 “salesforce” 时,它只会加载当前任务所需的那几个工具。
此外,还可以为search_tools工具添加一个细节级别参数(detail level),让智能体自行选择加载的定义程度:
仅名称(name only)
名称与描述(name + description)
完整定义(包括数据结构 schema)
这种机制能让智能体节省上下文空间,并更高效地发现合适的工具。
当处理大规模数据集时,智能体可以在执行环境中先过滤、转换结果,再将精简后的数据返回模型。
例如,假设要获取一个包含 10,000 行数据的电子表格:
// Without code execution - all rows flow through contextTOOLCALL: gdrive.getSheet(sheetId:'abc123')→ returns10,000rowsincontext to filter manually// With code execution - filter in the execution environmentconstallRows =awaitgdrive.getSheet({sheetId:'abc123'});constpendingOrders = 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
智能体最终只会看到前5 行数据,而不是完整的1 万行表格。
这种模式同样适用于聚合计算(aggregations)、跨多个数据源的关联(joins),或提取特定字段(field extraction)等场景,整个过程都不会让上下文窗口“爆炸”。
通过代码执行,循环、条件判断、错误处理等逻辑都可以用常见的编程结构来实现,而不需要在每次工具调用之间反复让模型判断和响应。
例如,如果你需要在Slack中接受部署完成的通知,智能体可以这样写:
letfound=false;while(!found){constmessages=awaitslack.getChannelHistory({channel:'C123456'});found=messages.some(m=>m.text.includes('deploymentcomplete'));if(!found)awaitnewPromise(r=>setTimeout(r,5000));}console.log('Deploymentnotificationreceived');这种方式比在智能体循环中反复切换MCP 工具调用与sleep 命令要高效得多。
此外,能够直接编写并执行条件分支(conditional tree)还能显著减少「首个 Token 延迟」(time to first token latency)。
也就是说,模型无需等待自身去判断if语句的结果,执行环境会直接处理这些逻辑,从而更快地进入下一步。
当智能体通过 MCP 执行代码时,中间结果默认保留在执行环境内,模型只会看到那些被明确记录(log)或返回(return)的内容。
这意味着,你不希望模型接触的数据,可以在整个流程中安全流转,而不会进入模型的上下文。
对于更加敏感的任务,智能体运行框架(Agent harness)还能自动对敏感数据进行脱敏与标记化(tokenization)。
例如,当你需要从电子表格中导入客户联系信息到 Salesforce 时,智能体可以这样编写代码:
constsheet=awaitgdrive.getSheet({sheetId:'abc123'});for(constrowofsheet.rows){awaitsalesforce.updateRecord({objectType:'Lead',recordId:row.salesforceId,data:{Email:row.email,Phone:row.phone,Name:row.name}});}console.log(`Updated${sheet.rows.length}leads`);MCP 客户端会在数据进入模型之前拦截它,并对其中的个人敏感信息(PII,Personally Identifiable Information)进行标记化处理(tokenization)。
//Whattheagentwouldsee,ifitloggedthesheet.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 客户端会通过查找(lookup)机制自动进行反标记(untokenize),将占位符还原为真实的邮箱地址、电话号码和姓名。
因此,真实数据会在Google Sheets 与 Salesforce之间安全传递,但整个过程中,模型从未直接接触这些敏感信息。
这种机制能有效防止智能体无意间记录或处理隐私数据,
同时,开发者还可以基于此定义确定性的安全规则(deterministic security rules),明确规定哪些数据可以在哪些系统之间流动,实现更严格、更可控的数据安全策略。
通过具备文件系统访问能力的代码执行环境,智能体可以在多次运行之间保持状态(persist state)。它能够将中间结果写入文件,以便后续任务继续使用或追踪进度。
例如:
constleads =awaitsalesforce.query({query:'SELECT Id, Email FROM Lead LIMIT 1000'});constcsvData = leads.map(l=>`${l.Id},${l.Email}`).join('\n');awaitfs.writeFile('./workspace/leads.csv', csvData);// Later execution picks up where it left offconstsaved =awaitfs.readFile('./workspace/leads.csv','utf-8');
智能体还可以将自己编写的代码保存为可复用的函数。当智能体为某个任务成功生成并验证了可用的代码后,它可以将这段实现保存下来,作为未来任务的现成工具重复使用。
// In ./skills/save-sheet-as-csv.tsimport*asgdrivefrom'./servers/google-drive';exportasyncfunctionsaveSheetAsCsv(sheetId: string) {constdata =awaitgdrive.getSheet({ sheetId });constcsv = data.map(row=>row.join(',')).join('\n');awaitfs.writeFile(`./workspace/sheet-${sheetId}.csv`, csv);return`./workspace/sheet-${sheetId}.csv`;}// Later, in any agent execution:import{ saveSheetAsCsv }from'./skills/save-sheet-as-csv';constcsvPath =awaitsaveSheetAsCsv('abc123');
这与“技能(Skills)”的概念密切相关。技能是由一组可复用的指令、脚本和资源组成的目录,用于帮助模型在特定任务上获得更好的表现。
当智能体将这些可复用的函数保存下来,并在其中添加一个SKILL.md文件(用于说明该技能的用途、输入输出等),就能形成一个结构化的技能单元(structured skill)。
模型随后可以直接引用并调用这些技能,从而逐渐构建出一个包含高层能力的工具箱。
随着时间推移,智能体会在不断的任务中积累出一整套成熟的“脚手架”(scaffolding),让它在未来的工作中运行得更稳定、更高效。
不过,代码执行本身也引入了一定复杂性。运行由智能体生成的代码,需要一个安全的执行环境,包括沙盒隔离(sandboxing)、资源限制(resource limits)和运行监控(monitoring)等机制。
这些基础设施要求会带来额外的运维成本和安全考量,而这正是直接调用工具(direct tool calls)所不需要的。
因此,采用代码执行模式的好处,需要在收益与实现复杂度之间做出权衡。例如降低 token 成本、减少延迟、提高工具组合灵活性,
MCP 为智能体提供了一个连接多种工具与系统的基础协议。但当连接的服务器过多时,工具定义与结果数据会迅速消耗上下文空间,降低运行效率。
虽然这些问题(上下文管理、工具组合、状态持久化等)看起来很新,但在传统软件工程中早已有成熟的解决方案。
代码执行(Code Execution)将这些经典模式引入智能体架构,让模型能够通过熟悉的编程逻辑更高效地与 MCP 服务器交互。
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |