AI Agent架构演进:集成Skill与MCP构建混合智能体系统
1. 项目缘起与核心目标:从“单打独斗”到“团队协作”的Agent进化
最近在折腾一个Cloud Agent项目,这算是我的一个长期技术实验田。前几篇笔记主要聚焦在Agent的核心心智模型、任务规划与执行循环,以及如何与外部工具(Tools)进行交互。简单来说,之前的版本就像一个能力很强的“全栈工程师”,能自己分析需求、拆解任务、调用各种API去完成工作。但随着场景复杂度的提升,我遇到了瓶颈:这个“全栈工程师”虽然啥都会一点,但面对某些需要深度专业知识的领域时,就显得力不从心,要么效率低下,要么结果不精准。
这让我开始思考下一个阶段的进化方向:如何让我的Agent从一个“全能手”转变为一个“团队领导者”?它不应该事必躬亲,而应该懂得在合适的时机,将特定的专业任务“外包”给更专业的“专家”去处理。这个思路,正好与当前AI Agent领域两个非常热门的概念不谋而合: Skill 和 MCP (Model Context Protocol) 。我的第四篇开发笔记,核心就是探索如何将这两者集成到我的Cloud Agent架构中,并记录下整个项目迭代至今的一些反思与后记。
简单来说,这次集成的目标很明确: 为我的Cloud Agent赋予“技能市场”和“专业外脑”的能力。 Skill让它能动态扩展和组合细粒度的原子能力,比如“发送邮件”、“生成图表”;而MCP则让它能无缝接入一个庞大且不断增长的、由社区维护的专业工具生态,比如直接调用一个专业的代码分析服务、一个联网搜索服务或者一个数据库查询服务。这样一来,Agent的边界被极大地拓宽了,它不再受限于我预先编码的那几十个工具函数,而是可以按需“雇佣”整个互联网上的专业服务来协同工作。
2. 概念厘清:Skill与MCP究竟是什么,为何要集成它们?
在动手之前,我们必须先搞清楚这两个概念的区别与联系,这是设计架构的基础。很多人容易把它们混淆,其实它们解决的是不同层面的问题。
2.1 Skill:Agent的“肌肉记忆”与“组合技”
你可以把 Skill 理解为Agent内置的、可重复使用的“动作”或“微能力”。它通常是一段封装好的、目标明确的代码逻辑。在我的TypeScript项目里,一个Skill可能就是一个异步函数,它接收特定的参数,执行一个明确的操作,并返回结构化的结果。
例如:
-
sendEmailSkill: 接收收件人、主题、正文,调用邮件服务API发送邮件。 -
fetchWebpageSkill: 接收一个URL,抓取网页内容并提取正文。 -
generateChartSkill: 接收一组数据和一个图表类型参数,调用图表库生成图片并返回URL。
Skill的核心特点是“内聚”和“可控” 。它们是我自己编写、测试、部署的,完全在我的代码库和运维体系内。它们的输入输出格式我完全掌控,执行逻辑我也一清二楚。Skill的集成,更像是为Agent的“工具箱”里添加更多好用的、顺手的“扳手”和“螺丝刀”。它的价值在于 能力的模块化与组合 。通过将复杂任务拆解为多个Skill的序列调用(或基于条件的并行调用),Agent可以像搭积木一样构建出复杂的工作流。
2.2 MCP:Agent的“外部专家顾问团”
而 MCP (Model Context Protocol) ,则是一个由Anthropic提出的开放协议,旨在为大语言模型(LLM)或AI Agent定义一种标准化的方式来发现、调用外部服务器(Server)提供的工具(Tools)和资源(Resources)。你可以把MCP Server想象成一个独立的、提供特定领域服务的“专家顾问公司”。
例如:
- 一个
tavily-mcp服务器 :专门提供实时、精准的网络搜索能力。 - 一个
brave-search-mcp服务器 :提供基于Brave搜索引擎的搜索服务,可能侧重隐私或特定数据源。 - 一个
sqlite-mcp服务器 :允许Agent直接对SQLite数据库执行查询、插入等操作。 - 一个
github-mcp服务器 :允许Agent读取仓库文件、查看PR、创建Issue等。
MCP的核心特点是“标准化”和“生态化” 。它通过一套标准的JSON-RPC over STDIO/SSE协议,定义了Server如何向Client(比如我的Agent)宣告自己有哪些工具( tools/list )、这些工具的参数结构是什么(通过JSON Schema描述),以及Client如何调用这些工具( tools/call )。这意味着, 任何实现了MCP协议的Server,我的Agent都可以即插即用,无需为每个服务编写特定的集成代码。 这极大地解放了Agent的能力边界,让它能够利用整个社区构建的专业工具生态。
2.3 为何要同时集成两者?
那么,既然MCP这么强大,为什么还要保留和开发自己的Skill呢?这是一个非常好的架构设计问题。
- 性能与延迟 :Skill是本地或同云服务内的函数调用,延迟极低,可靠性高。对于高频、核心的基础操作(如数据格式转换、内部状态管理),使用Skill是更优选择。
- 安全与可控 :Skill的代码在我自己手里,涉及敏感操作(如访问内部数据库、调用核心业务API)时,用Skill更安全,我可以进行最严格的权限和审计控制。而MCP Server可能由第三方维护,我需要信任其安全性。
- 成本 :调用某些MCP Server可能产生API费用(如搜索服务),而内建的Skill则没有额外成本。
- 功能互补 :Skill更适合封装我业务域内特有的、复杂的业务流程(比如一个需要调用多个内部微服务才能完成的“创建订单”流程)。MCP则更适合接入通用的、标准化的能力(如搜索、代码分析、文件处理)。
因此,一个成熟的Cloud Agent架构,应该是 “内置Skill + 外接MCP”的混合模式 。Skill是Agent的“本职工作技能”,MCP是它的“外援专家库”。Agent需要具备智能的“调度”能力,根据任务类型、成本、安全性要求,决定是动用自身的Skill,还是去咨询MCP专家。
3. 架构设计与实现:构建支持双引擎的Agent内核
明确了概念,接下来就是改造我的Agent核心。我的目标是让Agent的“大脑”(LLM)在规划任务时,能够同时看到我注册的所有Skill和所有已连接的MCP Server提供的工具,并从中选择最合适的一个来执行步骤。
3.1 定义统一的工具抽象层
首先,我需要建立一个统一的抽象,让LLM无需关心一个工具背后是Skill还是MCP Tool。我定义了一个 AgentTool 接口:
interface AgentTool {
name: string; // 工具的唯一标识,如 “send_email” 或 “web_search”
description: string; // 给LLM看的自然语言描述,至关重要
parameters: JsonSchema; // 遵循JSON Schema格式的参数定义
execute: (args: any) => Promise<AgentToolResult>; // 执行方法
}
interface AgentToolResult {
success: boolean;
output: string; // 给LLM看的文本结果
data?: any; // 结构化数据,供后续Skill或逻辑使用
}
无论是本地Skill还是远程MCP Tool,都需要适配成这个 AgentTool 接口。对于Skill,这个适配是直接的,因为Skill函数是我写的,我知道它的输入输出。对于MCP Tool,我需要通过MCP Client与Server通信来获取工具列表,并将每个工具包装成 AgentTool 。
3.2 集成MCP Client:连接外部专家
这是集成的核心难点之一。我需要在我的Agent服务中嵌入一个MCP Client。我选择了使用TypeScript社区中一个正在快速发展的MCP SDK,比如 @modelcontextprotocol/sdk 。
实现步骤与关键细节:
-
Server配置管理 :我设计了一个配置文件(如
mcp-servers.json),用来定义需要连接的MCP Server。每个配置包括Server的名称、启动命令(或SSE URL)、工作目录以及环境变量。[ { "name": "tavily-search", "command": "npx", "args": ["@modelcontextprotocol/server-tavily", "--api-key", "${TAVILY_API_KEY}"], "env": {"TAVILY_API_KEY": "your_key_here"} }, { "name": "filesystem", "command": "npx", "args": ["@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"] } ]注意 :这里的环境变量管理是个坑。千万不要把密钥硬编码在配置文件中!我采用了运行时从环境变量或密钥管理服务读取并注入的方式。
${TAVILY_API_KEY}只是一个占位符,在实际启动Server前,需要用真实的密钥替换。 -
动态启动与连接 :Agent初始化时,会根据配置,动态地使用
child_process.spawn启动这些MCP Server子进程,并通过标准输入输出(stdio)与它们建立JSON-RPC连接。这里要处理好进程的生命周期管理、错误处理和日志收集。 -
工具发现与注册 :连接建立后,Client立即向每个Server发送
tools/list请求。Server会返回它提供的所有工具的元数据,包括名称、描述和详尽的参数JSON Schema。 这一步的“描述”字段质量直接决定了LLM能否正确使用该工具 。幸运的是,大多数主流MCP Server的描述都写得不错。 -
包装与适配 :收到MCP Tool的元数据后,我创建一个对应的
AgentTool实例。其execute方法内部,会构造一个符合MCPtools/call格式的请求,通过Client发送给对应的Server,并异步等待结果返回,最后将结果格式化为AgentToolResult。
3.3 改造任务规划与执行循环
原有的Agent循环是:思考 -> 选择本地Tool -> 执行 -> 观察结果 -> 继续思考。
现在的循环升级为:思考 -> 从统一工具池(Skill + MCP Tools)中选择Tool -> 执行(可能是本地函数调用,也可能是远程RPC) -> 观察结果 -> 继续思考。
关键在于,提供给LLM的“工具列表”提示词(Prompt)现在包含了所有可用的选项。LLM需要根据工具的描述和当前任务上下文,做出最佳选择。例如,当用户问“今天AI领域有什么新闻?”时,LLM应该优先选择 tavily-search MCP工具,而不是我那个可能只能搜索固定知识库的本地 searchSkill 。
4. 实战踩坑:集成过程中的“血泪”经验
理论很美好,但集成过程绝非一帆风顺。下面分享几个让我调试到深夜的典型问题。
4.1 MCP Server的进程管理与资源泄漏
最初我简单地认为,启动子进程后就可以不管了。结果在长时间运行和频繁调用后,出现了内存缓慢增长和僵尸进程。
问题根因 :MCP通信是长连接,需要妥善管理进程。如果Agent因为异常重启,旧的Server进程可能没有被正确终止。此外,Server进程自身的资源管理也可能有问题。
我的解决方案 :
- 封装进程管理器 :我创建了一个
MCPServerManager类,负责所有Server进程的启动、状态维护和关闭。它为每个Server进程维护一个引用,并监听Agent进程的退出信号(SIGINT,SIGTERM),确保在Agent退出时,向所有子进程发送终止信号。 - 实现健康检查与重启 :为每个MCP Client增加简单的心跳或超时检查。如果某个Server长时间无响应或通信失败,管理器会记录错误,并可以选择自动重启该Server(对于非关键Server,我选择将其标记为不可用,并在日志中告警,而不是盲目重启,避免循环崩溃)。
- 资源限制 :在
spawn配置中,可以设置detached: false并妥善处理stdin,stdout,stderr流,确保它们被正确消费和关闭,避免流阻塞。
4.2 工具命名冲突与优先级仲裁
当我同时集成了 tavily-mcp (提供 search 工具) 和 brave-search-mcp (也提供 search 工具) 时,LLM困惑了。两个工具都叫 search ,描述也相似,它该用哪个?
问题根因 :MCP协议本身不强制要求工具全局唯一,不同Server提供同名工具是允许的。这需要Client端(也就是我的Agent)来处理冲突。
我的解决方案 :
- 命名空间化 :在注册工具到统一池时,我采用
server_name::tool_name的格式。例如,将工具名分别改为tavily-search::search和brave-search::search。同时,在提供给LLM的工具描述中,明确加上前缀,如“[Tavily网络搜索] 这是一个使用Tavily API进行联网搜索的工具...”。 - 定义优先级规则 :在架构层面,我可以定义一些简单的规则。例如,在配置文件中为每个Server设置一个
priority字段。当出现同名工具时,只注册优先级最高的那个,或者在工具描述中注明“此为默认搜索工具,另有Brave搜索备用”。更智能的做法是让LLM根据工具描述的细微差别(如“侧重实时新闻”、“侧重技术文档”)来选择,但这对Prompt工程要求较高。我目前采用的是“命名空间+在描述中强调特点”的组合方案。
4.3 错误处理与LLM的“困惑”管理
MCP Server调用可能失败:网络超时、Server内部错误、参数验证失败、达到速率限制等等。这些错误信息如果原封不动地扔给LLM,很可能会让它“困惑”,导致后续规划出错。
问题根因 :MCP tools/call 的响应中包含错误信息,但这些信息是给开发者看的,可能包含堆栈跟踪、内部错误码,对LLM不友好。
我的解决方案 :
- 统一错误格式化 :在
AgentTool的execute方法中,对所有异常进行捕获和格式化。将技术性错误转换为LLM能理解的、包含可操作建议的自然语言描述。// 伪代码 try { result = await mcpClient.callTool(toolName, args); } catch (error) { if (error.message.includes('rate limit')) { return { success: false, output: “搜索服务使用过于频繁,已达到速率限制。建议稍后再试,或者简化您的搜索查询词。” }; } else if (error.message.includes('invalid query')) { return { success: false, output: “搜索查询词格式似乎有问题,请尝试更换更明确的关键词。” }; } else { // 通用错误 return { success: false, output: `调用外部搜索服务时遇到问题:${error.message}。请尝试重新描述您的需求。` }; } } - 提供重试或备选方案 :在某些关键工具调用失败时,我的Agent逻辑可以尝试调用备用的同类工具(如果存在)。例如,Tavily搜索失败后,自动尝试使用Brave搜索。这需要在工具执行层之上,再封装一层简单的故障转移逻辑。
4.4 上下文长度与工具描述的平衡
每个MCP Tool都有一个详细的描述和复杂的参数Schema。当我连接了十几个Server,提供了上百个工具时,把这些描述全部塞进LLM的上下文(Context)里是不现实的,会极大消耗Token并可能影响核心任务的理解。
问题根因 :工具元数据过多,与任务无关的工具描述会形成“噪声”,干扰LLM。
我的解决方案 :
- 动态工具筛选 :不要在每次规划时都提供全部工具列表。我实现了一个简单的“工具路由”机制。在Agent接收到用户请求后,先用一个快速的、廉价的LLM调用(或基于嵌入向量的相似度计算)对用户意图进行初步分析,筛选出最可能相关的5-10个工具,再将这个精简列表提供给负责主任务规划的LLM。这大大减少了上下文负担。
- 描述精炼 :对于某些参数Schema特别复杂的工具,我尝试在不影响理解的前提下,手动简化其提供给LLM的描述,移除一些不常用的高级参数说明,让描述更聚焦核心功能。
- 分层提示 :采用更高级的提示策略,例如,先让LLM思考需要哪一类能力(“我需要搜索网络”),然后再从该类工具中挑选具体的一个。这需要更精细的Prompt设计。
5. 项目后记:反思、权衡与未来展望
这个将Skill与MCP集成到Cloud Agent的项目,与其说是一个功能的完结,不如说是一个新阶段的开始。它彻底改变了Agent的能力范式和设计哲学。以下是我的一些核心反思:
关于技术选型(TypeScript) :坚持使用TypeScript进行全栈开发,在这个项目中体现了巨大价值。MCP协议涉及大量的JSON-RPC消息交互和复杂的数据结构(如JSON Schema)。TypeScript的强类型系统在定义消息接口、工具参数类型时提供了无与伦比的开发体验和安全性,大部分接口错误在编译阶段就被捕获,避免了运行时难以调试的协议错误。配合现代IDE的智能提示,开发效率很高。
关于架构的复杂度 :引入MCP后,系统的复杂度从“单体应用”上升到了“微服务协调”。调试问题变得更具挑战性,一个问题可能出在Agent逻辑、MCP Client、MCP Server进程、或是它们之间的通信上。完善的日志系统变得至关重要。我不得不为不同组件(Agent核心、各个MCP Client)设置不同日志级别和输出通道,并统一使用 requestId 来串联一次用户请求涉及的所有跨进程调用,这才让问题追踪变得可行。
关于“智能”的边界 :集成大量工具后,Agent有时会表现出“工具选择困难症”或“过度依赖工具”。例如,一个简单的算术问题,它可能也会去调用一个计算器MCP工具,而不是直接推理。这提醒我, 工具是能力的延伸,而非思考的替代 。我需要在规划逻辑中增加一些启发式规则,或者通过few-shot示例在Prompt中引导LLM:优先使用内在推理,仅在必要时求助工具。
关于未来方向 :
- Skill与MCP的深度融合 :目前Skill和MCP Tool还是并列关系。未来可以探索让Skill内部也能调用MCP Tool,形成更强大的能力组合。例如,一个“撰写市场分析报告”的Skill,内部可以依次调用“搜索新闻”、“获取股价数据”、“生成图表”等多个MCP工具。
- 工具的动态发现与卸载 :现在的MCP Server列表是静态配置的。理想情况下,Agent应该能从一个“注册中心”动态发现可用的MCP服务,并根据当前任务负载和成本预算,动态加载或卸载它们,实现真正的弹性能力架构。
- 更高级的编排与工作流 :当工具数量爆炸式增长后,简单的“思考-选择-执行”循环可能不够用了。我需要引入更正式的工作流引擎或状态机,来管理涉及多个工具、带有条件分支和循环的复杂任务流程。
- 评估与优化 :需要建立一套评估体系,来衡量引入每个新Skill或MCP Tool后,对Agent任务完成成功率、耗时和成本的实际影响。用数据驱动决策,淘汰效果不佳的工具,优化工具使用策略。
这次集成实践让我深刻体会到,构建一个强大的AI Agent,技术实现只是一半,另一半是对能力边界的清晰认知、对复杂度的有效管理,以及持续不断的迭代和权衡。这条路很长,但每解决一个像“MCP进程泄漏”或“工具冲突”这样的具体问题,都让整个系统离真正的“智能体”更近了一步。
更多推荐


所有评论(0)