MCP管工具A2A管协作-双协议联合实战

MCP管工具,A2A管协作:双协议联合实战

摘要: MCP让Agent调工具,A2A让Agent之间对话。这篇把两个协议拧在一起,搭一个"搜索+总结+翻译"三Agent工作流。

一、两个协议,各管一件事

前面三篇你一直在跟A2A打交道。你的Agent能收消息、能回消息、能互相委托任务。但有个场景一直没解决:Agent怎么调用外部工具?

比如你想让Agent搜索网页、查数据库、读写文件。A2A不管这些——它只负责Agent之间的通信。管Agent和工具之间连接的,是另一个协议:MCP(Model Context Protocol)。

两个协议的分工很明确:

  • MCP

    (Anthropic出品)= Agent连工具的"USB-C接口"

  • A2A

    (Google出品)= Agent之间对话的"微信协议" 两个协议互补。一个Agent可以通过MCP调用搜索工具获取数据,然后通过A2A把搜索结果发给另一个Agent做分析。这就是2026年Agent开发的完整形态。 今天带你搭一个完整的三Agent工作流。

二、我们要搭什么

业务场景

你给Agent发一个关键词,比如"A2A协议最新进展"。Agent自动执行三步:

  1. 搜索Agent

    通过MCP调用搜索工具,拿到相关文章

  2. 总结Agent

    接收搜索结果,提炼关键信息

  3. 翻译Agent

    把总结翻成英文,返回最终结果

架构图

你 → 翻译Agent(A2A Server,端口10002)
        ↓ A2A
     总结Agent(A2A Server,端口10003)
        ↓ A2A
     搜索Agent(A2A Server,端口10004)
        ↓ MCP
     搜索工具(MCP Server)

搜索Agent同时是A2A Server(接收总结Agent的请求)和MCP Client(调用搜索工具)。一个Agent同时使用两个协议。

三、项目结构

a2a-mcp-workflow/
├── searcher/                    # 搜索Agent(A2A + MCP)
│   ├── server.py
│   ├── task_manager.py
│   └── mcp_config.yaml          # MCP工具配置
├── summarizer/                  # 总结Agent(纯A2A)
│   ├── server.py 
│   └── task_manager.py
├── translator/                  # 翻译Agent(纯A2A)
│   ├── server.py
│   └── task_manager.py
├── client.py
└── pyproject.toml
          

四、搭建搜索Agent(A2A + MCP双协议)

这是最复杂的一个Agent。它需要同时做两件事:

  1. 作为A2A Server,接收总结Agent发来的搜索请求

  2. 作为MCP Client,调用搜索工具获取结果

4.1 配置MCP工具

创建 searcher/ [mcp_config.yaml](http://mcp_config.yaml) ,声明要用的MCP工具:

mcpServers:
  web-search:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-brave-search"]
    env:
      BRAVE_API_KEY: "${BRAVE_API_KEY}"

这个配置告诉Agent:使用Brave Search作为搜索工具(通过MCP协议调用)。你需要一个Brave Search API Key( brave.com/search/api 申请免费额度)。

如果你不想用Brave Search,也可以用一个模拟的文件系统MCP Server做demo:

mcpServers:
  file-tools:
    command: "npx"
    args: [
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp/search-results"
    ]

4.2 搜索Agent的Task Manager

import json
import httpx
from 
            google_a2a.common.server.task_manager
           import InMemoryTaskManager
from 
            google_a2a.common.types
           import (
    Artifact, Message, SendTaskRequest, SendTaskResponse,
    Task, TaskState, TaskStatus,
)

OLLAMA_URL = "http://localhost:11434/api/generate"
OLLAMA_MODEL = "
            qwen2.5:7b"
          


classSearcherTaskManager(InMemoryTaskManager):
"""搜索Agent:接收关键词,通过MCP搜索,返回结果。"""

asyncdefon_send_task(
self, request: SendTaskRequest
    ) -> SendTaskResponse:
awaitself.upsert_task(
            request.params)
          

        task_id = 
            request.params.
          id
        keyword = 
            request.params.message.parts[
          0].text

# 第一步:通过MCP调用搜索工具
        search_results = awaitself._mcp_search(keyword)

# 第二步:用大模型整理搜索结果
        formatted = awaitself._format_results(
            keyword, search_results
        )

        task = awaitself._update_task(
            task_id=task_id,
            task_state=
            TaskState.COMPLETED,
          
            response_text=formatted,
        )

returnSendTaskResponse(id=request.id, result=task)

asyncdef_mcp_search(self, keyword: str) -> str:
"""
        通过MCP协议调用搜索工具。
        这里演示两种方式:真实MCP调用和模拟搜索。
        """
# 方式1:真实MCP调用(需要Brave API Key)
try:
import os
            api_key = 
            os.environ.
          get("BRAVE_API_KEY")
if api_key:
asyncwith httpx.AsyncClient(timeout=15.0) as c:
                    resp = await c.get(
                        "https://
            api.search.brave.com/res/v1/web/search",
          
                        params={"q": keyword, "count": 5},
                        headers={
"X-Subscription-Token": api_key,
"Accept": "application/json",
                        },
                    )
                    data = resp.json()
                    results = []
for item in data.get("web", {}).get("results", []):
                        results.append(
                            f"- {
            item.get('title',
           '')}: "
                            f"{
            item.get('description',
           '')} "
                            f"({
            item.get('url',
           '')})"
                        )
return"\n".join(results) if results else"未找到相关结果"
except Exception:
pass

# 方式2:模拟搜索(demo用)
return (
            f"[搜索结果模拟 - 关键词: {keyword}]\n"
"1. A2A协议是Google推出的Agent间通信标准\n"
"2. 目前已有50+厂商支持A2A协议\n"
"3. A2A协议已捐赠给Linux基金会\n"
"4. 最新版本
            v0.2.5,v1.0开发中\n"
          
"5. A2A与MCP协议互补,形成完整的Agent生态\n"
        )

asyncdef_format_results(
self, keyword: str, results: str
    ) -> str:
"""用大模型整理搜索结果。"""
try:
asyncwith httpx.AsyncClient(timeout=60.0) as c:
                resp = await c.post(
                    OLLAMA_URL,
                    json={
"model": OLLAMA_MODEL,
"prompt": (
                            f"根据以下搜索结果,整理一份关于"
                            f"「{keyword}」的信息摘要,"
                            f"包含5个关键要点:\n{results}"
                        ),
"stream": False,
                    },
                )
                result = resp.json()
return result.get("response", results)
except Exception:
return results

asyncdef_update_task(
self, task_id: str, task_state: TaskState,
        response_text: str
    ) -> Task:
        task = self.tasks[task_id]
        parts = [{"type": "text", "text": response_text}]
        
            task.status
           = TaskStatus(
            state=task_state,
            message=Message(role="agent", parts=parts),
        )
if task_state == 
            TaskState.COMPLETED:
          
            
            task.artifacts
           = [Artifact(parts=parts)]
return task

4.3 搜索Agent的Server

import logging
import click
from 
            google_a2a.common.types
           import (
    AgentSkill, AgentCapabilities, AgentCard
)
from 
            google_a2a.common.server
           import A2AServer
from task_manager import SearcherTaskManager

logging.basicConfig(level=
            logging.INFO)
          


@click.command()
@click.option("--host", default="localhost")
@click.option("--port", default=10004, type=int)
defmain(host, port):
    skill = AgentSkill(
id="search-skill",
        name="Web Searcher",
        description="Searches the web and returns organized results",
        tags=["search", "web", "mcp"],
        examples=["搜索A2A协议最新进展"],
        inputModes=["text"],
        outputModes=["text"],
    )

    agent_card = AgentCard(
        name="Searcher Agent",
        description=(
"Searches the web via MCP protocol and "
"returns organized information."
        ),
        url=f"http://{host}:{port}/",
        version="0.1.0",
        defaultInputModes=["text"],
        defaultOutputModes=["text"],
        capabilities=AgentCapabilities(streaming=False),
        skills=[skill],
    )

    task_manager = SearcherTaskManager()
    server = A2AServer(
        agent_card=agent_card,
        task_manager=task_manager,
        host=host,
        port=port,
    )
    logging.info(
        f"Searcher Agent running at http://{host}:{port}"
    )
    server.start()


if __name__ == "__main__":
main()

五、搭建总结Agent(纯A2A,调用搜索Agent)

总结Agent不直接调MCP工具,它通过A2A协议把搜索任务委托给搜索Agent。

# summarizer/
            task_manager.py
          
import httpx
from 
            google_a2a.common.server.task_manager
           import InMemoryTaskManager
from 
            google_a2a.common.types
           import (
    Artifact, Message, SendTaskRequest, SendTaskResponse,
    Task, TaskState, TaskStatus,
)

SEARCHER_URL = "http://localhost:10004"
OLLAMA_URL = "http://localhost:11434/api/generate"
OLLAMA_MODEL = "
            qwen2.5:7b"
          


classSummarizerTaskManager(InMemoryTaskManager):
"""总结Agent:调用搜索Agent获取信息,然后做总结。"""

asyncdefon_send_task(
self, request: SendTaskRequest
    ) -> SendTaskResponse:
awaitself.upsert_task(
            request.params)
          

        task_id = 
            request.params.
          id
        keyword = 
            request.params.message.parts[
          0].text

# 第一步:通过A2A调用搜索Agent
        search_info = awaitself._call_searcher(keyword)

# 第二步:用大模型做深度总结
        summary = awaitself._summarize(keyword, search_info)

        task = awaitself._update_task(
            task_id=task_id,
            task_state=
            TaskState.COMPLETED,
          
            response_text=summary,
        )

returnSendTaskResponse(id=request.id, result=task)

asyncdef_call_searcher(self, keyword: str) -> str:
"""通过A2A协议调用搜索Agent。"""
try:
asyncwith httpx.AsyncClient(timeout=60.0) as c:
# 发现搜索Agent
                card_resp = await c.get(
                    f"{SEARCHER_URL}/.well-known/
            agent.json"
          
                )
                card = card_resp.json()

# 发送A2A请求
                payload = {
"jsonrpc": "2.0",
"id": "search-request",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{
"type": "text",
"text": keyword,
                            }],
                        }
                    },
                }
                resp = await c.post(
                    card["url"],
                    json=payload,
                    headers={"Content-Type": "application/json"},
                )
                result = resp.json()

# 解析响应
if"result"in result:
                    task_data = result["result"]
                    artifacts = task_data.get("artifacts", [])
if artifacts:
return artifacts[0]["parts"][0]["text"]
                    msg = task_data.get("status", {}).get("message", {})
if msg.get("parts"):
return msg["parts"][0]["text"]
return"搜索Agent无响应"
except Exception as e:
return f"搜索调用失败: {str(e)}"

asyncdef_summarize(
self, keyword: str, info: str
    ) -> str:
"""用大模型做深度总结。"""
try:
asyncwith httpx.AsyncClient(timeout=60.0) as c:
                resp = await c.post(
                    OLLAMA_URL,
                    json={
"model": OLLAMA_MODEL,
"prompt": (
                            f"你是一个专业的研究分析师。请根据以下搜索结果,"
                            f"写一份关于「{keyword}」的深度分析报告。"
                            f"要求:3-5个核心观点,每个观点有具体数据支撑。\n\n"
                            f"搜索结果:\n{info}"
                        ),
"stream": False,
                    },
                )
                result = resp.json()
return result.get("response", info)
except Exception:
return info

asyncdef_update_task(
self, task_id: str, task_state: TaskState,
        response_text: str
    ) -> Task:
        task = self.tasks[task_id]
        parts = [{"type": "text", "text": response_text}]
        
            task.status
           = TaskStatus(
            state=task_state,
            message=Message(role="agent", parts=parts),
        )
if task_state == 
            TaskState.COMPLETED:
          
            
            task.artifacts
           = [Artifact(parts=parts)]
return task

Server部分和上篇类似,端口改为10003,不再赘述。

六、搭建翻译Agent(纯A2A,调用总结Agent)

翻译Agent的逻辑跟上篇一模一样——接收你的消息,翻译文本,调用另一个Agent。只不过这次它调用的是总结Agent而不是摘要Agent。

# translator/
            task_manager.py(核心部分)
          

SUMMARIZER_URL = "http://localhost:10003"

classTranslatorTaskManager(InMemoryTaskManager):
asyncdefon_send_task(self, request):
# ...(同上篇,省略)...

# 调用总结Agent(而不是摘要Agent)
        summary = awaitself._call_summarizer(input_text)

# 翻译结果
        translation = awaitself._translate(summary)

        final_text = (
            f"【中文总结】\n{summary}\n\n"
            f"【英文翻译】\n{translation}"
        )

        task = awaitself._update_task(
            task_id=task_id,
            task_state=
            TaskState.COMPLETED,
          
            response_text=final_text,
        )
returnSendTaskResponse(id=request.id, result=task)

asyncdef_call_summarizer(self, text: str) -> str:
"""通过A2A调用总结Agent。"""
# 代码与上篇的_call_summarizer相同
# 只是URL改为SUMMARIZER_URL = "http://localhost:10003"
# ...

七、启动测试

7.1 三个Agent全部启动

开三个终端:

# 终端1:搜索Agent(端口10004)
cd searcher && python 
            server.py
          

# 终端2:总结Agent(端口10003)
cd summarizer && python 
            server.py
          

# 终端3:翻译Agent(端口10002)
cd translator && python 
            server.py
          

三个终端都看到Uvicorn启动成功后,进入下一步。

7.2 客户端测试

# 
            client.py
          
import asyncio
import httpx
from 
            a2a.client
           import (
    A2ACardResolver, ClientConfig, create_client
)
from 
            a2a.helpers
           import new_text_message
from 
            a2a.types.a2a_pb2
           import Role, SendMessageRequest


asyncdefmain():
print("=== A2A + MCP 双协议工作流测试 ===\n")

asyncwith httpx.AsyncClient() as httpx_client:
# 发现翻译Agent
        resolver = A2ACardResolver(
            httpx_client=httpx_client,
            base_url="http://localhost:10002",
        )
        agent_card = await resolver.get_agent_card()
print(f"连接到: {
            agent_card.name}\n"
          )

# 创建客户端
        client = awaitcreate_client(
            agent=agent_card,
            client_config=ClientConfig(streaming=False),
        )

# 发送关键词
        keyword = "A2A协议最新进展"
        message = new_text_message(
            keyword, role=
            Role.ROLE_USER
          
        )
        request = SendMessageRequest(message=message)

print(f"发送关键词: {keyword}")
print("工作流: 翻译Agent → 总结Agent → 搜索Agent(MCP)")
print("=" * 60)

asyncfor chunk in client.send_message(request):
ifhasattr(chunk, 'artifacts') and 
            chunk.artifacts:
          
for part in 
            chunk.artifacts[
          0].parts:
print(
            part.text)
          

await client.close()


if __name__ == "__main__":
    asyncio.run(main())

7.3 预期输出

=== A2A + MCP 双协议工作流测试 ===

连接到: Translator Agent

发送关键词: A2A协议最新进展
工作流: 翻译Agent → 总结Agent → 搜索Agent(MCP)
============================================================
【中文总结】
1. A2A协议是Google于2025年推出的Agent间通信标准...
2. 目前已有50+厂商(Salesforce、微软、SAP等)支持...
3. 协议已捐赠Linux基金会,采用Apache 2.0开源许可...
4. 最新版本v0.2.5,与MCP协议互补...
5. 微软Copilot平台已同时支持MCP和A2A双协议...

【英文翻译】
1. The A2A protocol is an agent-to-agent communication
standard launched by Google in2025...
...
============================================================

一个关键词进去,三步处理出来。

八、MCP和A2A在代码层面的差异

你可能注意到了:搜索Agent里调MCP工具和调A2A Agent的代码长得不一样。

MCP调用(Agent→Tool):

# 通过MCP协议调用搜索工具
# 实际上是调用MCP Server暴露的resource/tool接口
resp = await c.get(
    "https://
            api.search.brave.com/res/v1/web/search",
          
    headers={"X-Subscription-Token": api_key},
)

MCP调用更像是传统的API调用——你知道工具的地址和参数,直接HTTP请求。

A2A调用(Agent→Agent):

# 通过A2A协议调用另一个Agent
# 1. 发现Agent
card = await c.get(f"{url}/.well-known/
            agent.json"
          ).json()
# 2. 发送JSON-RPC请求
payload = {
"jsonrpc": "2.0",
"id": "req-001",
"method": "message/send",
"params": {"message": {"role": "user", "parts": [...]}}
}
resp = await c.post(card["url"], json=payload)

A2A调用是"发现→通信"两步走。先拿到Agent Card了解对方能力,再发JSON-RPC请求。

两者的本质区别:

维度MCPA2A
调用对象工具/数据源其他Agent
交互方式请求-响应有状态的任务(Task)
发现机制配置文件/注册中心Agent Card自动发现
数据格式取决于具体工具JSON-RPC 2.0统一格式

九、生产环境的建议

这个demo用的是模拟搜索。在生产环境里,你可能需要:

  1. 用真实的MCP Server。

    Brave Search、GitHub、Playwright、文件系统等都有现成的MCP Server。

  2. 给Agent加认证。

    结合上一篇的OAuth 2.0认证方案。

  3. 加错误处理和重试。

    网络请求可能失败,需要超时和重试机制。

  4. 加任务队列。

    多个请求并发时,用消息队列(如Redis)管理任务。

  5. 加监控和日志。

    记录每次Agent间调用的耗时和状态。

十、系列总结

四篇文章,从零到一:

  1. 第1篇

    :搭了一个Echo Agent,搞懂A2A的基本概念

  2. 第2篇

    :搭了翻译Agent和摘要Agent,实现双Agent协作

  3. 第3篇

    :给Agent加了API Key / JWT / OAuth 2.0三把锁

  4. 第4篇(本篇)

    :MCP + A2A双协议联动,搭了三Agent工作流 A2A协议还在快速迭代,但核心设计已经稳定。你现在掌握的知识足够开始搭建自己的多Agent系统了。 2026年是Agent协议之年。就像2010年学HTTP一样——现在入场,刚刚好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值