1. 资料地址
hello-agents/docs/chapter10/第十章 智能体通信协议.md at main · datawhalechina/hello-agents · GitHub
2. 环境安装
按教材安装即可:
pip install "hello-agents[protocol]==0.2.2"
装完后确认:
python -c "from hello_agents.tools import MCPTool, A2ATool, ANPTool; print('ok')"
错误是系统找不到 npx。先看你的脚本在连什么 MCP 服务器。
npx: None
node: None
确认:环境里没有 Node.js,所以找不到 npx。脚本通过 npx 启动 @modelcontextprotocol/server-filesystem。
原因:脚本用 npx 拉起 MCP 文件系统服务器,但当前环境没有 Node.js,所以报 No such file or directory: 'npx'。
处理:安装 Node.js(会带上 npx):
# 方式一:conda(推荐,和当前 agent 环境一致)
conda install -n agent -c conda-forge nodejs -y
# 方式二:系统包
# apt-get update && apt-get install -y nodejs npm
装完验证:
node -v
npx -v
3. 调试
测试1: 01_TestConnect
import asyncio
from hello_agents.protocols import MCPClient
async def connect_to_server():
# 方式1:连接到社区提供的文件系统服务器
# npx会自动下载并运行@modelcontextprotocol/server-filesystem包
client = MCPClient([
"npx", "-y",
"@modelcontextprotocol/server-filesystem",
"." # 指定根目录
])
# 使用async with确保连接正确关闭
async with client:
# 在这里使用client
tools = await client.list_tools()
print(f"可用工具: {[t['name'] for t in tools]}")
# 方式2:连接到自定义的Python MCP服务器
client = MCPClient(["python", "my_mcp_server.py"])
async with client:
# 使用client...
pass
# 运行异步函数
asyncio.run(connect_to_server())
async def discover_tools():
client = MCPClient(["npx", "-y", "@modelcontextprotocol/server-filesystem", "."])
async with client:
# 获取所有可用工具
tools = await client.list_tools()
print(f"服务器提供了 {len(tools)} 个工具:")
for tool in tools:
print(f"\n工具名称: {tool['name']}")
print(f"描述: {tool.get('description', '无描述')}")
# 打印参数信息
if 'inputSchema' in tool:
schema = tool['inputSchema']
if 'properties' in schema:
print("参数:")
for param_name, param_info in schema['properties'].items():
param_type = param_info.get('type', 'any')
param_desc = param_info.get('description', '')
print(f" - {param_name} ({param_type}): {param_desc}")
asyncio.run(discover_tools())
报错:
RuntimeError: Event loop is closed
Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x7f69583048b0>
Traceback (most recent call last):
File "/data//envs/agent/lib/python3.10/asyncio/base_subprocess.py", line 126, in __del__
self.close()
File "/data//envs/agent/lib/python3.10/asyncio/base_subprocess.py", line 104, in close
proto.pipe.close()
File "/data//envs/agent/lib/python3.10/asyncio/unix_events.py", line 547, in close
self._close(None)
File "/data//envs/agent/lib/python3.10/asyncio/unix_events.py", line 571, in _close
self._loop.call_soon(self._call_connection_lost, exc)
File "/data//envs/agent/lib/python3.10/asyncio/base_events.py", line 753, in call_soon
self._check_closed()
File "/data//envs/agent/lib/python3.10/asyncio/base_events.py", line 515, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
测试2: 01_TestConnect
修改为:
import asyncio
import sys
from hello_agents.protocols import MCPClient
SERVER_CMD = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "."]
def _ignore_closed_loop_on_subprocess_del(unraisable):
"""过滤 asyncio 子进程管道在 loop 关闭后析构时的无害报错。"""
if (
unraisable.exc_type is RuntimeError
and "Event loop is closed" in str(unraisable.exc_value)
and getattr(unraisable.object, "__qualname__", "")
== "BaseSubprocessTransport.__del__"
):
return
sys.__unraisablehook__(unraisable)
sys.unraisablehook = _ignore_closed_loop_on_subprocess_del
async def connect_to_server(client: MCPClient):
tools = await client.list_tools()
print(f"可用工具: {[t['name'] for t in tools]}")
async def discover_tools(client: MCPClient):
tools = await client.list_tools()
print(f"服务器提供了 {len(tools)} 个工具:")
for tool in tools:
print(f"\n工具名称: {tool['name']}")
print(f"描述: {tool.get('description', '无描述')}")
schema = tool.get("input_schema") or tool.get("inputSchema") or {}
if "properties" in schema:
print("参数:")
for param_name, param_info in schema["properties"].items():
param_type = param_info.get("type", "any")
param_desc = param_info.get("description", "")
print(f" - {param_name} ({param_type}): {param_desc}")
async def main():
async with MCPClient(SERVER_CMD) as client:
await connect_to_server(client)
await discover_tools(client)
if __name__ == "__main__":
asyncio.run(main())
测试3: 02_Connect2MCP
# 02_Connect2MCP.py
import asyncio
import sys
from hello_agents.protocols import MCPClient
SERVER_CMD = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "."]
def _ignore_closed_loop_on_subprocess_del(unraisable):
"""过滤 asyncio 子进程管道在 loop 关闭后析构时的无害报错。"""
if (
unraisable.exc_type is RuntimeError
and "Event loop is closed" in str(unraisable.exc_value)
and getattr(unraisable.object, "__qualname__", "")
== "BaseSubprocessTransport.__del__"
):
return
sys.__unraisablehook__(unraisable)
sys.unraisablehook = _ignore_closed_loop_on_subprocess_del
async def connect_to_server(client: MCPClient):
tools = await client.list_tools()
print(f"可用工具: {[t['name'] for t in tools]}")
async def discover_tools(client: MCPClient):
tools = await client.list_tools()
print(f"服务器提供了 {len(tools)} 个工具:")
for tool in tools:
print(f"\n工具名称: {tool['name']}")
print(f"描述: {tool.get('description', '无描述')}")
schema = tool.get("input_schema") or tool.get("inputSchema") or {}
if "properties" in schema:
print("参数:")
for param_name, param_info in schema["properties"].items():
param_type = param_info.get("type", "any")
param_desc = param_info.get("description", "")
print(f" - {param_name} ({param_type}): {param_desc}")
async def use_tools(client: MCPClient):
result = await client.call_tool("read_file", {"path": "my_README.md"})
print(f"文件内容:\n{result}")
result = await client.call_tool("list_directory", {"path": "."})
print(f"当前目录文件:{result}")
result = await client.call_tool("write_file", {
"path": "output.txt",
"content": "Hello from MCP!"
})
print(f"写入结果:{result}")
async def safe_tool_call(client: MCPClient):
try:
result = await client.call_tool("read_file", {"path": "nonexistent.txt"})
print(result)
except Exception as e:
print(f"工具调用失败: {e}")
# 可以选择重试、使用默认值或向用户报告错误
async def main():
async with MCPClient(SERVER_CMD) as client:
await connect_to_server(client)
await discover_tools(client)
await use_tools(client)
await safe_tool_call(client)
if __name__ == "__main__":
asyncio.run(main())
输出:
It will be compatible before version 2.0.0.
from authlib.jose import JsonWebKey, JsonWebToken
📝 使用 Stdio 传输 (命令): npx -y @modelcontextprotocol/server-filesystem .
🔗 连接到 MCP 服务器...
Secure MCP Filesystem Server running on stdio
✅ 连接成功!
Client does not support MCP Roots, using allowed directories set from server args: [
'/data/py_workspace/agent_learning/hello_agents/chapter10'
]
可用工具: ['read_file', 'read_text_file', 'read_media_file', 'read_multiple_files', 'write_file', 'edit_file', 'create_directory', 'list_directory', 'list_directory_with_sizes', 'directory_tree', 'move_file', 'search_files', 'get_file_info', 'list_allowed_directories']
服务器提供了 14 个工具:
工具名称: read_file
描述: Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.
参数:
- path (string):
- tail (number): If provided, returns only the last N lines of the file
- head (number): If provided, returns only the first N lines of the file
测试4: 03_GitHubMCP
和 02_Connect2MCP.py 是同一类问题。先看你的 03_GitHubMCP.py。
和 02_Connect2MCP.py 是同一类问题,不是 GitHub MCP 调用失败。
从终端输出看,你的脚本已经正常跑完了:工具列表和仓库搜索结果都打印出来了。报错出现在程序退出之后。
原因:
MCPTool 通过 npx 启动了一个子进程
主程序结束时,asyncio 的事件循环已经关闭
子进程管道对象在垃圾回收时还想往已关闭的 loop 注册清理回调
于是出现 RuntimeError: Event loop is closed
这是 Python 3.10 + asyncio 子进程清理时的无害警告,可以忽略。
如果想消掉它,在脚本开头加上这段(和 02_Connect2MCP.py 一样):
import sys
def _ignore_closed_loop_on_subprocess_del(unraisable):
if (
unraisable.exc_type is RuntimeError
and "Event loop is closed" in str(unraisable.exc_value)
and getattr(unraisable.object, "__qualname__", "")
== "BaseSubprocessTransport.__del__"
):
return
sys.__unraisablehook__(unraisable)
sys.unraisablehook = _ignore_closed_loop_on_subprocess_del
结论: GitHub MCP 功能本身没问题;这只是退出时的清理噪音。
# 03_GitHubMCP.py
from hello_agents.tools import MCPTool
import sys
def _ignore_closed_loop_on_subprocess_del(unraisable):
if (
unraisable.exc_type is RuntimeError
and "Event loop is closed" in str(unraisable.exc_value)
and getattr(unraisable.object, "__qualname__", "")
== "BaseSubprocessTransport.__del__"
):
return
sys.__unraisablehook__(unraisable)
sys.unraisablehook = _ignore_closed_loop_on_subprocess_del
github_tool = MCPTool(
server_command=["npx", "-y", "@modelcontextprotocol/server-github"]
)
# 1. 列出可用工具
print("📋 可用工具:")
result = github_tool.run({"action": "list_tools"})
print(result)
# 2. 搜索仓库
print("\n🔍 搜索仓库:")
result = github_tool.run({
"action": "call_tool",
"tool_name": "search_repositories",
"arguments": {
"query": "AI agents language:python",
"page": 1,
"perPage": 3
}
})
print(result)
测试5:04_MCPTransport
# 04_MCPTransport.py
import sys
from hello_agents.tools import MCPTool
def _ignore_mcp_cleanup_warnings(unraisable):
"""过滤 MCP 子进程在程序退出时的无害清理警告。"""
if unraisable.exc_type is RuntimeError and "Event loop is closed" in str(unraisable.exc_value):
return
if (
unraisable.exc_type is AttributeError
and "_stop_event" in str(unraisable.exc_value)
and getattr(unraisable.object, "__qualname__", "") == "StdioTransport.__del__"
):
return
sys.__unraisablehook__(unraisable)
sys.unraisablehook = _ignore_mcp_cleanup_warnings
# 1. Memory Transport - 内存传输(用于测试)
# mcp_tool = MCPTool()
# 2. Stdio Transport - 标准输入输出传输(本地开发)
# mcp_tool = MCPTool(server_command=["python", "my_mcp_server.py"])
# 3. Stdio Transport with Args - 带参数的命令传输
# mcp_tool = MCPTool(server_command=["python", "my_mcp_server.py", "--debug"])
# 4. Stdio Transport - 社区服务器(npx方式)
# mcp_tool = MCPTool(server_command=["npx", "-y", "@modelcontextprotocol/server-filesystem", "."])
# 5. HTTP/SSE/StreamableHTTP Transport
# 注意:MCPTool 主要用于 Stdio 和 Memory 传输
# 对于 HTTP/SSE 等远程传输,建议直接使用 MCPClient
# 使用内置演示服务器(Memory 传输)
mcp_tool = MCPTool()
# 列出可用工具
result = mcp_tool.run({"action": "list_tools"})
print(result)
# 调用工具
result = mcp_tool.run({
"action": "call_tool",
"tool_name": "add",
"arguments": {
"a": 1,
"b": 2
}
})
print(result)
# 方式1:使用自定义Python服务器
mcp_tool = MCPTool(server_command=["python", "my_mcp_server.py"])
# 方式2:使用社区服务器(文件系统)
mcp_tool = MCPTool(server_command=["npx", "-y", "@modelcontextprotocol/server-filesystem", "."])
# 列出工具
result = mcp_tool.run({"action": "list_tools"})
print(result)
# 调用工具
result = mcp_tool.run({
"action": "call_tool",
"tool_name": "read_file",
"arguments": {"path": "my_README.md"}
})
print(result)
测试6:05_UseMCPToolInAgent
# 05_UseMCPToolInAgent.py
from dotenv import load_dotenv
load_dotenv() # 从当前目录的 .env 加载 LLM_API_KEY、LLM_BASE_URL 等配置
from hello_agents import SimpleAgent, HelloAgentsLLM
from hello_agents.tools import MCPTool
import sys
def _ignore_mcp_cleanup_warnings(unraisable):
"""过滤 MCP 子进程在程序退出时的无害清理警告。"""
if unraisable.exc_type is RuntimeError and "Event loop is closed" in str(unraisable.exc_value):
return
if (
unraisable.exc_type is AttributeError
and "_stop_event" in str(unraisable.exc_value)
and getattr(unraisable.object, "__qualname__", "") == "StdioTransport.__del__"
):
return
sys.__unraisablehook__(unraisable)
sys.unraisablehook = _ignore_mcp_cleanup_warnings
print("=" * 70)
print("方式1:使用内置演示服务器")
print("=" * 70)
agent = SimpleAgent(name="助手", llm=HelloAgentsLLM())
# 无需任何配置,自动使用内置演示服务器
# 内置服务器提供:add, subtract, multiply, divide, greet, get_system_info
mcp_tool = MCPTool() # 默认name="mcp"
agent.add_tool(mcp_tool)
# 智能体可以使用内置工具
response = agent.run("计算 123 + 456")
print(response) # 智能体会自动调用add工具
print("\n" + "=" * 70)
print("方式2:连接外部MCP服务器(使用多个服务器)")
print("=" * 70)
# 重要:为每个MCP服务器指定不同的name,避免工具名称冲突
# 示例1:连接到社区提供的文件系统服务器
fs_tool = MCPTool(
name="filesystem", # 指定唯一名称
description="访问本地文件系统",
server_command=["npx", "-y", "@modelcontextprotocol/server-filesystem", "."]
)
agent.add_tool(fs_tool)

1796

被折叠的 条评论
为什么被折叠?



