去年年初我准备给团队搭一套能自动写行业分析报告、跟踪竞品动态的多 Agent 系统。最先的版本就是一把梭:每个 Agent 都自己写死工具调用、自己维护上下文、自己决定任务步骤。跑通单点功能倒是挺快,可一旦想扩展成五六个 Agent 协作,问题就全冒出来了——A Agent 拿到的结果没法直接喂给 B Agent,工具接入方式五花八门,改一个公共逻辑要同步改四五个文件。那阵子我意识到,问题不在单个 Agent 不够聪明,而在于整个集群缺了三样东西:统一的工具接口标准、Agent 之间的通信协议、以及能力封装的复用机制。所以后来接触 MCP、A2A、Skills 这套组合时,第一反应是"这回总算是把话说明白了"。
这篇文章算是我搭建可编排、可互通、可扩展 Agent 集群的实战笔记。内容会覆盖四个关键词——DeepAgents、MCP、A2A、Skills——分别解决什么问题,它们在什么场景下配合使用,以及在真实搭建过程中我踩过哪些文档里没写清楚的坑。适合已经在用或者准备用 Agent 做实际业务的开发者,哪怕你只是刚听说 MCP 和 Skills,只要照着本文的思路走,也能避开大部分弯路。
1. 这四件套到底各自解决了什么问题:先别急着搭,把分工搞清楚
很多教程把 MCP、A2A、Skills 混在一篇里讲,结果读者看完只知道"它们都很重要",却不知道哪一层管哪件事。我自己的理解是这样:一个能打仗的 Agent 集群,至少要有四个层次——接线层、通信层、能力层、指挥层。DeepAgents 负责指挥层和整体方法论,MCP 管接线,A2A 管通信,Skills 管能力封装。
1.1 MCP 不是协议本身,而是"插口规范"
先说 MCP(Model Context Protocol,模型上下文协议)。很多人第一次接触时都会有个疑问:MCP 到底是软件协议还是硬件协议?其实它的设计思路借用了硬件接口的概念——你可以把 MCP 理解成 AI 世界的 USB-C。
USB 标准本身不定义你插上去的是什么设备,它只定义插口长什么样、数据怎么传输。MCP 也一样,它不关心你的 Agent 背后的模型是谁,也不关心你接入的工具是数据库查询器、浏览器控制还是代码执行器。它规定的是:一个 Agent(MCP Client)怎样和一个工具服务器(MCP Server)建立连接,客户端怎么发请求,服务器怎么返回结果,以及工具的描述信息用什么样的 JSON Schema 格式暴露出来。
具体的传输机制是 JSON-RPC 2.0。默认有两种传输方式:stdio 和 Streamable HTTP。stdio 适合本机直接拉起子进程,比如你在终端里跑一个 Node 脚本,这个脚本通过标准输入输出和 Agent 交互;Streamable HTTP 适合远程部署,比如你在一台服务器上跑一个 MCP Server,Agent 在另一台机器上通过 HTTP 调用。
MCP 三个核心原语:
- 工具(Tools):可被模型调用的函数,输入输出都走 JSON。
- 资源(Resources):暴露数据内容,让模型读取上下文,比如文件内容、数据库记录。
- 提示词模板(Prompts):预定义的提示词模板,在模型和 UI 之间共享 prompt 结构。
我第一次搭的时候只用了 Tools,后来才发现 Resources 和 Prompts 的价值——Resources 适合读取大段不占 token 的上下文,Prompts 适合多个 Agent 共用同一套指令开头。
MCP 最吸引我的一点是:它让"工具接入"从编码问题变成了配置问题。以前接一个新工具,我要为 Agent 写一套函数调用逻辑,模型不认的话还要调格式。现在只要配一个 MCP Server 地址,工具列表自动就暴露出来了。迟到的收益是,几个月后我看到之前同事写的各个工具都在用同样的接入方式,脑子一下就清楚了。
1.2 A2A:Agent 之间的商务合作协议
MCP 解决的是 Agent 连接工具的问题,那 Agent 之间怎么连接?这就是 A2A(Agent-to-Agent)协议登场的地方。
A2A 是 Google 在 2025 年推的开放协议,设计思路很像企业之间的商务对接。两家公司合作之前,先要对齐几个问题:你提供什么服务?你的接口地址是什么?你怎么响应我的请求?A2A 用一份名为agent-discovery.json(也常叫 Agent Card)的 JSON 文件来暴露这些信息,里面声明了 Agent 的能力、通信端点、认证方式。
协议运行按 JSON-RPC 那套思路,核心是任务(Task),会话双方交换的是 Messages,Messages 里可以带文本和 Artifacts(工件,比如生成的文件、图片、结构化数据)。通信是异步的,一端发一个 Task 给另一端,对方可以在后台跑,跑完了再通过回调或者主动推送把结果给回来。
这跟人类的工作方式很像:项目甲方把整个任务交给乙方,乙方拆解执行,中间可能会有进度汇报,最终交付物是工件。A2A 把整条链路搬到协议里,所以 Agent 之间不再是"互相调用函数"的关系,而是"互相委托任务"的关系。
1.3 Skills:能力封装的"配方包"
MCP 把工具标准化了,但工具只是能力的零件。真正干活的时候,Agent 需要一套"配方"——比如"写一篇合格的技术博客",光给一个生成文本的工具远远不够,它还需要知道文章应该分几个部分、语气是什么、要不要加代码示例、元信息怎么填、写作风格如何。这就是 Skills 的定位。
Skills 本质上是把 提示词 + 资源文件 + 脚本 + 工作流程 打包成一个可复用、可分享的单元。
我习惯的 Skill 目录结构是:
skill-name/ ├── SKILL.md # 主文件:声明该技能的描述、能力边界和使用指引 ├── scripts/ # 可执行脚本或 Python 代码 ├── references/ # 参考文档、模板、样式规范 └── assets/ # 静态资源,图片、示例文件等SKILL.md 的开头通常有 YAML frontmatter 声明名称和描述,正文则是给模型看的指令性文档。模型在决定调用这个 Skill 之前,会先读 SKILL.md 来判断"这个技能适不适合当前任务"。所以写 SKILL.md 最重要的是描述准确——你说得太宽泛,模型什么任务都想调用它;说得太窄,该调用时不调用,只能干着急。
Skills 和传统的 Function Calling 最大的区别在于:Function Calling 告诉模型"你能调什么函数",Skills 告诉模型"遇到任务该按什么流程处理"。一个是原子操作,一个是操作流程加上判断逻辑和参考资料的组合。
1.4 DeepAgents:把以上三样组装成指挥系统的方法论
最后说 DeepAgents。在不少框架语境里,DeepAgents 被当成一个具体的框架名,但我更愿意把它理解为一种构建方式:用"深度"而不是"广度"来组织 Agent 集群的方法论。广度指的是堆很多 Agent,各有各的功能;深度指的是每个 Agent 也许只负责一件小事,但这件事它可以做得非常深——深到会自己规划、会调工具、会反思修正、会请求别的 Agent 协助。
DeepAgents 的理念是把大任务解剖成解剖图:一个主 Agent 负责规划拆解,通过 A2A 协议把子任务派分给具备特定 Skills 的专用 Agent,专用 Agent 通过 MCP 调用各自的工具执行,执行完把结果回传给主 Agent。同时,每个 Agent 都可以配备一组 Skills 来增强自己的"深度":不仅会调用工具,还按照 Skill 里固化下来的人类经验去执行。
这四个词连起来,就是一套完整的集群构建思路:
MCP 解决"Agent 怎么用工具",A2A 解决"Agent 怎么找同伴",Skills 解决"Agent 怎么把一件事做精",DeepAgents 决定"谁来做规划、谁来做执行、结果怎么汇总"。
2. 从单体 Agent 到集群的演进路径:分层的组合方式和权衡分析
好,现在四件套的分工清楚了。但真正把它落地到一个项目里,还是需要一个演进过程。我见过不少团队一上来就想搭八个 Agent 的超级集群,结果编排出问题、通信各种卡壳,最后全拆回单体。合理的路径是:先把单体做扎实,再逐渐把"一层一层拆出来"。
2.1 单体 Agent 的三个明显痛点
在做集群之前,我们先复盘单体 Agent 到底痛在哪。
- 工具耦合严重:工具一变,Agent 的调用逻辑就要改。代码里散着各种 try-catch 和特殊格式处理,看着就头疼。
- 能力复用困难:写好的 Agent 逻辑没法给另一个 Agent 用,同样一段"查询竞品官网"的功能,换个 Agent 就得复制一份代码。
- 上下文相互污染:一个 Agent 里做多件事,上下文长了之后,指令容易被无关信息干扰,模型表现也不稳定。
尤其在模型参数量各异的情况下,你想用一个通用能力给不同模型(Claude、GPT 等),单体方案的迁移成本很高。这时候,把能力拆到 MCP Servers 和 Skills 里就显得特别值得——它们是模型无关的。
2.2 四层架构:工具层、能力层、通信层、编排层
我最终用的架构是四层,每一层的边界都尽量干净:
编排层(DeepAgents):主控 Agent、任务规划、状态监控 通信层(A2A / AgentCard):Agent 之间的路由、任务派发 能力层(Skills):一系列可复用的能力包 工具层(MCP Servers):各类外部服务接口对照表可以帮助理解每层的变化频率和职责:
| 层次 | 解决的核心问题 | 代表技术 | 变更频繁度 |
|---|---|---|---|
| 工具层 | 如何打通外部系统 | MCP Server | 高,工具集常加常改 |
| 能力层 | 如何把任务做精做专 | Skills(SKILL.md) | 中,流程优化迭代 |
| 通信层 | Agent 之间如何协作 | A2A、Agent Card | 低,协议相对稳定 |
| 编排层 | 谁决策、谁执行、谁收尾 | DeepAgents 框架/自定义编排 | 中,策略调整需求多 |
这个分层的好处是:新增一个工具,我只需要写一个 MCP Server,不需要动 Agent 本身的代码;优化一个流程,我改对应的 Skill 文件就行;新加一个 Agent 角色,只要初始化它的能力集,再在通信层注册自己的 Agent Card 就可以接入集群。
2.3 什么场景适合上集群,什么场景单体就够
不是所有项目都适合搞多 Agent 集群。我自己的判断依据:
- 单体够用:单 Agent、单工具、任务线性,比如一个内部问答机器人,知识库加一个检索工具就完事。这时候引入 MCP 和 Skills 会带来不必要的复杂度。
- 值得上集群:任务链条长(调研 → 分析 → 写报告 → 发通知),或者同时依赖多个垂直能力(数据库查询 + 网页搜索 + 代码执行 + 第三方 API),或者需要并行执行多个子任务时才真正受益。
- MCP 是底线:不管上不上集群,只要你的 Agent 需要接多个工具,建议都用 MCP 标准接,管线规范化比追求"全自动"更重要。
- Skills 是复用刚需:只要存在"同一流程要跑多遍"的场景,就值得把流程封装成 Skill。比如"舆情分析""周报生成""代码评审",一次封装,到处调用。
- A2A 是最后一步:等你有两个以上互相需要协作的 Agent,再引入 A2A 不迟。一上来就通信协议满天飞,不如先把单个 Agent 能力打磨好。
3. 实操记录:搭建一个最小可运行、可编排、可扩展的 Agent 集群
讲完理论部分,我把我搭最小系统的全过程记录下来。之所以叫"最小",是因为这一套跑通之后,后面所有扩展都只需要往框架里"加料",不需要动骨架。
3.1 环境准备与选型时的取舍
我建议起步阶段选 Python 加 FastAPI 这类轻量框架做 MCP Agent 宿主,因为生态比较完善;Node 生态也不错,尤其在 Web 场景。下面是我的最小依赖清单:
- Python 3.11+(异步支持好,类型标注完善)
- FastAPI/Uvicorn(提供 HTTP 服务,给 Agent 集群一个基础 Web 入口)
- mcp 官方 Python SDK(用来快速实现 MCP Server 和 Client)
- Skills 目录约定:每个 Skill 一个文件夹,SKILL.md 负责描述,scripts 目录放可执行脚本
- A2A 只需要准备一份 agent-discovery.json 和一个接收 Task 的 HTTP 端点
注意:这里选 FastAPI 不是为了做 Web 应用,而是让集群每个 Agent 都有独立的 HTTP 入口,方便后续接 A2A 通信。如果你只用 stdio 模式做本地任务,其实用 asyncio 就够了;我仍然建议一开始就留 HTTP 端点,插件生态的灵活性会高很多。
3.2 注册一个真实可用的 MCP Server:配置、调试与验证
以我常用的一个"网页搜索" MCP Server 为例。创建一个search_server.py,核心代码逻辑如下(展示结构思路,实际 SDK 包名以官方文档为准):
from mcp.server import Server from mcp.server.models import InitializationOptions import mcp.types as types import httpx server = Server("web-search") @server.list_tools() async def list_tools(): return [ types.Tool( name="web_search", description="搜索网页,返回前 n 条结果(标题、链接、摘要)。适合查找最新信息。", inputSchema={ "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"}, "max_results": {"type": "number", "description": "返回结果条数"}, }, "required": ["query"], }, ) ] @server.call_tool() async def call_tool(name: str, arguments: dict): if name == "web_search": async with httpx.AsyncClient() as client: # 实际对接搜索 API,这里省略参数细节 resp = await client.get("https://api.example.com/search", params={"q": arguments["query"]}) results = resp.json().get("results", [])[:arguments.get("max_results", 5)] return [types.TextContent(type="text", text=str(results))] raise ValueError(f"Unknown tool: {name}") # 入口:可选 stdio 或 HTTP 方式 if __name__ == "__main__": from mcp.server.stdio import stdio_server import asyncio async def main(): async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, InitializationOptions(server_name="web-search", server_version="0.1.0")) asyncio.run(main())写完代码,本地用 stdio 模式直接跑起来,配到你的 Agent 里,然后让 Agent 调用web_search试一下。如果 Agent 能拿回结果,说明工具链路通了。
这段代码里最容易忽略的是工具 description。很多教程的示例 description 都很敷衍,比如"搜索工具"四个字。但实际调用中,模型是靠描述来决定要不要调这个工具的,描述越具体,模型的调用准确率越高。我用过一段时间的感受是:描述里写清楚"适合什么场景、不适合什么场景、参数边界",比在代码里写一百行防御逻辑都管用。
3.3 把一个高频工作流封装成 Skill:目录、Frotmatter 和内容要点
假设我要把"竞品动态日报"这个工作流交付给团队。我不希望每次让 Agent 做日报时都从零讲一遍流程,于是把它做成一个 Skill。
Skill 目录结构示例:
competitor-daily/ ├── SKILL.md ├── scripts/ │ └── collect_updates.py └── references/ └── report_template.mdSKILL.md 的开头:
--- name: competitor-daily description: 生成竞品动态日报。输入竞品品牌列表,输出按日期整理的动态汇总,标记重点变化。 --- # 竞品动态日报生成流程 1. 读取 references/report_template.md,确认日报结构。 2. 对每个竞品品牌,调用 web_search 查询当天动态。 3. 通过 collect_updates.py 对搜索结果做去重和优先级排序。 4. 按模板生成日报,重点信息放到醒目位置。当主 Agent 收到"给我来一份今天的竞品日报"这类任务时,它读 SKILL.md,判断这是不是该用这个 Skill,然后按里面写好的流程一步一步执行。这个机制的价值在于,流程优化的对象从对话变成了文件——你调整流程,所有人的 Agent 都会用上,不用到处改提示词。
3.4 用 A2A 协议声明集群里的 Agent 能力:Agent Card 示例
接下来是 A2A 部分。为了让 Agent A 能发现 Agent B,并且知道它可以干什么,我给每个 Agent 准备了一个agent-discovery.json:
{ "name": "research-agent", "description": "擅长行业资料检索、数据整理与归类,返回结构化调研结果。", "url": "http://localhost:8011/a2a", "capabilities": { "task": { "supported": true, "streaming": true } }, "skills": [ "web-search", "competitor-daily" ] }这里有几个值得注意的细节。
第一,description要像写简历一样认真写。A2A 的路由和编排都靠它来判断把任务派给谁,写得含糊,编排层就会乱派。第二,skills字段列出 Agent 可用的 Skills,这不是协议必须的,但加上之后,编排层能更快知道这个 Agent 擅长什么。第三,capabilities.task.streaming表示支持流式进度更新,如果你的任务比较长,建议开启,方便上游 Agent 实时掌握进度。
有了这份文件,Agent 之间的协作关系就可以描述成下面的流程:
- 主 Agent 收到用户任务 → 拆解为子任务
- 读取各 Agent 的
agent-discovery.json→ 判断派给谁 - 通过 HTTP 调用目标 Agent 的 A2A Task 端点 → 提交子任务
- 目标 Agent 执行自己的流程(调用 MCP / 读 Skills)
- 完成后返回结构化结果 → 主 Agent 汇总输出
4. 真实踩坑记录:来自实践链路中的配置问题与解决思路
理论再通,实操该踩的坑一个都不会少。这一节我把我自己的失败经验一条条列出来,每一条都是反复折腾过才醒悟的。
4.1 工具描述写得像废话,Agent 根本不调你的工具
我第一次写 MCP Server 时,工具描述就一句话:"搜索工具"。结果给 Agent 配置好之后,任我怎么提问它都不调。看日志才发现模型根本不认为"搜索工具"适合回答"今天有什么科技新闻"这种问题——它觉得自己的内部知识够了,不需要外部工具。
后来我把描述改成下面这样:
搜索互联网并返回结果列表。当你需要获取最新信息、不熟悉的话题、 或者回答可能超出训练数据截止时间的问题时,务必调用此工具。 输入 query 为搜索关键词,max_results 为返回条数(默认 5)。改了描述之后调用率立刻上来了。经验总结:MCP 工具的 description 应该包含"什么时候用、什么时候不用"的触发条件。只写功能不写场景,等于没写。
4.2 Skill 的边界画太宽,主 Agent 反而变傻
还有一次,我把"生成日报"这个 Skill 的描述写成"生成各种报告"。结果主 Agent 遇到任何跟"报告"沾边的任务都优先调这个 Skill,哪怕用户只是要求"帮我算一下数据"。因为 Skill 描述太宽,模型无法判断"这个任务不完全匹配",于是强行套用流程,输出结果反而更差。
正确做法是:把描述写窄、"怎么办"写清楚、放"什么时候不要用"。
那次调整后,我的 SKILL.md 描述变成了:
--- name: competitor-daily description: 按模板生成竞品动态日报,输入竞品品牌列表。仅适用于每日固定格式的竞品动态汇总; 不适合一般的数据分析或独立报告撰写。 ---4.3 A2A 协议跑通了不代表编排逻辑就稳了:跨框架路由才是真问题
A2A 协议本身我按照规范建了 Task 端点,Agent 之间的消息也能传递。但以为万事大吉之后,我遇到一个更隐蔽的问题:上游 Agent 拿到下游 Agent 的返回值,不知道下一步该做什么。
比如我让 A 去调研市场,然后让 B 基于调研结果写报告。A 的返回值里有文本、有链接、有表格,B 拿到之后一通乱读,输出和报告要求的格式完全对不上。
后来发现关键在于:下游 Agent 返回的结果结构必须事先约定。A2A 协议只管消息怎么传,不管消息内容长什么样。两个 Agent 之间必须提前约定好工件(Artifact)的格式——调研 Agent 返回的必须是结构化的 JSON,带summary、sources、key_points字段,写报告 Agent 只需要这几个字段就能开工。
4.4 本地 stdio 与远程 Streamable HTTP 的选择:影响权限和配网决策
MCP 的传输方式有时会直接影响 Agent 能做什么、不能做什么。最开始我图方便全用 stdio,但后来一个工具需要给远程集群共享,就发现 stdio 只能跑在本地机器,远程访问走权限配置很麻烦。于是拆了一半服务改成 Streamable HTTP 模式。
我的建议:
- 单机调试用 stdio,零网络开销,不涉及跨机器权限。
- 需要跨机器共享、或有多个 Agent 都在用的通用工具,用 Streamable HTTP,并通过 API Key 或 OAuth 做认证。
安全提示:无论用哪种传输方式,MCP Server 一旦暴露到网络,就要做好认证。别图省事把工具裸跑在公网上,数据泄露风险太大。
5. 集群编排的实战心得:怎么让 Agent 集群"听话"地干活
最后再加上几个编排层面的心得,这部分直接决定集群好不好用。
5.1 编排器用什么模式:规划器-执行器 vs 管道模式
我用过两种编排模式。一种是规划器-执行器模式:主 Agent 拿到任务后,自己规划子任务清单,再逐个派发;另一种是管道模式:一个 Agent 的输出直接作为下一个 Agent 的输入,任务流是固定的,比如"搜索 → 总结 → 生成报告 → 发送"。
规划器模式灵活,适合开放性的任务;管道模式稳定,适合流程固定的业务场景。我的建议是小范围跑通优先用管道模式——先把端到端的数据流打通,再去追求动态规划。
5.2 任务派发的"往回看"机制:结果不够好时怎么重派
最开始我的编排器很线性:主 Agent 把任务派下去,收到结果就直接进入下一步。后来发现下游 Agent 的结果经常缺失细节或格式不对,但上游 Agent 没有"返回重做"的机制。
之后我加了一层任务校验逻辑——主 Agent 收到下游返回的 Artifact 后,先对比任务要求检查完整度,不合格就带着反馈重新派一次任务,最多重试两轮。这个"再加工"机制让整体输出的正确率明显提升,代价是多消耗一些调用时长,整体看还是值得的。
5.3 观测与调优:日志不只是排障用,还是能力优化的依据
集群跑起来之后,最容易被忽视的就是观测。我维护了一套很简单的日志方案,每次任务执行记录:谁发的任务、发给谁、调用哪些 MCP 工具、哪个 Skill 被触发、耗时多少、返回结果是否达标。整理成表之后,就能看出哪些 Agent 经常被派到不合适的任务、哪些 Skill 的触发率太低、哪些 MCP 工具调用失败了。
观测数据就是集群调优的起点。比如我的一个"信息提取" Skill 触发率很低,打开日志发现,主 Agent 总是更信任内置能力而不去调 Skill。后来我分析了原因:这个 Skill 在 SKILL.md 里的触发条件和主 Agent 的任务描述匹配度不够。调整描述后触发率正常。没有日志,这种问题我根本意识不到是描述匹配的问题。
5.4 扩展性设计:加一个新的 Agent 到什么成本
最后聊一下扩展性。我搭这套集群的初衷之一就是"加人方便"。新加一个 Agent 的成本仔细算下来,主要落在四件事上:
- 决定它负责什么领域、定义好 Agent Card 里的 description。
- 把要用的工具接成 MCP Server,或者复用已有的。
- 把要执行的流程封装成 Skill。
- 在编排器里登记新 Agent 的能力范围。
如果以上四步都走标准流程,一个 Agent 从零到接入集群大概一两天。相比之下,之前单体架构加一个新工具都要改主代码。用这套分层的好处就是,每一次扩展都只需要动对应层的东西,不用推倒重来。
我自己实际用下来最大的体会是:DeepAgents + MCP + A2A + Skills 这套组合,真正解决的并不是"如何让 Agent 更聪明"这个宏大的问题,而是让"一批中等聪明的 Agent 凑在一起也能稳定产出"这个工程问题。它把 Agent 集群从"手工作坊"变成了"标准化生产线"。如果你刚入门,我建议的顺序是:先老老实实把 MCP 的工具接入跑通,再做一个简单的 Skill 封装自己的高频流程,等有两个以上 Agent 协作,再去碰 A2A。每一步都打通了,你自然知道自己还需要什么,而不是被各种概念拽着跑。