ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MCP + A2A双协议:企业级多智能体集群落地实战

MCP + A2A双协议:企业级多智能体集群落地实战 去年我给某制造集团落地智能运维平台时老板抛过来一个很朴素的需求让AI自动处理告警、工单和库存联动。我最初的想法也挺天真——一个Agent挂上一堆工具再配个知识库不就完事了吗。结果一上线就翻车单Agent面对几十种业务系统、上百个工具函数上下文窗口被塞满不说工具调用链路一旦超过三跳就开始迷路更别提多个部门各自的Agent之间完全没法协作A部门和B部门的智能体各做各的数据割裂流程断档。后来反复试错才摸到一套靠谱的解法用MCP管Agent和工具、数据之间的上下行用A2A管Agent与Agent之间的平级协作两者叠加才是企业级多智能体集群应用该有的样子。这篇文章我就把这套架构的来龙去脉、落地细节和踩坑实录完整拆给你看特别适合正在做Agent平台化、或者被多智能体协作搞到头大的架构师和开发同学。1. 多智能体集群为什么绕不开双协议这道坎1.1 单个Agent的天花板不在聪明而在连接先说一个很多人容易忽略的事实现在的LLM本身不缺推理能力缺的是和企业真实系统之间的可控连接。企业里有什么有ERP、CRM、WMS、监控系统、工单系统有MySQL、PostgreSQL还有一堆内部API和Excel报表。Agent要干活第一步就是得能安全、可控、可审计地去调用这些系统。我见过不少团队的第一版方案是用Python直接写工具函数然后用Function Calling硬编码进Agent。单个Agent、五六个函数的时候确实简单粗暴问题也不大。但一旦规模上来你会发现三个痛点非常致命工具和Agent强耦合每加一个工具就要改一次Agent代码不同Agent各自维护一套工具调用逻辑重复造轮子而且调用风格不统一工具鉴权、参数校验、日志审计散落各处安全上完全不可控这时候**MCPModel Context Protocol**的价值就出来了——它把Agent调用工具这件事标准化成了一套协议。Agent不再直接感知工具的具体实现而是通过统一的MCP客户端去发现、调用、获取结果。你可以把MCP理解成AI世界的USB接口不管插的是鼠标、键盘还是U盘只要遵循USB协议即插即用。MCP干的就是这个事让任何Agent都能即插即用任何符合规范的工具服务。1.2 A2A解决的是Agent之间怎么说话的问题MCP解决的是Agent往下的工具连接但企业里还有另一层需求Agent 和 Agent 之间怎么协作比如工单Agent发现某个告警需要库存系统介入它是自己直接去查库存、扣库存还是喊一声库存Agent来处理一下如果所有Agent都直接去调所有工具那整个系统的复杂度是指数级爆炸的。每个Agent都得知道其他Agent的能力边界、调用方式、消息格式这不叫协作叫耦合。而且当Agent数量超过五个以后点对点的网状连接根本维护不下去。A2AAgent-to-Agent协议就是用来解决这个问题的。它定义了一套Agent之间相互发现、通信、任务协作的标准每个Agent对外发布一张能力名片Agent Card其他Agent通过这张名片知道它能干什么、该怎么调用任务下发、状态回传、结果交付全部走标准化的JSON-RPC消息格式。MCP和A2A一个是手脚一个是嘴巴。手脚负责够到工具和数据嘴巴负责让不同Agent能对话、分工、交接。两者不冲突反而天然互补——这也是DeepAgents这套架构最核心的出发点。1.3 DeepAgents的定位不是又一个框架而是一套组合拳我第一次接触DeepAgents这个概念时以为它跟LangGraph、AutoGen一样又是某个多智能体编排框架。但仔细研究下来DeepAgents更像是一套架构方法论底层用MCP统一接入工具和知识中间用A2A实现Agent之间的动态协作上层再做集群化的调度、容错和观测。这套组合拳的好处很直接Agent不需要内置海量工具需要什么能力就去MCP Server里发现Agent之间不写死调用关系通过A2A动态发现和协商集群层负责把多个Agent组织成一条生产流水线而不是各干各的孤岛接下来我把这三层逐个拆开讲清楚实现要点。2. MCP接入层给每个Agent装上标准化的手和眼睛2.1 MCP的核心交互模型Host、Client、Server三方博弈MCP的架构模型很好记就三方Host宿主、Client客户端、Server服务端。Host跑Agent的进程比如你的Agent服务、IDE插件、聊天应用ClientHost内部嵌入的MCP客户端负责和Server建立连接、发起请求Server独立运行的工具服务暴露出Resources、Tools、Prompts三类原语我常用一个餐厅类比来解释这套东西Agent是食客MCP Server是后厨MCP Client是服务员。食客不直接进后厨炒菜而是告诉服务员我要一份宫保鸡丁服务员负责把菜单Tools列表递给食客、把需求传进后厨、再把菜端出来。整个过程食客和后厨不需要知道彼此的细节。在企业落地时MCP Server通常是独立部署的微服务通过Streamable HTTP或stdio传输走JSON-RPC 2.0格式。核心交互就几个方法方法方向作用tools/listClient → Server获取该Server支持的Tool清单tools/callClient → Server调用指定的Tool并传入参数resources/listClient → Server获取上下文资源列表resources/readClient → Server读取资源内容注入Agent上下文prompts/listClient → Server获取提示词模板实操中很多团队会纠结一个问题我和现有API Gateway的关系是什么我的回答是MCP Server不做业务逻辑它的职责是把已有系统翻译成Agent能理解和调用的工具。真正的业务逻辑应该留在原系统里MCP Server只做协议适配和参数映射。2.2 企业场景下MCP Server的选型标准MCP Server的选型和普通微服务选型不太一样除了常规的性能、稳定性还要额外关注几个点工具语义的清晰度。Agent是靠工具的description字段来决定什么时候调用它的。如果description写得模棱两可Agent就会在错误的时机调用错误的工具。我给团队定的标准是description必须写清楚这个工具解决什么问题、什么场景下用、参数有什么限制。实测下来description写得好不好直接决定Agent工具调用的准确率比重试机制管用得多。鉴权模型的灵活度。企业里有的工具是内部免费调的有的需要走OAuth2.0有的要校验Token。MCP Server最好支持在Transport层挂载自定义鉴权逻辑建议统一走OAuth 2.1规范但要注意Server启动时通过Authorization Options动态发布会话协商信息客户端先协商再连接。无状态设计。这是很多初版实现最容易踩的坑把MCP Server写成了有状态服务连接数一多就崩。MCP Server要做到无状态业务状态放在外部存储Redis或数据库Tool调用只负责执行返回不保存会话上下文。下面是一个用FastMCP框架写MCP Server的示例做个参考骨架from fastmcp import FastMCP mcp FastMCP( inventory-dashboard-mcp, dependencies[pymysql, redis], # 实际项目中这里会配置鉴权、超时、连接池等参数 ) mcp.tool() def get_real_time_stock(sku_id: str) - dict: 查询商品实时可售库存。 适用场景库存核对、订单履约校验、补货决策时调用。 Args: sku_id: 商品SKU编号如 SKU-10086 # 伪代码示意实际应走数据服务层 stock query_mysql(SELECT stock FROM inventory WHERE sku_id%s, sku_id) return {sku_id: sku_id, stock: stock} mcp.tool() def reserve_stock(order_id: str, sku_id: str, quantity: int) - dict: 预占库存防止超卖。 # 需要走Redis分布式锁 数据库事务 ... if __name__ __main__: mcp.run(transportstreamable-http)这里我特意用了FastMCP而不是裸写JSON-RPC因为FastMCP帮我们把协议细节封装好了写Tool就像写普通Python函数可读性和可维护性都好很多。2.3 Agent侧接入MCP的连接管理细节Agent这边接MCP最核心的就是连接管理和错误处理。企业里Agent不止一个每个Agent可能同时连多个MCP Server如果每个连接都长驻常开资源消耗很大。我推荐的做法是搞一个MCP连接池中间层按Server维度维护连接复用空闲超时自动释放请求时动态获取。连接池参数参考值参数推荐值说明单Server最大连接数50根据Agent并发度调整空闲连接超时300秒避免空闲连接占资源请求超时30秒工具调用整体不能拖太久重试次数3次指数退避1s/2s/4s另外MCP Server返回的错误信息中不同错误码要区别对待。比如Tool Not Found就要立刻熔断换个Server或换个工具而超时错误可以重试。我在项目里专门封装了一个error-router组件把MCP返回的JSON-RPC错误码映射到不同的降级策略上这个在后面坑位部分会详细说。3. A2A通信层Agent之间如何协作而不互相打架3.1 A2A的Agent CardAgent的能力名片A2A协议的核心设计哲学是Agent之间不直接硬编码调用而是通过能力声明的形式互相发现和协商。每个Agent启动时会在固定路径发布一张Agent Card内容大致长这样{ name: order-fulfillment-agent, description: 订单履约Agent负责创建履约单、同步WMS、回传物流状态, skills: [ { id: create_fulfillment, name: 创建履约单, description: 根据订单信息生成履约单并推送至WMS系统 }, { id: query_fulfillment_status, name: 查询履约状态, description: 根据订单号查询履约进展和物流轨迹 } ], capabilities: { streaming: true, pushNotifications: true }, defaultInputModes: [application/json], url: https://agent.internal.core/order-agent/a2a/ }其他Agent拿到这张名片后就能通过message/send、task/get、task/cancel这些标准方法发起任务、查询状态、取消任务。注意Agent Card里有个非常关键的字段是url这决定了A2A消息要发到什么地址所以内部Agent服务注册中心必须保证这个url是可达的。3.2 Task模型一次协作就是一个任务生命周期A2A协议里有个很精巧的设计——Task任务模型。一次Agent协作不是简单发一条消息就完了而是创建一个Task然后Task经历完整的生命周期submitted → working → input-required → completed ↘ failed / canceled为什么要设计这么重的任务模型因为企业场景下Agent协作经常不是一步到位。比如工单Agent让订单履约Agent处理一个复杂订单履约Agent发现库存不够它不能瞎编得返回一个needs input的状态向工单Agent索要是否允许缺货发运的决策。这种多轮协商在一个轻量级的message模型里很难管理而Task模型天生支持。实操时企业内部Agent之间的Task状态同步是重点。A2A支持流式和推拉两种方式简单场景发起方轮询task/get拿到最终结果实时场景Agent提供Push Notification配置状态变化时主动推送给发起方我们的经验是集群内部的Agent协作建议走推送外网跨域的走轮询别一上来就全上推送否则通知风暴会先把你打趴。3.3 多Agent协作的典型流程拆解我拿一个真实的业务场景来演示A2A协作流程客户下单后订单Agent、库存Agent、履约Agent之间的配合。订单Agent解析用户订单发现需要验证库存通过内部Agent注册中心查找到库存Agent的Card确认有check_stock能力订单Agent向库存Agent发送message/send创建Task输入参数为SKU列表库存Agent执行MCP工具获取实时库存Task流转到completed回传结果订单Agent根据结果生成履约指令再向履约Agent发起新Task履约Agent创建履约单调用WMS系统的MCP工具同步数据全链路状态通过日志和Trace系统上报供运营监控这个链路里MCP是Agent往下够工具的通道A2A是Agent之间横向对话的通道。两条通道各自独立互不干扰又同时服务于同一条业务链。4. 企业级集群应用的落地架构设计4.1 分层架构接入层、编排层、服务层、数据层理论讲完该上硬菜了。企业级多智能体集群我建议按四层来设计千万别把Agent直接打成一个个裸服务让它们互相调用那迟早是灾难第一层网关接入层。这一层负责接收外部业务请求做协议转换、鉴权、限流。外部系统不用关心后面是Agent还是人统一走API网关进来。第二层编排调度层。这是DeepAgents架构的大脑负责任务分解、Agent路由、上下文管理、任务状态机维护。我建议用独立的Orchestrator组件而不是让每个Agent自己决定下一步找谁。第三层Agent服务层。这一层部署具体的业务Agent每个Agent通过MCP Client连接工具服务通过A2A Client和其他Agent通信。Agent本身尽量无状态业务状态全部外置。第四层工具与数据层。包括所有MCP Server、数据库、消息队列、缓存。分层逻辑的核心思想是让Agent做一个聪明的调用者而不是复杂的集成者。复杂的集成逻辑放到编排层和工具层Agent只负责理解任务、分解任务、调用合适的资源和Agent。4.2 Agent注册中心所有协作的通讯录A2A协议本身支持通过标准URL发现Agent Card但在企业集群里我强烈建议不要直接吃外网URL而是自建一个内部Agent注册中心。所有Agent启动时向注册中心上报自己的Card、健康状态、负载情况。注册中心的价值不只是发现更重要的是路由决策。当编排层要找一个能查库存的Agent时它问注册中心注册中心返回的不是一个Agent而是按健康度、负载、地理位置排序后的Agent列表。编排层再按策略选一个。注册中心的存储我用的是Redis或者etcdCard数据量不大重要的是要支持Watch机制Agent上下线能实时感知。健康检查建议两种结合Agent主动心跳 编排层定期探测A2A的card/get。4.3 可靠性设计超时、熔断、幂等、重试企业级应用绕不开可靠性。多智能体集群里故障点比单体系统多一个数量级所以每一层都要做防护超时控制A2A Task调用要区分总超时和单步超时。我习惯的做法是Orchestrator对每个业务请求设置总超时例如30秒每步Agent调用设置步超时例如8秒MCP工具调用再设5秒。三层超时逐层收敛出了问题能快速定位到底卡在哪一层。熔断降级当某个Agent或某个MCP Server连续报错超过阈值需要快速熔断避免级联雪崩。熔断状态要放在注册中心里让其他Agent在路由阶段就绕开故障节点而不是等调用了再失败。幂等处理Agent重试最怕的不是重复调而是重复调造成脏数据。所有A2A Task都要带一个全局唯一的messageId服务端要做去重。MCP工具也要设计成幂等比如创建履约单前台上要做如果已存在相同orderId的履约单直接返回已有结果。重试策略网络抖动和瞬时过载是可以重试的但要注意指数退避不要让所有Agent在同一时间点疯了一样重试。我用的是1s/2s/4s/8s最多5次超过就进死信队列人工处理。4.4 可观测性没有Trace的集群等于盲人摸象多智能体集群最头疼的问题就是一次业务请求内部到底经过了几个Agent、调了多少次工具、哪一步慢、哪一步错没有良好的Trace出了问题就只能靠猜。我的建议是接入OpenTelemetry全链路Trace。关键的Span点包括网关收到请求记录原始入参Orchestrator任务分解结果每次A2A Task的发起和回执每次MCP Tool的调用和返回Agent内部LLM推理的关键节点这一步耗时通常最高必须单独统计除了Trace还要做Agent链路审计日志。企业场景里AI做的决策必须有记录不然出了问题没法追溯。审计日志和普通日志不一样它要强调谁在什么时间基于什么输入做了什么决策字段要结构化存保留时间按企业合规要求来。5. 实操从零搭一个最小可用的双协议集群5.1 环境准备与组件选型下面我带着你从零搭一个最小可用的双协议集群组件清单如下Python 3.11Poetry管理依赖FastMCP快速构建MCP Servera2a-sdkGoogle官方开源的A2A Python SDKFastAPI承载A2A Agent Card和EndpointRedisAgent注册中心 缓存初始化一个项目目录mkdir deepagents-demo cd deepagents-demo poetry init poetry add fastmcp a2a-sdk fastapi uvicorn redis5.2 第一步建MCP工具服务库存查询先建一个库存查询的MCP Server用FastMCP暴露一个工具# mcp_servers/stock_server.py from fastmcp import FastMCP mcp FastMCP(stock-mcp-server) mcp.tool() def get_stock(sku_id: str) - dict: 查询指定SKU的实时库存。 用于库存核对、订单可售校验等场景。 # 真实项目里这里接Redis或数据库 mock_data { SKU-001: {available: 120, frozen: 3}, SKU-002: {available: 8, frozen: 1}, } return mock_data.get(sku_id, {available: 0, frozen: 0}) if __name__ __main__: mcp.run(transportstreamable-http, host0.0.0.0, port9001)启动python mcp_servers/stock_server.py5.3 第二步建一个带MCP客户端的A2A Agent这一层是重点——Agent既要能跟业务方通过A2A对话又要能通过MCP调用库存工具。核心代码如下# agents/stock_agent.py import json from a2a.types import AgentCard, Skill, Message, Task from a2a.server import Agent, register_agent from fastmcp import Client class StockAgent(Agent): def __init__(self): # 初始化MCP客户端连接库存MCP Server self.mcp_client Client(http://localhost:9001/mcp) async def handle_message(self, message: Message, task: Task) - Task: # 解析业务参数 payload json.loads(message.content) sku_id payload.get(sku_id) # 通过MCP调用库存工具 result await self.mcp_client.call_tool(get_stock, {sku_id: sku_id}) # 更新Task状态回传结果 task.payload json.dumps({stock: result.data}) task.status completed return task # 定义Agent Card agent_card AgentCard( namestock-agent, description库存查询Agent通过MCP接入实时库存系统, skills[Skill(idquery_stock, name查询库存, description根据SKU查询实时库存)], urlhttp://localhost:9002/stock-agent/, ) register_agent(agent_card, StockAgent())5.4 第三步Orchestrator完成任务路由和A2A调用最后写一个极简Orchestrator收到业务请求后通过注册中心找到库存Agent再用A2A协议下发任务# orchestrator.py import httpx from a2a.types import Message, Task async def route_business_request(order_item: dict): # 1. 从注册中心读取库存Agent的Card card await get_agent_card(stock-agent) # 2. 构造A2A任务消息 msg Message(contentjson.dumps({sku_id: order_item[sku_id]})) task Task(messages[msg]) # 3. 发送到库存Agent的A2A Endpoint async with httpx.AsyncClient() as client: resp await client.post( card.url message/send, jsontask.to_dict() ) result_task Task.from_dict(resp.json()) # 4. 判断最终状态并返回 if result_task.status completed: return result_task.payload else: raise RuntimeError(f任务未完成: {result_task.status})这里我只展示了最小链路实际项目里编排层还会做任务分解、多Agent并行、条件路由这些逻辑但骨架就是这个样子MCP管工具A2A管协作编排层管流程。6. 踩坑记录与实际调优这些坑我替你走过了6.1 坑一MCP连接数爆炸上线第一周就遇到一个问题Agent一多MCP Server的连接数蹭蹭往上涨直接打满File Descriptor限制。排查下来发现每个Agent的每次请求都新建了MCP连接用完没复用。解决方案其实前面提过就是连接池。但这里有个细节很多人会忽略MCP的连接池不能只看总数要看每个Server维度的复用率。我在监控面板里加了一个指标——mcp_connection_reuse_rate低于70%就要检查是不是连接池配置不合理或者连接泄漏了。6.2 坑二A2A普通消息和流式响应打架A2A协议支持streaming但streaming的响应对网关和客户端都有额外要求需要支持SSE。我们一开始图省事所有A2A调用都要求streaming结果网关的HTTP连接被长连接占满导致其他短请求被饿死。调优策略很粗暴但有效内部Agent之间的小任务用普通非流式模式需要长时间处理的大任务才用streaming并且给streaming连接单独开一个网关线程池。你可以理解为别让所有人都在高速公路上开慢车。6.3 坑三Agent Card的URL写错导致跨命名空间调用失败多智能体集群大概率会跨namespace或者跨网络环境部署。Agent Card里写的是内网地址还是外网地址用K8s Service名还是Ingress地址这个看起来很小的问题坑了我们一位同事整整两天。后来我们定了规范Agent Card里的url统一由部署平台注入环境变量Agent启动时读取不允许硬编码。同时在Agent注册中心里加了一道校验——Agent上报Card时要主动探测自己声明的url是否可达不可达直接拒绝注册。6.4 坑四MCP Tool的description写得烂Agent直接摆烂这是最隐蔽的坑。人工测工具调用全对一放到真实Agent场景就歇菜。后来拉日志分析发现Agent在错误时机调了错误工具甚至干脆不调工具。根因就是description太抽象。比如我当时写了一个get_inventory_datadescription写的是获取库存数据Agent根本不知道什么时候该用。改成查询指定SKU的实时可售库存用于库存校验、订单履约、超卖检查等场景参数sku_id必填之后调用准确率肉眼可见地提升了。所以给MCP工具写description我总结了三原则写明适用场景让Agent知道什么时候该用我写清参数限制减少Agent乱传参的概率写明常见错误比如查不到时返回空对象而非抛异常6.5 性能调优LLM推理时间和配比最后说一个调优方向多智能体集群的响应时间大头通常在LLM推理而不是网络和工具调用。我观测过一个真实的订单处理链路总耗时18秒其中LLM推理占了12秒MCP工具调用只占2秒A2A传输甚至不到0.5秒。这意味着你要优化集群性能光优化网络和协议意义不大核心还是得想方设法减少LLM的无效推理。几个经验通用的、不需要推理的工具调用走规则引擎不要每次都给LLM做一次思考Agent上下文尽量精简只注入当前任务相关的信息上下文越长推理越慢复杂任务拆分成子任务并让多个Agent并行处理比让一个Agent串行推理快得多我在这个项目里实际跑下来的数据是规则引擎预处理掉约30%的固定动作请求集群整体吞吐提升了将近一倍而且单请求延迟更稳定。这套MCP加A2A的双协议架构前前后后在我的项目里迭代了四个月。从最初单Agent的单打独斗到现在十几个Agent在集群里各司其职、动态协作最大的感触是协议和框架都是工具真正的难点在于想清楚每个Agent的职责边界并且用标准化的方式让它们安全、可控地协作。如果你正在规划企业的多智能体平台不妨先从一个小业务场景入手用MCP解决工具接入用A2A解决Agent协作跑通之后再把规模慢慢做起来这条路我替你验证过了是走得通的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进