三协议合一实战:用MCP+A2A+ANP搭建一个智能研究助手
写在前面
MCP、A2A、ANP三个协议我们都分别讲过了------MCP管工具接入,A2A管Agent间协作,ANP管服务发现。但分开讲总觉得不够直观,很多人问:"这三个协议放在一起到底怎么用?能不能给一个完整的例子?"
今天这篇文章就来满足这个需求。我会带大家从零搭建一个"智能研究助手"系统------用户输入一个研究主题,系统自动完成搜索资料、阅读网页、整合信息、撰写报告的全流程。整个系统会同时用到MCP、A2A、ANP三个协议,让你直观看到它们是怎么协同工作的。
这个项目我自己花了一个周末做出来,虽然不算复杂,但跑通的那一刻还是挺有成就感的。希望这篇文章能帮你把三个协议的知识串起来。
一、项目目标和系统架构
先明确一下我们要做什么。智能研究助手的核心功能是:
用户输入一个研究主题(比如"大模型推理优化的最新进展"),系统自动:
- 搜索相关资料
- 阅读搜索到的网页
- 整合信息,撰写一份结构化的研究报告
为了实现这个功能,我们设计四个Agent:
- 主控Agent(Orchestrator):接收用户请求,分解任务,协调其他Agent,汇总最终结果
- 搜索Agent(Searcher):负责搜索相关资料,通过MCP调用搜索引擎工具
- 阅读Agent(Reader):负责抓取和阅读网页内容,通过MCP调用网页抓取工具
- 写作Agent(Writer):负责整合资料,撰写研究报告
三个协议在系统中的分工:
用户 → 主控Agent
│
│ ANP:发现搜索/阅读/写作Agent的地址
▼
┌────┴────┐
▼ ▼ ▼
搜索Agent 阅读Agent 写作Agent
│ │
│ MCP │ MCP
▼ ▼
搜索引擎 网页抓取
- ANP:主控Agent通过ANP发现其他三个Agent的地址,不需要硬编码
- A2A:主控Agent通过A2A跟其他三个Agent通信,委托任务、获取结果
- MCP:搜索Agent和阅读Agent通过MCP调用外部工具(搜索引擎、网页抓取)
你看,三个协议各司其职,组合起来就是一个完整的多Agent系统。
二、第一步:搭建ANP注册中心
老规矩,先把基础设施搭起来。注册中心是整个系统的"通讯录",所有Agent启动时都来这里注册。
python
# registry.py
from helloagents.anp import ANPRegistry
registry = ANPRegistry(
host="0.0.0.0",
port=9000,
health_check_interval=15,
expire_after=45
)
if __name__ == "__main__":
print("ANP注册中心已启动,端口9000")
registry.run()
就这么简单。运行起来之后,所有Agent都可以注册到这里。
三、第二步:实现MCP工具服务器
搜索Agent和阅读Agent需要调用外部工具。我们用MCP来封装这些工具。
搜索工具MCP服务器
python
# search_mcp_server.py
from mcp.server import Server
from mcp.types import Tool, TextContent
import asyncio
server = Server("search-tools")
@server.list_tools()
async def list_tools():
return [
Tool(
name="web_search",
description="搜索互联网,返回相关网页的标题、链接和摘要",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"num_results": {"type": "integer", "description": "返回结果数量", "default": 5}
},
"required": ["query"]
}
)
]
@server.call_tool()
async def call_tool(name, arguments):
if name == "web_search":
query = arguments["query"]
num = arguments.get("num_results", 5)
# 实际项目中这里调用真实的搜索API(如Bing、Google)
# 这里用模拟数据
results = [
{"title": f"{query}相关文章1", "url": "https://example.com/1", "snippet": "这是第一篇文章的摘要..."},
{"title": f"{query}相关文章2", "url": "https://example.com/2", "snippet": "这是第二篇文章的摘要..."},
{"title": f"{query}相关文章3", "url": "https://example.com/3", "snippet": "这是第三篇文章的摘要..."},
][:num]
return [TextContent(type="text", text=str(results))]
return [TextContent(type="text", text="未知工具")]
if __name__ == "__main__":
asyncio.run(server.run_stdio())
网页抓取工具MCP服务器
python
# fetch_mcp_server.py
from mcp.server import Server
from mcp.types import Tool, TextContent
import asyncio
server = Server("fetch-tools")
@server.list_tools()
async def list_tools():
return [
Tool(
name="fetch_webpage",
description="抓取指定URL的网页内容,返回正文文本",
inputSchema={
"type": "object",
"properties": {
"url": {"type": "string", "description": "网页URL"},
"max_length": {"type": "integer", "description": "最大返回字符数", "default": 5000}
},
"required": ["url"]
}
)
]
@server.call_tool()
async def call_tool(name, arguments):
if name == "fetch_webpage":
url = arguments["url"]
max_len = arguments.get("max_length", 5000)
# 实际项目中这里用requests/httpx抓取真实网页
# 这里用模拟数据
content = f"这是从{url}抓取的网页正文内容。文章讨论了相关主题的核心观点、方法论和最新进展..."[:max_len]
return [TextContent(type="text", text=content)]
return [TextContent(type="text", text="未知工具")]
if __name__ == "__main__":
asyncio.run(server.run_stdio())
这两个MCP服务器分别提供搜索和网页抓取能力。实际项目中,你可以把模拟数据换成真实的API调用。
四、第三步:实现各个Agent
搜索Agent
搜索Agent通过MCP调用搜索工具,然后把结果返回给主控Agent。
python
# search_agent.py
from helloagents import A2AAgent, TaskResult, MCPTransport
import asyncio
class SearchAgent(A2AAgent):
name = "搜索助手"
description = "搜索互联网资料"
capabilities = ["research.search"]
anp_registry_url = "http://localhost:9000"
anp_agent_host = "localhost"
anp_agent_port = 8001
async def on_start(self):
# 连接到搜索MCP服务器
self.search_transport = MCPTransport(command="python", args=["search_mcp_server.py"])
await self.search_transport.connect()
async def handle_task(self, task):
query = task.input.get("query", "")
num = task.input.get("num_results", 5)
# 通过MCP调用搜索工具
result = await self.search_transport.call_tool(
name="web_search",
arguments={"query": query, "num_results": num}
)
return TaskResult.success({
"query": query,
"results": eval(result.content[0].text) # 实际项目中用json.loads
})
if __name__ == "__main__":
SearchAgent().run(host="0.0.0.0", port=8001)
阅读Agent
阅读Agent通过MCP调用网页抓取工具,读取网页内容。
python
# reader_agent.py
from helloagents import A2AAgent, TaskResult, MCPTransport
class ReaderAgent(A2AAgent):
name = "阅读助手"
description = "抓取和阅读网页内容"
capabilities = ["research.read"]
anp_registry_url = "http://localhost:9000"
anp_agent_host = "localhost"
anp_agent_port = 8002
async def on_start(self):
self.fetch_transport = MCPTransport(command="python", args=["fetch_mcp_server.py"])
await self.fetch_transport.connect()
async def handle_task(self, task):
urls = task.input.get("urls", [])
contents = []
for url in urls[:3]: # 最多读3个网页,避免太长
result = await self.fetch_transport.call_tool(
name="fetch_webpage",
arguments={"url": url, "max_length": 3000}
)
contents.append({"url": url, "content": result.content[0].text})
return TaskResult.success({
"urls": urls,
"contents": contents,
"summary": f"已阅读{len(contents)}个网页"
})
if __name__ == "__main__":
ReaderAgent().run(host="0.0.0.0", port=8002)
写作Agent
写作Agent接收资料,撰写研究报告。这个Agent不需要MCP工具,纯靠大模型能力。
python
# writer_agent.py
from helloagents import A2AAgent, TaskResult
class WriterAgent(A2AAgent):
name = "写作助手"
description = "整合资料撰写研究报告"
capabilities = ["research.write"]
anp_registry_url = "http://localhost:9000"
anp_agent_host = "localhost"
anp_agent_port = 8003
async def handle_task(self, task):
topic = task.input.get("topic", "")
materials = task.input.get("materials", [])
# 实际项目中这里调用大模型生成报告
# 这里用模板模拟
report = f"""# {topic}研究报告
## 概述
本报告围绕"{topic}"主题,基于搜集到的资料进行整理和分析。
## 核心观点
1. 观点一:基于资料分析,该领域的主要发展方向是...
2. 观点二:关键技术挑战包括...
3. 观点三:未来趋势可能是...
## 资料来源
共参考了{len(materials)}份资料。
## 结论
{topic}是一个快速发展的领域,值得持续关注。
"""
return TaskResult.success({
"topic": topic,
"report": report,
"word_count": len(report)
})
if __name__ == "__main__":
WriterAgent().run(host="0.0.0.0", port=8003)
主控Agent
主控Agent是整个系统的核心,它通过ANP发现其他Agent,通过A2A委托任务,最后汇总结果。
python
# orchestrator.py
from helloagents import A2AAgent, ANPClient, A2AClient, TaskResult
import asyncio
class OrchestratorAgent(A2AAgent):
name = "研究助手"
description = "智能研究助手,自动完成资料搜索、阅读和报告撰写"
capabilities = ["research.plan"]
anp_registry_url = "http://localhost:9000"
async def on_start(self):
self.anp = ANPClient(self.anp_registry_url)
async def handle_task(self, task):
topic = task.input.get("topic", "")
print(f"收到研究任务:{topic}")
# 第一步:通过ANP发现搜索Agent
search_agent = await self.anp.discover_one(capability="research.search")
print(f"发现搜索Agent:{search_agent.name}")
# 第二步:委托搜索任务
search_client = await A2AClient.connect(search_agent.address)
search_result = await search_client.create_task_and_wait(
input={"query": topic, "num_results": 5},
timeout=60
)
await search_client.close()
print(f"搜索完成,找到{len(search_result.output['results'])}个结果")
# 第三步:通过ANP发现阅读Agent
reader_agent = await self.anp.discover_one(capability="research.read")
print(f"发现阅读Agent:{reader_agent.name}")
# 第四步:委托阅读任务
urls = [r["url"] for r in search_result.output["results"]]
reader_client = await A2AClient.connect(reader_agent.address)
reader_result = await reader_client.create_task_and_wait(
input={"urls": urls},
timeout=120
)
await reader_client.close()
print(f"阅读完成,读取了{len(reader_result.output['contents'])}个网页")
# 第五步:通过ANP发现写作Agent
writer_agent = await self.anp.discover_one(capability="research.write")
print(f"发现写作Agent:{writer_agent.name}")
# 第六步:委托写作任务
writer_client = await A2AClient.connect(writer_agent.address)
writer_result = await writer_client.create_task_and_wait(
input={
"topic": topic,
"materials": reader_result.output["contents"]
},
timeout=120
)
await writer_client.close()
print("报告撰写完成")
# 第七步:汇总结果
return TaskResult.success({
"topic": topic,
"report": writer_result.output["report"],
"sources": search_result.output["results"],
"stats": {
"search_results": len(search_result.output["results"]),
"pages_read": len(reader_result.output["contents"]),
"report_words": writer_result.output["word_count"]
}
})
if __name__ == "__main__":
OrchestratorAgent().run(host="0.0.0.0", port=8000)
主控Agent的逻辑很清晰:发现 → 委托搜索 → 委托阅读 → 委托写作 → 汇总。每一步都通过ANP发现Agent,通过A2A委托任务。
五、运行整个系统
现在所有组件都写好了,我们按顺序启动:
bash
# 终端1:启动注册中心
python registry.py
# 终端2:启动搜索Agent(会自动连接MCP搜索工具)
python search_agent.py
# 终端3:启动阅读Agent(会自动连接MCP抓取工具)
python reader_agent.py
# 终端4:启动写作Agent
python writer_agent.py
# 终端5:启动主控Agent
python orchestrator.py
全部启动后,你可以在注册中心查一下,确认四个Agent都注册成功了:
bash
curl http://localhost:9000/agents
然后给主控Agent发一个任务:
bash
curl -X POST http://localhost:8000/task \
-H "Content-Type: application/json" \
-d '{"input": {"topic": "大模型推理优化的最新进展"}}'
你应该能看到主控Agent的终端输出类似这样的日志:
收到研究任务:大模型推理优化的最新进展
发现搜索Agent:搜索助手
搜索完成,找到5个结果
发现阅读Agent:阅读助手
阅读完成,读取了3个网页
发现写作Agent:写作助手
报告撰写完成
最后返回一份完整的研究报告。整个过程中,主控Agent完全不需要知道其他Agent的地址------全靠ANP自动发现;Agent之间的通信全靠A2A;工具调用全靠MCP。三个协议完美配合。
六、系统中的协议协作细节
跑通之后,我想再深入聊聊三个协议在这个系统里是怎么协作的,一些细节可能你之前没注意到。
ANP的作用:解耦地址和能力
在这个系统里,主控Agent没有硬编码任何其他Agent的地址。它只知道"我需要一个能搜索的Agent",ANP就帮它找到了。这意味着:
- 搜索Agent换了地址,主控Agent不需要改
- 部署多个搜索Agent实例,主控Agent自动负载均衡
- 搜索Agent挂了,ANP自动摘除,主控Agent不会再调用它
这种解耦在系统规模变大的时候尤其重要。
A2A的作用:标准化Agent间协作
主控Agent跟其他Agent的通信全是A2A协议。不管是搜索Agent还是写作Agent,主控Agent用的都是同一套API:connect → create_task_and_wait → close。不需要为不同的Agent写不同的调用逻辑。
而且A2A的Task机制自带状态跟踪------主控Agent可以随时查询任务进度,任务失败了也有明确的错误信息。
MCP的作用:工具即插即用
搜索Agent需要搜索引擎,阅读Agent需要网页抓取。这些工具都是通过MCP接入的。如果以后想把搜索引擎从Bing换成Google,只需要换一个MCP服务器,搜索Agent的代码一行都不用改。
这就是协议的力量------每一层都可以独立替换,不影响其他层。
七、我踩的坑
做这个项目的过程中,踩了不少坑,挑几个典型的说说:
坑1:MCP服务器的stdio模式跟Agent的stdout冲突
搜索Agent本身是一个HTTP服务,会输出日志到stdout。但它连接的MCP服务器也是stdio模式,MCP协议需要用stdout传消息。结果日志混进了MCP消息,导致解析失败。
解决办法:把Agent的日志输出到stderr或者文件,不要用stdout。MCP的stdio传输对stdout是独占的。
坑2:ANP发现到的Agent可能已经挂了
有一次我把搜索Agent关了,但还没到过期时间,ANP还返回了它的地址。主控Agent连接的时候就超时了。
解决办法:客户端一定要做超时和重试。发现到的Agent不一定是活的,连接失败了就换一个,或者等ANP自动摘除后重试。
坑3:任务超时时间要设合理
阅读Agent要抓多个网页,可能比较慢。我一开始把超时设成30秒,经常超时。后来改成120秒就稳定了。不同类型的任务超时时间不一样,搜索可能30秒够了,阅读和写作可能需要更长。
坑4:大模型生成的报告可能引用不存在的资料
写作Agent拿到资料后,大模型可能会"幻觉"出一些资料里没有的内容。这个问题靠协议解决不了,需要在Prompt里强调"只能基于提供的资料撰写,不要编造",或者加一个事实核查的环节。
坑5:组件太多,调试困难
四个Agent + 两个MCP服务器 + 一个注册中心,七个进程同时跑,出了问题不知道是哪一层的锅。
解决办法:每个组件都加详细的日志,并且日志里带上请求ID,这样可以追踪一个请求在各个组件中的流转路径。
八、可以扩展的方向
这个项目只是一个最小可用版本,还有很多可以扩展的方向:
1. 并行化:目前搜索、阅读、写作是串行的。其实搜索完成后,可以并行阅读多个网页,甚至并行写作不同章节。
2. 迭代优化:写作Agent写完报告后,可以再让一个"评审Agent"审阅,提出修改意见,然后写作Agent修改,多轮迭代提升质量。
3. 缓存机制:相同的研究主题可以缓存结果,避免重复搜索和阅读。
4. 用户反馈:用户可以对报告打分和评论,系统根据反馈优化后续的搜索和写作策略。
5. 多模态:目前只处理文本,以后可以加入图片、视频、PDF等多模态资料的处理。
这些扩展都不需要改动协议层------MCP加新工具、A2A加新Agent、ANP自动发现,架构天然支持扩展。
写在最后
做完这个项目,我最大的感受是:协议让复杂系统变得可控。
如果没有这些协议,四个Agent之间的通信、工具调用、服务发现,每一处都得自己写定制化的代码,系统稍微大一点就会变成一团乱麻。有了MCP、A2A、ANP这些标准协议,每个组件只需要关注自己的业务逻辑,跟其他组件的交互全靠标准化的协议,系统就清晰多了。
这篇文章是10.4节ANP协议实战的收尾,也是整个通信协议章节的倒数第二篇。从"为什么需要协议"到"三协议合一实战",我们把Agent通信的核心内容都过了一遍。
下一篇会进入10.5节,聊聊怎么构建自定义MCP服务器------这是你把自己的工具接入Agent世界的关键技能。
你们有没有做过类似的多Agent项目?用的什么技术栈?遇到过什么有意思的问题?欢迎在评论区交流。
下一篇预告:创建你的第一个MCP服务器。我们会从零开始,手把手教你写一个功能完整的MCP服务器,把你自己的工具暴露给Agent使用。