
做多智能体系统到现在我最大的一个感受是协议选对了后面能省一万个心眼。MCP 和 A2A 这两个名字这两年出现频率极高但真正把它们放进一个系统里协同工作的人其实还不多。大多数教程要么单独讲 MCP 怎么接工具要么单独讲 A2A 怎么让两个 Agent 对话很少有一篇文章从设计角度告诉你它们俩到底什么关系、边界画在哪、系统里该怎么编排。这是《使用 MCP 和 A2A 设计多智能体 AI 系统》系列的第八篇。前七篇我们把环境搭建、单 Agent 工具接入、MCP Server 开发、A2A 握手流程都过了一遍这一篇更像是“合拢”——把散落的零件装进一套完整的多智能体框架里重点解决三件事第一MCP 和 A2A 各自的定位怎么区分第二多智能体协作的拓扑与任务流转怎么设计第三接入过程中那些文档里不会写、但实际一定会踩的坑。适合正在做 Agent 编排、需要把多个 AI 服务或企业系统接在一起的同学参考尤其适合那种“手上有两三个智能体但不知道怎么让它们正经协作”的中间状态项目。1. MCP 与 A2A先分清“接工具”和“接智能体”很多人把 MCP 和 A2A 放在一起聊以为它们是竞争关系甚至有人觉得“既然 A2A 更强是不是可以替代 MCP”。这是我这段时间见过最多的误解。真实情况是这俩协议解决的根本不是同一个问题正确理解应该是——MCP 让 AI 能调用工具A2A 让 AI 能被其他 AI 调用。1.1 MCP把外部能力变成模型的“USB 接口”MCP 全称 Model Context Protocol2024 年底由 Anthropic 开源。它解决的问题非常具体以前每个大模型要接一个数据库、接一个 API、接一套业务系统都要单独写适配层模型换了全得重来。MCP 把这个过程标准化了相当于给模型装了一个 USB 接口插上哪个 MCP Server模型就能用哪个工具。MCP 的架构是经典的 Client-Server 模型Host比如你写的 Agent、IDE、Claude Desktop作为客户端MCP Server 负责暴露能力。能力被抽象成三种原语Tools 是可执行动作Resources 是上下文数据Prompts 是预置的提示模板。传输层支持 stdio 和 Streamable HTTP 两种方式前者适合本地进程内嵌后者适合跨机器调用。生态里现成的 Server 非常多GitHub、Playwright、数据库连接器、图数据库、Slack 都有官方或社区实现。实际项目里MCP 的价值不在“协议多华丽”而在“生态复用”。我在一个项目里要给 Agent 接浏览器自动化当时在 browser-use 和 Playwright MCP 之间纠结了一段时间。两者的差别很典型browser-use 更像“让 Agent 自己看屏幕、自己决定操作”适合强交互推理场景Playwright MCP 是把定位器、点击、填表这些事情封装成确定性的工具调用适合流程稳定、需要可控的自动化。这种选型问题如果没有 MCP 这种统一协议每个项目都要重新造一遍轮子。1.2 A2A给智能体之间建立“普通话”A2A 全称 Agent2Agent2025 年 4 月由 Google 提出后来捐赠给了 Linux 基金会。它的目标不是替代 MCP而是解决 MCP 管不到的另一层问题当一个 Agent 需要把任务交给另一个 Agent 时两边怎么发现彼此、怎么建立任务、怎么传递中间状态、怎么交付结果。A2A 里有几个关键概念。Agent Card 是每个智能体的“名册信息”声明它叫什么、能做什么、支持什么输入输出格式、有什么安全认证方式。Task 是任务单元有完整生命周期。Message 是流式事件负责把文本、状态更新、产物更新实时推给调用方。Transport 层用的是 JSON-RPC 2.0 over HTTP所以对现有后端体系非常友好不是那种“必须全家桶换掉”的协议。用生活化的比喻讲MCP 是给每个员工配了统一型号的工具箱插上就能用A2A 是公司部门之间的协作流程——你发一封任务邮件Task对方回复进度Message最后提交成果物Artifact。一个管“人怎么用工具”一个管“人怎么和人协作”两者天然互补。1.3 设计原则别把两个协议用反了根据我这几个项目的实践有一个边界值得反复强调单智能体内部接工具走 MCP智能体之间协作走 A2A。不要在单个 Agent 内部用 A2A 去调小工具那属于把简单问题复杂化也不要在两个 Agent 之间用 MCP 强行互相调用MCP 的定位是“模型拿工具”不是“智能体对等协作”。我还见过一种错误的做法把所有业务能力都揉进一个 Agent然后同时暴露 MCP Server 和 A2A Server 给外部用。这样不是不行但很容易让 Agent Card 描述变得很虚外部智能体根本判断不了“你到底擅长什么”。我后面会展开讲这一点多智能体系统里最强的约束不是模型能力而是服务边界的清晰度。2. 设计多智能体系统先定拓扑再谈协议协议是骨架拓扑是血肉。真正落地的多智能体系统第一步不是写代码而是想清楚智能体之间是什么关系、流量怎么走、谁能调用谁。A2A 和 MCP 都只是实现手段拓扑没定协议选得再对也会拧巴。2.1 三种主流拓扑星型、总线型、点对点多智能体协作的拓扑我实际用过的主要有三种各有明确的适用场景。星型拓扑是最常见也是最稳妥的起步方案。一个中央编排器或者叫路由器、调度员统一接收用户请求根据任务内容决定调用哪个子智能体。子智能体之间不直接通信所有编排逻辑都集中在中央节点。优点是排查问题非常舒服——你只需要盯一条调用链日志能按序串起来。缺点也明显中央节点会成为瓶颈而且一旦编排器挂了整个系统就瘫了。适合初期项目或者智能体数量不超过五个的场景。总线型拓扑是我自己比较喜欢的进阶方案。智能体之间不直接调用而是通过消息总线发布和订阅事件。比如“订单已创建”这个事件发到总线上报价智能体和库存智能体各自订阅、各自处理。这样耦合度极低加新智能体几乎不影响老系统。代价是事件驱动的系统调试困难你要同时看多个消费者的日志才能还原完整链路。适合业务中已经有事件流基础设施的团队。点对点拓扑是 A2A 最“原生”的形态。每个智能体通过 Agent Card 暴露自己的能力其他智能体发现 Card 后直接发起请求。好处是延迟最低、没有单点坏处是调用关系会变成网状时间一长没人说得清完整链路。适合智能体数量少但交互频繁的场景比如一个财务分析 Agent 和一个数据查询 Agent 之间高频配合。2.2 用 Agent Card 做能力发现与路由A2A 的 Agent Card 是我认为整个协议里最有价值的机制。它相当于每个智能体的“服务契约”外部智能体拿到这张卡就能判断要不要跟它协作。设计 Agent Card 时最容易犯的错是把它写成营销文案。我看到不少 Card 里写“擅长智能处理各种业务需求”这种描述在路由阶段等于没说。正确的做法是用可验证的功能词描述能力并且严格控制 skill 的粒度。比如“查询实时库存支持 SKU 批查单次最多 100 条”“生成标准格式报价单支持人民币/美元计价”。这样路由层无论是用规则还是用 LLM 去解析都能得到清晰的决策依据。路由方式上前期我建议先用规则匹配按 skill 名称和描述里的关键词走 if-else。等智能体数量多到规则爆炸再引入一个“路由器 Agent”让它读取所有 Agent Card结合用户请求做路由决策。这里有个细节路由器 Agent 读的是 Agent Card 的元信息不是真实业务数据信息密度可控不用担心上下文被撑爆。2.3 任务编排一次请求怎么流转有了拓扑和路由还要把任务流转设计清楚。我用一个具体例子来说明用户问“SKU 10086 的库存够不够发 500 件货能不能 48 小时内交付”。这时订单助手 Agent 收到请求自己先通过 MCP 调用订单系统的查询工具拿到订单约束。然后它发现库存信息不在自己的能力范围内就通过 A2A 向库存 Agent 发起一个 task请求查询 SKU 10086 的实时库存。库存 Agent 收到任务后内部再通过 MCP 调用库存数据库的查询工具把结果包装成 Artifact 返回。订单助手拿到结果后自己汇总答案回给用户。这个例子里MCP 和 A2A 的分工一目了然订单助手内部接订单系统的工具走 MCP库存 Agent 内部接库存系统的工具也走 MCP两个 Agent 之间交换任务和结果走 A2A。每一层都只做自己擅长的事链路短、责任清晰。3. 协议细节与接口设计要点拓扑定了就开始抠协议细节。这个环节最磨人因为 MCP 和 A2A 的规范看起来不复杂真正对接的时候却是细节决定成败。3.1 MCP 三原语Tools、Resources、PromptsMCP 规范里反复强调三种能力但实际项目里很多人只用到了 Tools另外两个没有发挥价值。Tools 是最直观的定义工具名、描述参数、执行动作。Agent 判断“要不要用这个工具”完全依赖描述质量所以工具描述里除了写功能最好把边界条件和返回结构也写清楚。比如“查询库存入参 sku 字符串必填返回 list字段包含 sku、stock、warehouse、updated_at”。描述里信息越实模型误调用的概率越低。Resources 用于把数据注入上下文适合“拿一份资料给模型看”的场景。它用 URI 标识支持 list 和 read。这里有个实操建议千万不要把 Resources 当作数据库查询接口用。它适合读取静态或准静态的文档、配置、报表快照不适合高频动态查询否则既浪费上下文窗口又会把 Server 拖垮。动态查数据请老老实实写 Tools。Prompts 是预置提示词模板适合固定流程的多轮对话比如“客服开场白”“每周周报生成模板”。把模板下沉到 MCP ServerHost 那边就不需要每个客户端重复维护一遍。实际用起来Prompts 对 MCP Client 端的一致性帮助很大但对 Agent 自身的价值相对有限因为 Agent 本身就有 System Prompt 机制两块容易重叠。设计时建议按“谁产出内容”划分模板内容偶尔变动的放 MCP需要动态组织上下文的放 Agent 内部。3.2 A2A 的任务生命周期与事件流A2A 的任务生命周期比大多数人预想的要简单但正是那些状态转换边缘最容易出现协作事故。完整状态包括 submitted、working、input-required、completed、canceled、failed。我的经验是调用方写代码时一定要覆盖全部状态尤其是 failed 和 input-required这两个最容易遗漏。任务提交后如果目标 Agent 需要用户补充信息会返回 input-required 状态并附上需要的信息项。这实际上是 A2A 里最优雅的设计它把“人机交互”嵌入了智能体协作链路。在订单确认、风险审核这种必须人工拍板的场景里input-required 比让 Agent 自己决策安全得多。流式事件是另一个容易忽略的点。A2A 支持 TextEvent文本增量、StatusUpdateEvent状态变化和 TaskArtifactUpdateEvent产物更新。调用方订阅这些事件才能实时感知任务进度否则用户那边只能干等到 completed。我在企业落地时见过一个案例任务实际上要跑五分钟但系统没有任何中间反馈用户以为卡死了体验极差。引入流式事件后每做完一个阶段推一条状态用户侧体验立刻不一样。3.3 身份认证与数据边界多智能体系统的安全设计比单 Agent 系统难一个数量级。因为调用链变长了信任边界也模糊了。必须在架构阶段就把“谁有权调谁”定义清楚否则后期补安全基本要重构。MCP 层面工具暴露时要有鉴权意识。尤其是数据库 MCP、支付类工具不能裸奔。AWS Lambda、Azure Functions 或者服务网关都能做一层 token 校验不要让任何 MCP Server 直接暴露在不可信网络里。A2A 层面Agent Card 里可以声明 security 机制。目前主流用 API Key 或 OAuth 两种。Agent 互调时调用方要携带自己的身份凭证目标 Agent 校验通过后才响应。这里有一个纵深防御思维就算智能体 A 合法也不能让 A 绕过智能体 B 直接访问 B 背后的数据库或内部系统。B 的 MCP Server 只信任 BA 想拿数据只能通过 B 的 A2A 接口。坚持这个原则就能避免“一个 Agent 被攻破全系统数据裸奔”的情况。3.4 错误处理与超时多智能体系统的错误处理本质上不是“try catch”而是“约定”。因为 MCP 工具调用失败、A2A 任务失败、网络超时三者失败语义完全不同。我之前踩过一个大坑把 A2A 的 task failed 当成了工具级异常导致任务重试时重复扣款。现在我的做法是三层处理最内层是 MCP 工具调用失败就返回结构化的错误信息带上错误码和可重试标记中间层是 A2A 任务层失败要区分“目标 Agent 明确拒绝”和“目标 Agent 失联”前者不重试后者可以带着 retry-after 语义重试最外层是编排层超时时间按任务类型分级快速查询类给 10 秒重计算类给 120 秒超过时间直接返回“任务超时稍后查询结果”而不是干等。4. 实操把 MCP 服务接入 A2A 编排层理论讲完看一个可落地的简化示例。我用 Python FastAPI 搭一个库存 Agent它对外提供 A2A 接口对内通过 MCP 协议访问一个库存查询服务。这样你就能直观看到 MCP 和 A2A 在同一个进程里是怎么配合的。需要说明以下代码是教学简化版本主要展示链路结构。生产环境中建议用官方 SDKMCP Python SDK、A2A SDK减少重复劳动但原理完全一致。4.1 环境与依赖基础依赖就三块mcp 库做 MCP 客户端、a2a 相关的客户端或自己实现 JSON-RPC 传输、fastapi uvicorn 做 HTTP 服务。如果目标 Agent 与 MCP Server 在同一台机器MCP 走 stdio 即可如果分开部署记得用 Streamable HTTP配置好鉴权。pip install mcp fastapi uvicorn a2a-sdk注意a2a-sdk 的包名和 API 会随版本演进以你安装时的官方文档为准。核心的传输协议是稳定的后续改动不影响整体设计。4.2 定义一个库存 Agent 的 Agent CardAgent Card 是 A2A 协作的入口。文件放服务根目录路径按规范走/.well-known/agent.json。下面这个 Card 声明了查询库存这一个 skill{ name: inventory-agent, description: 查询实时库存、存放地与库存水位, url: http://inventory-agent:8000, skills: [ { id: stock_query, name: 实时库存查询, description: 按 SKU 列表查询实时库存返回 sku、stock、warehouse、updated_at, tags: [stock, inventory, sku], examples: [查一下 SKU 10086 的库存] } ], security: { scheme: api_key, credentials: Bearer token } }这里有三个实践要点。第一description 一定要写清楚入参和出参结构外部路由 Agent 靠这几句话做匹配。第二tags 别乱填路由搜索的边缘场景全靠标签兜底。第三security 要真实生效不要只在 Card 里声明。我见过 Card 写了 api_key 但服务端根本不校验的这不叫设计错误叫安全事故。4.3 内部通过 MCP Client 调用库存服务库存 Agent 内部依赖一个 MCP Server它封装了真实的库存数据库访问。Agent 进程作为 MCP Client 连上它。这里用 MCP Python SDK 的 stdio 方式连接本地 Server示意代码如下from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def call_stock_mcp(sku: str): params StdioServerParameters( commandpython, args[stock_mcp_server.py] ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.call_tool( query_stock, {sku: sku} ) return result.content调用方不用关心 MCP Server 后面是 SQLite 还是 PostgreSQL不用关心鉴权细节只要拿到标准化的工具返回。这就是 MCP 对 Agent 内部架构的价值——把基础设施和业务智能体彻底解耦。4.4 暴露 A2A 端点并处理任务现在把 MCP 查询能力包装成 A2A 可调用的任务接口。最核心的端点是POST /task/send接收来自其他智能体的任务请求。流程是解析请求 → 校验 Agent Card 声明的能力 → 调 MCP 工具 → 返回 Task 响应。from fastapi import FastAPI, Request, HTTPException app FastAPI() app.post(/task/send) async def task_send(req: Request): body await req.json() task_id body.get(taskId) message body.get(message, {}) skill message.get(skillId) if skill ! stock_query: raise HTTPException(status_code400, detailunsupported skill) sku message.get(params, {}).get(sku) if not sku: # 缺参数时返回 input-required让调用方补信息 return { taskId: task_id, status: input-required, message: {role: agent, parts: [{text: 缺少 sku 参数}]} } stock_data await call_stock_mcp(sku) return { taskId: task_id, status: completed, artifacts: [{name: stock_result, parts: [{text: str(stock_data)}]}] }这段代码里最值得学习的是 input-required 分支。任务缺参数时不要直接返回 failed而是告诉调用方“我需要什么”让调用方补充后再来。这在真实多智能体协作里比失败重试体面得多也更接近人与人之间的协作方式。4.5 调用方怎么发起任务另一个 Agent比如订单助手调用库存 Agent 时核心逻辑是提交任务、订阅状态、拿到产物import httpx async def ask_inventory(sku: str): payload { taskId: ftask-{uuid4()}, message: { role: user, parts: [{text: f查询 {sku} 库存}], skillId: stock_query, params: {sku: sku} } } async with httpx.AsyncClient() as client: resp await client.post(http://inventory-agent:8000/task/send, jsonpayload) body resp.json() if body[status] completed: return body[artifacts]生产环境里调用方还应该订阅流式事件、处理超时、实现幂等重试。幂等尤其重要调用方重试时不要重新生成 taskId而应该复用原 taskId目标 Agent 就能识别这是重试从而避免重复执行副作用操作。这个细节我在第一次对接时就吃过亏订单任务被重复执行了两次。5. 常见问题与排查技巧实录最后这部分整理几个我在 MCP A2A 多智能体项目里真实踩过的坑。这些问题都不是规范文档里会写的但大概率是每个人第一次接入都会遇到的。5.1 MCP 连接不稳定stdio 还是 HTTPMCP 用 stdio 时最大的坑是子进程悄悄挂掉但父进程不知道。尤其是 MCP Server 里如果调用了外部 API偶发的 timeout 会导致整个子进程崩溃。排查时要盯两方面一是启动命令是否带了完整路径二是 stderr 日志有没有被吞掉。我习惯把 MCP Server 进程的 stderr 重定向到独立日志文件否则报错信息消失在终端里排查效率极低。跨机器部署时建议直接上 Streamable HTTP但 HTTP 模式的鉴权和超时配置要比 stdio 严谨得多。我的经验是本地开发用 stdio测试环境立刻切成 HTTP不要在 stdio 模式下跑太久不然环境差异会在联调阶段集中爆发。5.2 A2A 握手失败Agent Card 可达性差A2A 握手最常见的问题是调用方拿不到 Agent Card。原因通常有两个一是 Agent Card 路径不对规范要求放在/.well-known/agent.json有人习惯性地放在/api/agent.json调用方按规范路径去取自然 404。二是 Card 里的 url 字段地址填错了比如填了 localhost导致外部 Agent 根本访问不到。排查技巧很简单先直接 curl 访问 Agent Card 的地址再用与调用方相同的网络环境测试。很多握手失败都是网络可达性问题不是协议问题。5.3 任务卡在 working 状态A2A 任务长时间停在 working 但没有任何事件流出常见原因是目标 Agent 内部死等一个资源数据库连接池耗尽、MCP Server 某次调用没有设超时。这个问题在单机表现不明显一旦多个智能体并发协作非常容易触发。解法是分级超时任务级超时、MCP 调用超时、HTTP 请求超时都要独立设置并且在超时发生后把任务状态置为 failed而不是让它一直悬着。另外给每个 taskId 加上全局 trace_id贯穿所有智能体的日志否则你根本不知道任务在哪个环节卡住了。5.4 返回内容过大或格式混乱MCP 工具返回内容如果设计得不好很容易出现“Agent 被一堆无效字段淹没”的情况。A2A 也一样目标 Agent 返回的 Artifact 如果结构混乱调用方解析代码会写得像屎山。我的建议是所有 MCP 工具返回都定义统一的 schema过滤掉不必要字段A2A 的 Artifact 明确声明 content type调用方按类型解析。如果数据量大优先用分页或者让调用方按需二次查询千万不要把十万字的 JSON 一股脑塞进一个工具结果里。5.5 可观测性缺失多智能体之间到底发生了什么单 Agent 调试靠日志就行多智能体系统要是没有链路追踪出了问题就只能飞书群里互相甩锅。我的做法是引入统一的事件记录每次 A2A 任务创建、状态变化、MCP 工具调用都记录一个结构化事件带上时间戳、调用方、目标方、taskId。不需要上多重的 APM 系统一个带索引的日志服务就能解决大部分问题。问题典型表现处理建议MCP 子进程崩溃工具调用突然失败 / process exit重定向 stderr 到日志监控子进程存活Agent Card 不可达握手 404 或连接超时核对路径与 url 字段用调用方网络 curl 验证任务卡 working无事件流出、状态长期不变分级超时 全局 trace_id把卡点暴露出来工具结果过大Agent 上下文爆掉 / 解析慢统一返回 schema数据量大可分页或二次查询重复执行副作用重试导致重复扣款 / 重复下单复用 taskId目标端做幂等校验最后说几句实在话协议只是骨架真正难的是把业务边界定清楚。我做完几个项目后最大的体会是如果单 Agent 加几个 MCP 工具能解决问题就别急着上 A2A 多智能体。A2A 一旦引入排查成本是指数级上升的你必须能回答“用户请求到底经过了哪些智能体”“每个智能体做了什么决策”“谁对最终结果负责”这些问题。从实践顺序来看我建议先让一个人负责全链路跑通先搭 MCP 工具链确保单 Agent 能把核心业务闭环再引入第二个 Agent 做协作。另一个值得做的前置工作是加防腐层——在 MCP Client 和 A2A Server 之间做一个统一的内部数据模型不管是工具返回还是任务产物都转换成同一个 data class。这样后续换协议、加智能体成本都低得多。系列写到这里MCP 和 A2A 的“骨架”就算搭完了。下一篇如果继续我会重点拆一个完整的落地案例三个智能体、三种工具链、一套权限体系直接跑一个真实业务场景把今天这些设计原则全部串起来用一遍。