1. 这门课到底在解决什么问题
这几年做 Agent 开发的人应该都有一个共同的感受:单机单 Agent 的玩法已经快到头了。无论你用的是 Claude、GPT 还是本地模型,单个 Agent 再聪明,它也就是一个“能干活但只能干自己那份活”的个体。一旦遇到跨系统联动、多角色协作、工具权限隔离、能力复用这些场景,光靠一个 Agent 加一套 Function Call,根本撑不住。
这个项目的标题——DeepAgents + MCP + A2A + Skills 超级多智能体——就是把这一代 Agent 开发的核心拼图凑齐了。它本质上讲的是四层东西:DeepAgents 是编排骨架,MCP 是让 Agent 能调用外部工具的统一协议,A2A 是让 Agent 之间能互相通信的协作协议,Skills 则是把一些可复用的能力沉淀成可加载的技能包。四者合起来,构成一个不仅能单打独斗、还能组队作战的多智能体集群。
我看过很多开发者卡在同一个地方:明明思路都有,模型也能返回正确意图,但一跑到真实环境就垮了。要么是工具越加越多,每个 Agent 的上下文被塞爆;要么是各种 API 对接方式五花八门,维护成本远超收益;要么是做完了多个 Agent,却发现它们之间的通信要靠手工传参。这不是模型能力的问题,是缺少一套规范的架构设计。这门课的价值就在于,把上面四个概念串起来,真正落地到一套可以跑起来的集群架构里。
对于还在用单 Agent 做原型、想升级到多 Agent 生产环境的团队,或者刚接触 Agent 开发、想搞清楚 MCP 和 A2A 到底是什么关系的新手,这个项目能帮你少走很多弯路。下面的内容,我会按照“编排、互通、扩展”这条主线,拆解这套技术栈的核心思路、关键实现、以及我在实际动手过程中踩过的坑。
2. 整体架构拆解:为什么是这四个东西组合在一起
2.1 从单体 Agent 到 Agent 集群的演化逻辑
要理解这套技术栈,就得先理解 Agent 架构的演化路径。第一代 Agent 基本就是个“大模型 + 工具调用”的直筒子:用户提问,模型决定要不要调用某个工具,工具返回结果,模型组织答案。这个模式在单场景、少工具的情况下是可行的,但一旦工具数量超过几十个,模型做工具选择的准确率就会明显下降,每次调用还要把大量工具描述塞进上下文,token 消耗感人。
第二代演化是“Agent + 工作流引擎”。比如 LangGraph 那种带状态机的编排方式——把任务拆成节点,每个节点是一个 Agent 或工具,节点之间用边连接,通过图执行来控制流程。这种方案解决了一部分“谁先谁后”的问题,但仍然偏中心化:工作流的编排逻辑是写死的,Agent 本身没有太多自主决策空间。
到了第三代,就是这套方案的主场:多个智能 Agent 组成一个松耦合的集群。DeepAgents 负责调度和编排,MCP 把外部工具和内部能力统一成标准接口,A2A 让集群内的 Agent 可以像服务一样互相发现和调用,Skills 则允许把常用能力打包成即插即用的模块。这个组合不再假设所有能力都在同一个 Agent 里,而是假设一个集群里每个 Agent 各有专长,彼此可以通过标准协议协作。这是从“单个全能选手”到“一个各司其职的团队”的架构转变。
2.2 四层分工:编排层、工具层、通信层、能力层
我习惯把这套技术栈分成四个层次来看,这样更容易理解各自承担的角色:
| 层次 | 对应技术 | 解决的问题 | 生活化类比 |
|---|---|---|---|
| 编排层 | DeepAgents | 谁来调度、按什么顺序调度、任务如何分配 | 项目总指挥 |
| 工具层 | MCP | Agent 如何标准化地调用外部工具和数据 | USB 统一接口 |
| 通信层 | A2A | Agent 之间如何发现对方、如何传递任务和结果 | 团队内部的对讲机 |
| 能力层 | Skills | 如何把领域知识和方法沉淀为可复用模块 | 员工培训手册 |
分开看都很简单,但真正关键的是怎么把它们组合起来。比如,MCP 和 A2A 容易被混淆:MCP 解决的是 Agent 访问工具的问题,方向是“Agent 向下接入系统”;A2A 解决的是 Agent 之间互访的问题,方向是“Agent 横向连接 Agent”。Skills 则不是独立的网络协议,它更像是知识和流程的封装单位,既可以挂在 MCP 工具上,也可以挂在 A2A 服务能力上。
2.3 选这套组合而不是其他方案的理由
在最初设计架构时,我也对比过另一条路线:直接用 Message Queue + JSON Schema 自研一套 Agent 通信协议,再加一个中心化调度器。后来放弃的原因有三个。
第一,自研协议的兼容成本太高。AGENTS(即 A2A 前身协议)和 MCP 已经是一线厂商在推的开放标准,社区生态、SDK、调试工具都相对成熟。如果自己定义协议,就意味着每一个接入方都要按你的约定来写代码,而用开放标准,等于直接获得了跨厂商的互操作能力。
第二,纯中心化调度会把编排逻辑变成一个大泥球。DeepAgents 这类编排框架的调度模型是“协商式”的——不是所有任务都由一个中心节点拍板,而是 Agent 之间可以互相请求、拒绝、转派,这在任务边界模糊的场景下,比死板的流程引擎灵活得多。
第三,Skills 的价值被严重低估。很多人觉得 Skill 就是“提示词 + 几个工具函数”,但实际上一个设计良好的 Skill 模块应该包含完整的“触发条件、使用步骤、输出格式、错误处理”四要素。它不是零散的工具,而是半个 Agent 的“肌肉记忆”。
3. MCP 实操细节:让 Agent 真正摸到外部世界
3.1 MCP 的本质是“协议”而不是“框架”
不少初学者会把 MCP 理解成一个 SDK 或一个服务框架,其实不对。MCP(Model Context Protocol)的核心是一套 JSON-RPC 风格的消息约定,定义了两类角色:MCP Host 和 MCP Server。Host 是 Agent 运行时,它负责收集用户的意图,按需调用工具;Server 是被动的能力提供者,它把工具、资源、提示词三种能力暴露给 Host。
实操中,你需要理解 MCP 中最关键的三种原语:
- Tools:可被模型调用的函数,执行后返回结构化结果,适合“做一件事”,比如查天气、发邮件、写文件;
- Resources:可被读取的数据,类似 RESTful GET 接口,适合“取数据”,比如读一个配置文件、拉一份报表;
- Prompts:可被复用的提示信息模板,适合“引导模型”,比如一个固定的排障流程的前置指引。
我在第一版实现时只注册了 Tools,后来发现很多只读查询场景用 Resources 更合理——模型可以自主决定“读哪个数据”,而不是硬编码一个工具调用。两者虽然都能实现类似效果,但 Resources 更轻量,不占用工具选择的复杂度。
3.2 用 30 行代码实现一个最小 MCP Server
为了直观理解,我当时用 Python 写了一个最小化的 MCP Server,只暴露一个读取服务器负载的工具。这里贴一下核心代码,关键点我都写在注释里:
from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app = Server("metrics-server") @app.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="get_cpu_load", description="获取当前服务器的 CPU 负载百分比", inputSchema={ "type": "object", "properties": { "mode": { "type": "string", "enum": ["1m", "5m"], "description": "负载统计周期,默认 1m" } } } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: if name == "get_cpu_load": # 实际场景这里可以对接 psutil 或 /proc/loadavg load_value = 12.5 return [TextContent(type="text", text=f"当前CPU负载: {load_value}%")] raise ValueError(f"未知工具: {name}") async def main(): async with stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream) if __name__ == "__main__": import asyncio asyncio.run(main())注意:MCP 的 stdio 传输模式适合本地开发调试,Host 和 Server 在同一台机器上,通过标准输入输出通信。如果要部署到远程,需要切换到 Streamable HTTP 传输模式,并处理鉴权、心跳、流式响应等额外逻辑。
很多第一次接触 MCP 的人会忽略inputSchema的重要性。这个 schema 不是写给人看的,而是给模型的。模型需要根据这个 schema 决定传什么参数。schema 写得越精确,模型调用越不容易出错。尤其要注意字段的description部分,实测表明它比字段名更能影响模型的理解,这也是最容易优化的一环。
3.3 Host 端接入的两种方式和参数选择
MCP Host 端接入时,常见的做法有两种:
- SDK 直连:在 Agent 的代码里直接注册 MCP Server,用
mcp客户端库建立会话; - 配置文件加载:通过
mcp.json这类配置文件声明多个 Server,Host 启动时自动加载。
我推荐生产环境用方案二。把 Server 的地址、传输模式、鉴权信息放到配置中心里,运维人员不需要改 Agent 代码就能增减工具。这相当于把工具接入变成了“配置驱动的热插拔”。
在超时和并发参数的选择上,我的经验值是:单次工具调用超时设为 15-30 秒,长任务用异步任务机制而不是同步等待;Host 到 Server 的连接数,起步设 5-10 个长连接,再根据并发压测结果调整。不要盲目开高并发,很多 MCP Server 是 IO 密集型的,连接数多了反而容易触发背压问题。
3.4 MCP 与安全边界
最后必须强调 MCP 的安全边界问题。MCP Server 是直接暴露给模型的“手和脚”,模型的幻觉如果落到工具调用上,后果可能很严重。所以我在每个 Server 上做了三层防护:
- 输入层校验:所有参数都必须过 JSON Schema 校验和类型白名单;
- 权限层隔离:Host 只给 Server 分配最小权限的凭证,比如只读的数据库账户、限流的 API Key;
- 审计层记录:每次工具调用的参数和结果都写入审计日志,方便回溯异常行为。
此外,涉及写操作的工具,比如发邮件、改数据库、执行 Shell,建议在 Server 端设置人工确认回调。模型可以“建议”执行,但真正落盘必须经过人工确认。否则一旦用户的提示词被注入,Agent 可能做出非预期的高危操作。
4. A2A 互操作:让 Agent 之间像服务一样对接
4.1 A2A 的诞生背景和核心特征
A2A(Agent-to-Agent)协议是在 MCP 已经解决“Agent 调用工具”之后出现的互补协议,解决的是另一个问题:AgentA 需要调用 AgentB 的专业能力,怎么办?在没有 A2A 之前,常见的做法是把 AgentB 封装成 RESTful API,再把它注册成 AgentA 的一个工具。这种方案能用,但有几个问题:无法表达 Agent 的意图和能力边界,没有任务状态的交互定义,回传结果的方式也要自己造轮子。
A2A 的思路是,把每个 Agent 看成一个“自带能力描述的 HTTP 服务”,通过标准化的 JSON 格式对外暴露自己是谁、能干什么、如何连接。核心组件包括:
- Agent Card:一个 JSON 文件,描述 Agent 的名称、描述、能力、通信端点;
- Task 对象:定义一次任务请求的完整生命周期,包括输入、状态、输出;
- Message 与 Artifact:任务过程中的消息流和最终交付物。
用类比来说,MCP 相当于给 Agent 装了各种 USB 外设,A2A 则让 Agent 之间有了通用的“网络协议”,可以相互发现、握手、分包、回传。没有 A2A 时,多 Agent 协作靠“点对点硬编码”;有了 A2A,协作变成了“服务发现 + 标准通信”。
4.2 Agent Card 的推荐最小配置
Agent Card 是 A2A 互操作的起点,相当于服务的“自我介绍”。我先放一个实测过的最小可运行配置,字段解释放在表格里:
{ "name": "data-analyst-agent", "description": "负责数据分析、图表生成与指标解读的专用Agent", "url": "https://agent.example.com/a2a", "version": "1.0.0", "capabilities": { "skills": ["data_analysis", "chart_generation", "report_writing"], "maxConcurrentTasks": 3 }, "defaultInputModes": ["text"], "defaultOutputModes": ["text", "json"], "security": { "authentication": { "schemes": ["bearer"], "credentials": "env:A2A_API_TOKEN" } } }| 字段 | 作用 | 我的建议 |
|---|---|---|
name | Agent 唯一标识 | 用语义化名称,方便其他 Agent 理解 |
description | 能力概述 | 要写得像“招聘 JD”,能让他人判断何时调用你 |
url | A2A 通信端点 | 必须是外网可访问的 HTTPS 地址,内网需借助网关 |
capabilities.skills | 能力标签 | 要与下文 Skills 体系对应,便于路由 |
security | 鉴权配置 | 生产环境绝不能用无鉴权方案,至少要 bearer token |
这里有一个容易踩坑的点:description不是写给人看的,是写给“其他 Agent 的模型”看的。它直接影响下游 Agent 会不会把这个 Agent 纳入自己的工具选择范围。写得含糊,这个 Agent 基本不会被调用到。我初期写的是“提供数据分析功能”,后来改成“当用户需要分析CSV、Excel或数据库指标并生成可视化图表时调用”,调用率明显上升。
4.3 实战中如何暴露一个 Agent 为 A2A 端点
在项目里把 Agent 暴露成 A2A 端点,核心是三步:
- 在 Agent 外层包一个 A2A 兼容的 HTTP 服务,接收
POST请求; - 请求体里的
task对象解析出来后交给内部 Agent 处理; - 轮询或回调方式返回任务状态和最终结果。
如果 Agent 是异步执行的长任务,我建议用“任务创建 + 状态查询”的模式:客户端先POST /task拿到taskId,再定期GET /task/{id}获取状态。不要尝试在一个请求里同步等待长任务完成,否则整个集群的吞吐都会被拖垮。
另一个容易被忽略的是callback机制。A2A 的 spec 允许 Server 端主动向 Client 推送任务状态变更。如果你实现了回调,Client 就不需要轮询。但回调 URL 的接收方也要是公网可访问的服务,否则回调消息发不出去。团队内部部署时,这个回调地址往往是内网 IP,需要在网关层做映射,容易漏配置。
4.4 A2A 和 MCP 的配合方式
在实际架构里,A2A 和 MCP 并不是互斥的,而是前后串联的:一个 Agent 在收到 A2A 任务请求后,内部可能再用 MCP 去调用底层工具。比如数据分析 Agent 接到“生成上周销售报表”的任务,通过 A2A 路由到这个 Agent;这个 Agent 内部再用 MCP 调用数据库查询工具、图表生成工具。所以布线方式是“外部通信走 A2A,内部工具走 MCP” —— 这是最清晰的分层。
我在初期架构里犯过一个错:试图把所有功能都做成 MCP 工具,然后让调度 Agent 直接调用。结果工具越来越多,每个 A2A Agent 变得像一个空的壳子,价值没有体现出来。后来调整思路:MCP 负责“原子操作”,A2A 负责“复合服务”,Skills 负责“标准流程”,三层职责立刻清晰了。
5. Skills 的沉淀与复用:能力不是写在提示词里的
5.1 Skill 到底是什么
Skills 是这四个概念里最容易被低估、也最容易被误解的一个。很多人以为 Skills 就是一段精心编写的 Prompt 模板,真正用起来才发现,远不止如此。
我理解的 Skill 是一个“最小可复用能力单元”,它要包含四部分:触发条件、执行步骤、输出规范、边界约束。触发条件决定“什么情况下该用这个 Skill”;执行步骤是一套有序的操作流程,可能包含多个工具调用和模型推理;输出规范定义了结果的格式,方便下游程序处理;边界约束则限定适用范围,防止 Skill 被用到不合适的地方。
举个例子,一个“SQL 调优” Skill 的触发条件是“用户给出了一个 SQL 查询语句且对执行效率不满意”,执行步骤通常包括:解析 SQL 计划、识别全表扫描、建议索引、生成优化版本、对比两次执行计划。它不只是“请帮我优化 SQL”这句话,而是把调优的整个方法论封装进了可执行流程。
5.2 如何设计一个可扩展的 Skills 目录
在项目里,我把 Skills 统一放在一个skills/目录下,每个 Skill 占一个子目录,用SKILL.md描述元信息,scripts/放可执行脚本或提示词模板。规范的目录结构如下:
skills/ ├──>