feat(plugin): 集成 MCP 协议支持并优化代码风格
- 新增 fastmcp 依赖,支持通过 Streamable HTTP 连接外部工具服务器 - 在 component_registry 与 tool_api 中实现 MCP 工具加载、注册及调用链路 - 补充 README 中的 MCP 特性说明 - 统一修复多处 import 顺序、空行、引号及类型注解,提升代码整洁度 - 在 pyproject.toml 中忽略 PERF203 规则,允许循环内异常处理 - 优化语音缓存与本地 ASR 调用逻辑,减少冗余代码
This commit is contained in:
59
scripts/debug_mcp_tools.py
Normal file
59
scripts/debug_mcp_tools.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
调试 MCP 工具列表获取
|
||||
|
||||
直接测试 MCP 客户端是否能获取工具
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastmcp.client import Client, StreamableHttpTransport
|
||||
|
||||
|
||||
async def test_direct_connection():
|
||||
"""直接连接 MCP 服务器并获取工具列表"""
|
||||
print("=" * 60)
|
||||
print("直接测试 MCP 服务器连接")
|
||||
print("=" * 60)
|
||||
|
||||
url = "http://localhost:8000/mcp"
|
||||
print(f"\n连接到: {url}")
|
||||
|
||||
try:
|
||||
# 创建传输层
|
||||
transport = StreamableHttpTransport(url)
|
||||
print("✓ 传输层创建成功")
|
||||
|
||||
# 创建客户端
|
||||
async with Client(transport) as client:
|
||||
print("✓ 客户端连接成功")
|
||||
|
||||
# 获取工具列表
|
||||
print("\n正在获取工具列表...")
|
||||
tools_result = await client.list_tools()
|
||||
|
||||
print(f"\n获取结果类型: {type(tools_result)}")
|
||||
print(f"结果内容: {tools_result}")
|
||||
|
||||
# 检查是否有 tools 属性
|
||||
if hasattr(tools_result, "tools"):
|
||||
tools = tools_result.tools
|
||||
print(f"\n✓ 找到 tools 属性,包含 {len(tools)} 个工具")
|
||||
|
||||
for i, tool in enumerate(tools, 1):
|
||||
print(f"\n工具 {i}:")
|
||||
print(f" 名称: {tool.name}")
|
||||
print(f" 描述: {tool.description}")
|
||||
if hasattr(tool, "inputSchema"):
|
||||
print(f" 参数 Schema: {tool.inputSchema}")
|
||||
else:
|
||||
print("\n✗ 结果中没有 tools 属性")
|
||||
print(f"可用属性: {dir(tools_result)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 连接失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_direct_connection())
|
||||
142
scripts/simple_mcp_server.py
Normal file
142
scripts/simple_mcp_server.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
简单的 MCP 测试服务器
|
||||
|
||||
使用 fastmcp 创建一个简单的 MCP 服务器供测试使用。
|
||||
|
||||
安装依赖:
|
||||
pip install fastmcp uvicorn
|
||||
|
||||
运行服务器:
|
||||
python scripts/simple_mcp_server.py
|
||||
|
||||
服务器将在 http://localhost:8000/mcp 提供 MCP 服务
|
||||
"""
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# 创建 MCP 服务器实例
|
||||
mcp = FastMCP("Demo Server")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""将两个数字相加
|
||||
|
||||
Args:
|
||||
a: 第一个数字
|
||||
b: 第二个数字
|
||||
|
||||
Returns:
|
||||
两个数字的和
|
||||
"""
|
||||
return a + b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def multiply(a: float, b: float) -> float:
|
||||
"""将两个数字相乘
|
||||
|
||||
Args:
|
||||
a: 第一个数字
|
||||
b: 第二个数字
|
||||
|
||||
Returns:
|
||||
两个数字的乘积
|
||||
"""
|
||||
return a * b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_weather(city: str) -> str:
|
||||
"""获取指定城市的天气信息(模拟)
|
||||
|
||||
Args:
|
||||
city: 城市名称
|
||||
|
||||
Returns:
|
||||
天气信息字符串
|
||||
"""
|
||||
# 这是一个模拟实现
|
||||
weather_data = {
|
||||
"beijing": "北京今天晴朗,温度 20°C",
|
||||
"shanghai": "上海今天多云,温度 18°C",
|
||||
"guangzhou": "广州今天有雨,温度 25°C",
|
||||
}
|
||||
|
||||
city_lower = city.lower()
|
||||
return weather_data.get(
|
||||
city_lower,
|
||||
f"{city}的天气信息暂不可用"
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def echo(message: str, repeat: int = 1) -> str:
|
||||
"""重复输出一条消息
|
||||
|
||||
Args:
|
||||
message: 要重复的消息
|
||||
repeat: 重复次数,默认为 1
|
||||
|
||||
Returns:
|
||||
重复后的消息
|
||||
"""
|
||||
return (message + "\n") * repeat
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def check_prime(number: int) -> bool:
|
||||
"""检查一个数字是否为质数
|
||||
|
||||
Args:
|
||||
number: 要检查的数字
|
||||
|
||||
Returns:
|
||||
如果是质数返回 True,否则返回 False
|
||||
"""
|
||||
if number < 2:
|
||||
return False
|
||||
|
||||
for i in range(2, int(number ** 0.5) + 1):
|
||||
if number % i == 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("简单 MCP 测试服务器")
|
||||
print("=" * 60)
|
||||
print("\n服务器配置:")
|
||||
print(" - 传输协议: Streamable HTTP")
|
||||
print(" - 地址: http://localhost:8000/mcp")
|
||||
print("\n可用工具:")
|
||||
print(" 1. add(a: int, b: int) -> int")
|
||||
print(" 2. multiply(a: float, b: float) -> float")
|
||||
print(" 3. get_weather(city: str) -> str")
|
||||
print(" 4. echo(message: str, repeat: int = 1) -> str")
|
||||
print(" 5. check_prime(number: int) -> bool")
|
||||
print("\n配置示例 (config/mcp.json):")
|
||||
print("""
|
||||
{
|
||||
"$schema": "./mcp.schema.json",
|
||||
"mcpServers": {
|
||||
"demo_server": {
|
||||
"enabled": true,
|
||||
"transport": {
|
||||
"type": "streamable-http",
|
||||
"url": "http://localhost:8000/mcp"
|
||||
},
|
||||
"timeout": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
print("=" * 60)
|
||||
print("\n正在启动服务器...")
|
||||
print("请参考 fastmcp 官方文档了解如何运行此服务器。")
|
||||
print("文档: https://github.com/jlowin/fastmcp")
|
||||
print("\n基本命令:")
|
||||
print(" fastmcp run simple_mcp_server:mcp")
|
||||
print("=" * 60)
|
||||
15
scripts/test/demo_mcp_server.py
Normal file
15
scripts/test/demo_mcp_server.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from fastmcp import FastMCP
|
||||
|
||||
app = FastMCP(
|
||||
name="Demo MCP Server",
|
||||
streamable_http_path="/mcp"
|
||||
)
|
||||
|
||||
@app.tool()
|
||||
async def echo_tool(input: str) -> str:
|
||||
"""一个简单的回声工具"""
|
||||
return f"Echo: {input}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, transport="streamable-http"
|
||||
)
|
||||
190
scripts/test_mcp_integration.py
Normal file
190
scripts/test_mcp_integration.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
MCP 集成测试脚本
|
||||
|
||||
测试 MCP 客户端连接、工具列表获取和工具调用功能
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.core.component_registry import ComponentRegistry
|
||||
from src.plugin_system.core.mcp_client_manager import MCPClientManager
|
||||
|
||||
logger = get_logger("test_mcp_integration")
|
||||
|
||||
|
||||
async def test_mcp_client_manager():
|
||||
"""测试 MCPClientManager 基本功能"""
|
||||
print("\n" + "="*60)
|
||||
print("测试 1: MCPClientManager 连接和工具列表")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
# 初始化 MCP 客户端管理器
|
||||
manager = MCPClientManager()
|
||||
await manager.initialize()
|
||||
|
||||
print("\n✓ MCP 客户端管理器初始化成功")
|
||||
print(f"已连接服务器数量: {len(manager.clients)}")
|
||||
|
||||
# 获取所有工具
|
||||
tools = await manager.get_all_tools()
|
||||
print(f"\n获取到 {len(tools)} 个 MCP 工具:")
|
||||
|
||||
for tool in tools:
|
||||
print(f"\n 工具: {tool}")
|
||||
# 注意: 这里 tool 是字符串形式的工具名称
|
||||
# 如果需要工具详情,需要从其他地方获取
|
||||
|
||||
return manager, tools
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 测试失败: {e}")
|
||||
logger.exception("MCPClientManager 测试失败")
|
||||
return None, []
|
||||
|
||||
|
||||
async def test_tool_call(manager: MCPClientManager, tools):
|
||||
"""测试工具调用"""
|
||||
print("\n" + "="*60)
|
||||
print("测试 2: MCP 工具调用")
|
||||
print("="*60)
|
||||
|
||||
if not tools:
|
||||
print("\n⚠ 没有可用的工具进行测试")
|
||||
return
|
||||
|
||||
try:
|
||||
# 工具列表测试已在第一个测试中完成
|
||||
print("\n✓ 工具列表获取成功")
|
||||
print(f"可用工具数量: {len(tools)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 工具调用测试失败: {e}")
|
||||
logger.exception("工具调用测试失败")
|
||||
|
||||
|
||||
async def test_component_registry_integration():
|
||||
"""测试 ComponentRegistry 集成"""
|
||||
print("\n" + "="*60)
|
||||
print("测试 3: ComponentRegistry MCP 工具集成")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
registry = ComponentRegistry()
|
||||
|
||||
# 加载 MCP 工具
|
||||
await registry.load_mcp_tools()
|
||||
|
||||
# 获取 MCP 工具
|
||||
mcp_tools = registry.get_mcp_tools()
|
||||
print(f"\n✓ ComponentRegistry 加载了 {len(mcp_tools)} 个 MCP 工具")
|
||||
|
||||
for tool in mcp_tools:
|
||||
print(f"\n 工具: {tool.name}")
|
||||
print(f" 描述: {tool.description}")
|
||||
print(f" 参数数量: {len(tool.parameters)}")
|
||||
|
||||
# 测试 is_mcp_tool 方法
|
||||
is_mcp = registry.is_mcp_tool(tool.name)
|
||||
print(f" is_mcp_tool 检测: {'✓' if is_mcp else '✗'}")
|
||||
|
||||
return mcp_tools
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ ComponentRegistry 集成测试失败: {e}")
|
||||
logger.exception("ComponentRegistry 集成测试失败")
|
||||
return []
|
||||
|
||||
|
||||
async def test_tool_execution(mcp_tools):
|
||||
"""测试通过适配器执行工具"""
|
||||
print("\n" + "="*60)
|
||||
print("测试 4: MCPToolAdapter 工具执行")
|
||||
print("="*60)
|
||||
|
||||
if not mcp_tools:
|
||||
print("\n⚠ 没有可用的 MCP 工具进行测试")
|
||||
return
|
||||
|
||||
try:
|
||||
# 选择第一个工具测试
|
||||
test_tool = mcp_tools[0]
|
||||
print(f"\n测试工具: {test_tool.name}")
|
||||
|
||||
# 构建测试参数
|
||||
test_args = {}
|
||||
for param_name, param_type, param_desc, is_required, enum_values in test_tool.parameters:
|
||||
if is_required:
|
||||
# 根据类型提供默认值
|
||||
from src.llm_models.payload_content.tool_option import ToolParamType
|
||||
|
||||
if param_type == ToolParamType.STRING:
|
||||
test_args[param_name] = "test_value"
|
||||
elif param_type == ToolParamType.INTEGER:
|
||||
test_args[param_name] = 1
|
||||
elif param_type == ToolParamType.FLOAT:
|
||||
test_args[param_name] = 1.0
|
||||
elif param_type == ToolParamType.BOOLEAN:
|
||||
test_args[param_name] = True
|
||||
|
||||
print(f"测试参数: {test_args}")
|
||||
|
||||
# 执行工具
|
||||
result = await test_tool.execute(test_args)
|
||||
|
||||
if result:
|
||||
print("\n✓ 工具执行成功")
|
||||
print(f"结果类型: {result.get('type')}")
|
||||
print(f"结果内容: {result.get('content', '')[:200]}...") # 只显示前200字符
|
||||
else:
|
||||
print("\n✗ 工具执行失败,返回 None")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 工具执行测试失败: {e}")
|
||||
logger.exception("工具执行测试失败")
|
||||
|
||||
|
||||
async def main():
|
||||
"""主测试流程"""
|
||||
print("\n" + "="*60)
|
||||
print("MCP 集成测试")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
# 测试 1: MCPClientManager 基本功能
|
||||
manager, tools = await test_mcp_client_manager()
|
||||
|
||||
if manager:
|
||||
# 测试 2: 工具调用
|
||||
await test_tool_call(manager, tools)
|
||||
|
||||
# 测试 3: ComponentRegistry 集成
|
||||
mcp_tools = await test_component_registry_integration()
|
||||
|
||||
# 测试 4: 工具执行
|
||||
await test_tool_execution(mcp_tools)
|
||||
|
||||
# 关闭连接
|
||||
await manager.close()
|
||||
print("\n✓ MCP 客户端连接已关闭")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("测试完成")
|
||||
print("="*60 + "\n")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n测试被用户中断")
|
||||
except Exception as e:
|
||||
print(f"\n测试过程中发生错误: {e}")
|
||||
logger.exception("测试失败")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user