MCP+A2A实战:构建可运维的多智能体AI系统 1. 多智能体系统进入深水区为什么单靠MCP已经不够了如果你最近半年一直在跟MCP打交道大概率已经经历过这样一个阶段一开始觉得MCP真是个好东西把各种工具、数据源、API统一成一套协议让模型可以标准化地调用外部能力。但当你真正开始搭建一个需要多个Agent协作的系统时问题就来了——MCP解决的是“单个Agent怎么调用工具”的问题它并没有解决“多个Agent之间怎么对话、怎么分工、怎么协商”的问题。这就是A2AAgent-to-Agent要补上的那块拼图。我在过去几个月里陆续搭过几套多智能体系统从最简单的“一个主Agent带两个工具Agent”到后来稍微复杂一点的“规划Agent执行Agent审查Agent”三件套踩过的坑可以说是一箩筐。这一篇作为系列第九篇我打算把MCP和A2A放在一起聊聊怎么用它们配合设计一套真正能跑起来的多智能体AI系统。不是那种demo级别的玩具而是能处理真实任务、有明确分工、出错能恢复的系统。先说清楚这套东西适合谁看。如果你已经在用MCP Server接工具但觉得单个Agent处理复杂任务时力不从心那这篇就是写给你的。如果你还没接触过MCP建议先回去补一下基础否则后面讲A2A协作时会有点吃力。整篇内容会围绕三个核心问题展开MCP在多智能体架构里到底扮演什么角色、A2A协议怎么让Agent之间高效通信、以及两者结合时有哪些必须注意的工程细节。2. 核心架构拆解MCP管工具A2A管协作2.1 先搞清楚MCP和A2A的边界在哪里很多人一开始会混淆MCP和A2A的职责。我用一个比较直白的类比来解释MCP就像是给每个员工配了一套标准化的工具箱接口不管你是电工、水管工还是木工你拿工具的方式是统一的。而A2A是员工之间的沟通规范——怎么开会、怎么分配任务、怎么汇报进度、怎么处理分歧。具体到技术层面MCP定义的是Agent与工具/资源之间的交互协议。一个MCP Server暴露的是tools、resources、prompts这三类能力。Agent通过MCP Client连接到Server然后调用这些能力。这个过程中MCP不关心是谁在调用也不关心调用之后结果怎么在多个Agent之间流转。A2A定义的是Agent与Agent之间的交互协议。它关心的是Agent A怎么向Agent B发起一个任务请求、B怎么回复状态、双方怎么交换中间结果、任务完成后怎么通知。A2A的核心概念包括Agent Card描述一个Agent能做什么、Task一个可追踪的工作单元、MessageAgent之间的消息交换。注意MCP和A2A不是替代关系而是互补关系。一个多智能体系统里每个Agent内部用MCP调用工具Agent之间用A2A通信。两者各司其职不要试图用MCP去解决Agent间通信的问题也不要用A2A去替代工具调用。2.2 多智能体系统的分层设计思路我在实际项目中总结出来的分层结构是这样的第一层工具层MCP Server集群。这一层由多个MCP Server组成每个Server负责一类工具能力。比如一个Server管文件操作一个Server管数据库查询一个Server管浏览器自动化。它们之间互相独立通过MCP协议对外暴露能力。第二层Agent层单个智能体。每个Agent是一个独立的智能体内部通过MCP Client连接到一个或多个MCP Server。Agent有自己的系统提示词、自己的决策逻辑、自己的上下文管理策略。一个Agent可以只连一个Server也可以连多个。第三层协作层A2A通信。这一层负责Agent之间的任务分发、状态同步、结果汇总。通常需要一个协调者Orchestrator角色它不直接执行具体任务而是根据任务需求把工作拆解后分给合适的Agent。第四层应用层对外接口。最终用户或者外部系统通过这一层与整个多智能体系统交互。可以是一个API网关也可以是一个对话界面。这个分层的好处是每一层可以独立演进。你换了一个MCP Server不影响Agent之间的协作逻辑你调整了A2A的通信策略也不影响底层工具的实现。2.3 为什么选择MCPA2A而不是其他方案市面上做多智能体协作的方案不少有基于消息队列的有基于RPC的也有基于共享内存的。我选择MCPA2A组合主要基于几个考虑标准化程度高。MCP和A2A都是开放协议有明确的规范文档社区生态也在快速成长。这意味着你不需要自己造轮子定义一套私有协议后续接入新的工具或Agent时成本更低。职责分离清晰。MCP只管工具调用A2A只管Agent通信两者边界明确。这种清晰的分工让系统调试变得容易——出问题时你能快速定位是工具调用的问题还是Agent协作的问题。可组合性强。你可以先用MCP搭一个单Agent系统跑通了之后再引入A2A扩展成多Agent。反过来也行先设计好A2A的协作框架再逐个给Agent接上MCP工具。这种渐进式的搭建方式对工程实践非常友好。社区活跃。从热搜词就能看出来MCP相关的工具和教程层出不穷从Figma MCP到Playwright MCP再到各种IDE的MCP集成生态在快速膨胀。A2A虽然相对新一些但背后的推动力也很强。3. 核心细节解析MCP Server选型与A2A通信设计3.1 MCP Server的选型与配置要点在实际搭建多智能体系统时MCP Server的选型直接决定了Agent的能力边界。我一般从三个维度来评估功能覆盖度。这个Server提供的工具是否覆盖了目标场景的核心需求。比如你要做一个自动化测试Agent那Playwright MCP基本是必选的它提供了浏览器操作、页面截图、元素定位等能力。如果你要做设计稿转代码Figma MCP和蓝湖MCP就很有用。稳定性与错误处理。一个MCP Server在高频调用下是否稳定出错时是否返回有意义的错误信息这直接影响Agent的决策质量。我踩过的一个坑是某个Server在超时后直接断开连接导致Agent收到一个空响应然后基于空响应做出了完全错误的下一步操作。认证与安全。如果Server需要访问外部服务认证机制是否合理。比如通过token认证的Servertoken的刷新策略、权限范围都需要提前规划好。配置MCP Server时有几个参数需要特别注意{ mcpServers: { playwright: { command: npx, args: [-y, anthropic/mcp-playwright], env: { BROWSER: chromium, HEADLESS: true, TIMEOUT: 30000 } } } }这里的TIMEOUT设置很关键。设太短复杂页面加载不完就超时了设太长Agent会卡在那里干等。我的经验值是30秒起步对于特别复杂的页面可以调到60秒但不要超过90秒否则整个系统的响应时间会变得不可接受。3.2 A2A通信的核心机制与实现细节A2A协议的核心是Agent Card和Task两个概念。Agent Card是一个JSON文档描述了一个Agent的基本信息{ name: code-review-agent, description: 负责代码审查的智能体, capabilities: [code-review, security-scan, style-check], endpoint: http://localhost:8081/a2a, inputSchema: { type: object, properties: { code: {type: string}, language: {type: string}, reviewLevel: {type: string, enum: [basic, strict]} } } }当协调者Agent需要做代码审查时它会查找所有声明了code-review能力的Agent Card然后选择一个发起Task请求。Task的生命周期包括submitted、working、input-required、completed、failed、canceled这几个状态。这里有一个设计决策点同步还是异步。同步模式下协调者发起Task后一直等到完成或失败。异步模式下协调者发起Task后可以继续做别的事通过轮询或回调来获取状态更新。我的建议是对于耗时短10秒的任务用同步对于耗时长的任务用异步。但异步模式下要特别注意状态同步的问题——如果协调者Agent在等待期间又发起了新的Task两个Task的结果回来时怎么区分这就需要在Task ID上做好映射。3.3 Agent之间的消息格式设计A2A的消息格式设计直接影响协作效率。我一般会把消息分成三类任务请求消息。包含任务描述、输入参数、期望输出格式、优先级、截止时间等。这类消息要尽量结构化避免自然语言的歧义。状态更新消息。Agent在执行任务过程中定期发送进度更新。这类消息不需要太详细但要让协调者知道任务还在推进中没有卡死。结果返回消息。包含任务执行结果、执行过程中的关键日志、遇到的错误如果有、以及建议的后续操作。实操心得消息里一定要带一个correlationId用来把请求和响应关联起来。在多Agent并发执行时没有这个ID你根本分不清哪个结果对应哪个请求。我一开始没加这个字段调试时差点把自己逼疯。4. 实操过程从零搭建一个MCPA2A多智能体系统4.1 环境准备与基础依赖安装先列一下我这次搭建用的技术栈运行时Node.js 20 或 Python 3.11MCP SDK官方提供的modelcontextprotocol/sdk或mcpPython包A2A框架可以用官方的A2A SDK也可以基于HTTPJSON自己实现轻量级版本消息队列可选Redis或RabbitMQ用于异步任务的状态管理日志系统结构化日志推荐JSON格式安装基础依赖npm init -y npm install modelcontextprotocol/sdk express uuid npm install -D typescript types/node types/express如果你用Pythonpip install mcp fastapi uvicorn httpx pydantic4.2 搭建第一个MCP Server文件操作能力我们先从一个最简单的MCP Server开始提供文件读写能力。这个Server会被后续的Agent调用。import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import fs from fs/promises; import path from path; const server new Server({ name: file-ops-server, version: 1.0.0 }, { capabilities: { tools: {} } }); server.setRequestHandler(tools/list, async () ({ tools: [ { name: read_file, description: 读取指定路径的文件内容, inputSchema: { type: object, properties: { filePath: { type: string, description: 文件绝对路径 } }, required: [filePath] } }, { name: write_file, description: 将内容写入指定文件, inputSchema: { type: object, properties: { filePath: { type: string }, content: { type: string } }, required: [filePath, content] } } ] })); server.setRequestHandler(tools/call, async (request) { const { name, arguments: args } request.params; if (name read_file) { const content await fs.readFile(args.filePath, utf-8); return { content: [{ type: text, text: content }] }; } if (name write_file) { await fs.writeFile(args.filePath, args.content, utf-8); return { content: [{ type: text, text: 写入成功 }] }; } throw new Error(未知工具: ${name}); }); const transport new StdioServerTransport(); await server.connect(transport);这个Server虽然简单但已经具备了MCP的核心要素工具声明、参数schema、调用处理。实际项目中你需要根据业务需求扩展更多工具比如列目录、删除文件、搜索文件内容等。4.3 实现A2A通信层Agent注册与任务分发接下来实现A2A的通信层。我选择用HTTPJSON的方式因为这样最容易调试也最容易和其他系统集成。import express from express; import { v4 as uuidv4 } from uuid; interface AgentCard { name: string; description: string; capabilities: string[]; endpoint: string; } interface Task { id: string; correlationId: string; from: string; to: string; capability: string; input: Recordstring, any; status: submitted | working | completed | failed; result?: any; error?: string; createdAt: number; updatedAt: number; } class A2ARegistry { private agents: Mapstring, AgentCard new Map(); private tasks: Mapstring, Task new Map(); registerAgent(card: AgentCard) { this.agents.set(card.name, card); console.log(Agent注册: ${card.name}, 能力: ${card.capabilities.join(, )}); } findAgentsByCapability(capability: string): AgentCard[] { return Array.from(this.agents.values()) .filter(a a.capabilities.includes(capability)); } createTask(from: string, to: string, capability: string, input: any): Task { const task: Task { id: uuidv4(), correlationId: uuidv4(), from, to, capability, input, status: submitted, createdAt: Date.now(), updatedAt: Date.now() }; this.tasks.set(task.id, task); return task; } updateTaskStatus(taskId: string, status: Task[status], result?: any, error?: string) { const task this.tasks.get(taskId); if (!task) throw new Error(任务不存在: ${taskId}); task.status status; task.result result; task.error error; task.updatedAt Date.now(); } getTask(taskId: string): Task | undefined { return this.tasks.get(taskId); } } const registry new A2ARegistry(); const app express(); app.use(express.json()); app.post(/a2a/register, (req, res) { registry.registerAgent(req.body); res.json({ success: true }); }); app.post(/a2a/task, (req, res) { const { from, capability, input } req.body; const candidates registry.findAgentsByCapability(capability); if (candidates.length 0) { return res.status(404).json({ error: 没有Agent具备能力: ${capability} }); } const target candidates[0]; const task registry.createTask(from, target.name, capability, input); // 异步转发任务给目标Agent fetch(${target.endpoint}/execute, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ taskId: task.id, input }) }).then(async (resp) { const result await resp.json(); registry.updateTaskStatus(task.id, completed, result); }).catch((err) { registry.updateTaskStatus(task.id, failed, undefined, err.message); }); res.json({ taskId: task.id, correlationId: task.correlationId, status: submitted }); }); app.get(/a2a/task/:taskId, (req, res) { const task registry.getTask(req.params.taskId); if (!task) return res.status(404).json({ error: 任务不存在 }); res.json(task); }); app.listen(8080, () console.log(A2A Registry启动在8080端口));这段代码实现了一个最简版的A2A注册中心。实际生产中你需要考虑Agent的健康检查、任务超时处理、重试机制、并发控制等。4.4 组装一个完整的协作流程现在我们把MCP Server和A2A通信层串起来实现一个完整的协作场景用户提交一个“分析项目代码并生成报告”的任务。流程是这样的协调者Agent收到用户请求协调者通过A2A查找具备code-analysis能力的Agent协调者创建Task并分发给代码分析Agent代码分析Agent通过MCP调用文件操作Server读取代码文件代码分析Agent完成分析后通过A2A返回结果协调者再查找具备report-generation能力的Agent报告生成Agent通过MCP调用文件操作Server写入报告协调者汇总结果返回给用户class OrchestratorAgent { constructor( private a2aEndpoint: string, private mcpClient: any ) {} async handleUserRequest(request: string) { // 第一步分发代码分析任务 const analysisTask await this.dispatchTask( code-analysis, { path: /project/src, depth: full } ); const analysisResult await this.waitForTask(analysisTask.taskId); if (analysisResult.status failed) { return { error: 代码分析失败, detail: analysisResult.error }; } // 第二步分发报告生成任务 const reportTask await this.dispatchTask( report-generation, { analysisData: analysisResult.result, format: markdown, outputPath: /project/report.md } ); const reportResult await this.waitForTask(reportTask.taskId); return { success: true, analysis: analysisResult.result, report: reportResult.result }; } private async dispatchTask(capability: string, input: any) { const resp await fetch(${this.a2aEndpoint}/a2a/task, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ from: orchestrator, capability, input }) }); return resp.json(); } private async waitForTask(taskId: string, maxWait 60000) { const start Date.now(); while (Date.now() - start maxWait) { const resp await fetch(${this.a2aEndpoint}/a2a/task/${taskId}); const task await resp.json(); if (task.status completed || task.status failed) { return task; } await new Promise(r setTimeout(r, 1000)); } throw new Error(任务超时: ${taskId}); } }这个流程看起来简单但实际跑起来会遇到各种问题。下面一节我会详细讲。5. 常见问题与排查技巧实录5.1 MCP连接层面的典型故障问题一MCP Server启动后Agent连不上。最常见的原因是stdio传输模式下Server的输出被其他日志污染了。MCP协议要求stdio模式下stdout只能输出协议消息任何调试日志都必须走stderr。我踩过这个坑在Server里加了一行console.log(Server started)结果Agent直接解析失败。排查方法检查Server的stdout输出确保只有JSON-RPC消息。所有日志改用console.error。问题二工具调用超时。有些MCP工具执行时间较长比如浏览器自动化操作默认超时时间可能不够。需要在Client端设置合理的超时同时在Server端做好超时后的清理工作。问题三并发调用时的资源竞争。多个Agent同时调用同一个MCP Server的文件写入工具可能导致文件内容错乱。解决方案是在Server端加锁或者让Agent在调用前先申请资源锁。5.2 A2A通信中的常见异常问题一任务状态不同步。协调者认为任务还在working但实际上目标Agent已经完成了只是状态更新消息丢了。解决方案是引入心跳机制目标Agent定期上报状态协调者超过一定时间没收到心跳就主动查询。问题二Agent能力声明与实际不符。Agent Card里声明了某个能力但实际执行时发现做不了。这通常是因为Agent Card是静态配置的没有反映Agent的实时状态。解决方案是让Agent Card支持动态更新或者在任务分发前先做一个能力探测。问题三循环依赖。Agent A把任务分给Agent BAgent B又把任务分回Agent A形成死循环。解决方案是在Task里记录调用链检测到循环时直接报错。5.3 多智能体系统的性能优化技巧技巧一MCP连接池化。不要每次调用工具都新建MCP连接维护一个连接池复用已有连接。这在高频调用场景下能显著降低延迟。技巧二A2A消息批量处理。如果协调者需要向多个Agent分发任务可以批量发送而不是逐个发送。但要注意批量大小太大反而会增加单次请求的失败风险。技巧三结果缓存。对于幂等的工具调用比如读取文件内容可以在Agent层加缓存。同一个文件在短时间内被多次读取时直接返回缓存结果。技巧四异步流水线。把整个协作流程拆成多个阶段阶段之间用异步消息衔接。这样前一个阶段还在处理时后一个阶段就可以开始准备整体吞吐量能提升不少。5.4 常见问题速查表问题现象可能原因排查方向解决方案Agent连不上MCP Serverstdout被污染检查Server日志输出日志改走stderr工具调用返回空结果超时或连接断开查看Server端日志增加超时时间加连接保活A2A任务一直submitted目标Agent未响应检查目标Agent是否在线加健康检查自动重试任务结果错乱correlationId缺失检查消息关联字段强制要求带correlationId系统响应越来越慢连接泄漏或内存泄漏监控连接数和内存连接池化定期清理Agent决策质量下降上下文过长检查上下文窗口使用率加摘要机制定期压缩上下文6. 进阶话题让多智能体系统真正可运维6.1 日志与可观测性设计多智能体系统最让人头疼的就是调试。一个任务经过三四个Agent每个Agent又调用了若干MCP工具出问题时你根本不知道是哪一步挂了。我的做法是在每个环节都打结构化日志并且用同一个traceId串起来。interface LogEntry { timestamp: number; traceId: string; agentName: string; action: string; detail: Recordstring, any; level: info | warn | error; } function log(entry: LogEntry) { console.log(JSON.stringify(entry)); }traceId在用户请求进入系统时生成然后通过A2A消息和MCP调用一路传递下去。这样你可以在日志系统里用traceId过滤出一次完整请求的所有日志排查效率提升非常明显。6.2 错误恢复与重试策略多智能体系统里错误是常态关键是怎么优雅地恢复。我一般会区分三类错误可重试错误。比如网络抖动导致的超时、临时性的资源不可用。这类错误直接重试但要有退避策略不能疯狂重试把系统打垮。可降级错误。比如某个非关键Agent挂了但主流程还能继续。这时候可以跳过这个Agent用默认值或者简化流程继续。不可恢复错误。比如输入数据格式完全不对、依赖的外部服务彻底不可用。这类错误直接上报让用户或运维介入。重试策略我一般用指数退避第一次等1秒第二次等2秒第三次等4秒最多重试3次。超过3次就标记为失败走降级或上报流程。6.3 安全边界与权限控制多智能体系统里每个Agent能做什么、不能做什么必须有明确的边界。我见过因为权限控制没做好一个负责文本处理的Agent意外调用了文件删除工具把重要数据删了的情况。我的做法是在MCP Server层面做权限控制。每个Agent连接Server时带一个身份标识Server根据身份标识决定哪些工具可以调用。比如只读Agent只能调用read_file不能调用write_file。A2A层面也要做权限控制。不是所有Agent都能向所有其他Agent发起任务。协调者Agent有分发任务的权限执行Agent只有接收任务的权限不能反过来给协调者派活。6.4 系统扩展时的注意事项当你需要给系统增加新的Agent或新的MCP Server时有几个点需要特别注意能力注册的原子性。新Agent注册时要么全部能力都注册成功要么全部失败。不要出现注册了一半的情况否则协调者可能把任务分给一个能力不完整的Agent。版本兼容性。A2A协议和MCP协议都在演进新老版本之间可能有差异。升级时要么全量升级要么做好版本协商。容量规划。每增加一个Agent系统的并发能力理论上增加但协调者的负担也增加。协调者本身可能成为瓶颈需要考虑协调者的水平扩展。7. 我个人在实际项目中的几点体会搭过多套MCPA2A系统之后我最大的体会是不要一开始就追求大而全。我见过太多人一上来就想设计一个包含十几个Agent、几十个MCP工具的复杂系统结果连最基本的任务分发都没跑通就放弃了。比较务实的路径是先用MCP搭一个单Agent系统把工具调用跑通。然后引入第二个Agent用最简单的A2A通信把两个Agent串起来。跑通之后再逐步增加Agent数量和工具复杂度。每一步都确保系统是可用状态而不是一堆半成品。另一个体会是日志和监控要提前做。多智能体系统的调试难度比单Agent高一个数量级如果没有好的日志和监控出问题时你就是在盲人摸象。我现在的习惯是在写业务逻辑之前先把日志框架和traceId机制搭好后面会省很多事。还有一点关于MCP Server的选择优先用社区成熟的Server不要什么都自己写。像Playwright MCP、Figma MCP这些已经有比较完善的实现直接用就行。自己写Server只在有特殊需求时才考虑因为维护成本不低。最后分享一个实用小技巧在开发阶段可以给A2A注册中心加一个“模拟模式”让所有Agent的响应都返回预设的mock数据。这样你可以在不启动真实Agent的情况下调试协调者的逻辑效率会高很多。等协调者逻辑稳定了再切换到真实Agent进行集成测试。