Ruff format
This commit is contained in:
@@ -19,6 +19,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
@dataclass
|
||||
class ConversionResult:
|
||||
"""转换结果"""
|
||||
|
||||
success: bool
|
||||
servers: List[Dict[str, Any]] = field(default_factory=list)
|
||||
errors: List[str] = field(default_factory=list)
|
||||
@@ -53,7 +54,7 @@ class ConfigConverter:
|
||||
@classmethod
|
||||
def detect_format(cls, config: Dict[str, Any]) -> Optional[str]:
|
||||
"""检测配置格式类型
|
||||
|
||||
|
||||
Returns:
|
||||
"claude": Claude Desktop 格式 (mcpServers 对象)
|
||||
"kiro": Kiro MCP 格式 (mcpServers 对象,与 Claude 相同)
|
||||
@@ -82,7 +83,7 @@ class ConfigConverter:
|
||||
@classmethod
|
||||
def parse_json_safe(cls, json_str: str) -> Tuple[Optional[Any], Optional[str]]:
|
||||
"""安全解析 JSON 字符串
|
||||
|
||||
|
||||
Returns:
|
||||
(解析结果, 错误信息)
|
||||
"""
|
||||
@@ -102,11 +103,11 @@ class ConfigConverter:
|
||||
@classmethod
|
||||
def validate_server_config(cls, name: str, config: Dict[str, Any]) -> Tuple[bool, Optional[str], List[str]]:
|
||||
"""验证单个服务器配置
|
||||
|
||||
|
||||
Args:
|
||||
name: 服务器名称
|
||||
config: 服务器配置字典
|
||||
|
||||
|
||||
Returns:
|
||||
(是否有效, 错误信息, 警告列表)
|
||||
"""
|
||||
@@ -177,11 +178,11 @@ class ConfigConverter:
|
||||
@classmethod
|
||||
def convert_claude_server(cls, name: str, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""将单个 Claude 格式服务器配置转换为 MaiBot 格式
|
||||
|
||||
|
||||
Args:
|
||||
name: 服务器名称
|
||||
config: Claude 格式的服务器配置
|
||||
|
||||
|
||||
Returns:
|
||||
MaiBot 格式的服务器配置
|
||||
"""
|
||||
@@ -231,10 +232,10 @@ class ConfigConverter:
|
||||
@classmethod
|
||||
def convert_maibot_server(cls, config: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""将单个 MaiBot 格式服务器配置转换为 Claude 格式
|
||||
|
||||
|
||||
Args:
|
||||
config: MaiBot 格式的服务器配置
|
||||
|
||||
|
||||
Returns:
|
||||
(服务器名称, Claude 格式的服务器配置)
|
||||
"""
|
||||
@@ -271,17 +272,13 @@ class ConfigConverter:
|
||||
return name, result
|
||||
|
||||
@classmethod
|
||||
def from_claude_format(
|
||||
cls,
|
||||
config: Dict[str, Any],
|
||||
existing_names: Optional[set] = None
|
||||
) -> ConversionResult:
|
||||
def from_claude_format(cls, config: Dict[str, Any], existing_names: Optional[set] = None) -> ConversionResult:
|
||||
"""从 Claude Desktop 格式转换为 MaiBot 格式
|
||||
|
||||
|
||||
Args:
|
||||
config: Claude Desktop 配置 (包含 mcpServers 字段)
|
||||
existing_names: 已存在的服务器名称集合,用于跳过重复
|
||||
|
||||
|
||||
Returns:
|
||||
ConversionResult
|
||||
"""
|
||||
@@ -336,10 +333,10 @@ class ConfigConverter:
|
||||
@classmethod
|
||||
def to_claude_format(cls, servers: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""将 MaiBot 格式转换为 Claude Desktop 格式
|
||||
|
||||
|
||||
Args:
|
||||
servers: MaiBot 格式的服务器列表
|
||||
|
||||
|
||||
Returns:
|
||||
Claude Desktop 格式的配置
|
||||
"""
|
||||
@@ -355,19 +352,15 @@ class ConfigConverter:
|
||||
return {"mcpServers": mcp_servers}
|
||||
|
||||
@classmethod
|
||||
def import_from_string(
|
||||
cls,
|
||||
json_str: str,
|
||||
existing_names: Optional[set] = None
|
||||
) -> ConversionResult:
|
||||
def import_from_string(cls, json_str: str, existing_names: Optional[set] = None) -> ConversionResult:
|
||||
"""从 JSON 字符串导入配置
|
||||
|
||||
|
||||
自动检测格式并转换为 MaiBot 格式
|
||||
|
||||
|
||||
Args:
|
||||
json_str: JSON 字符串
|
||||
existing_names: 已存在的服务器名称集合
|
||||
|
||||
|
||||
Returns:
|
||||
ConversionResult
|
||||
"""
|
||||
@@ -422,19 +415,14 @@ class ConfigConverter:
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def export_to_string(
|
||||
cls,
|
||||
servers: List[Dict[str, Any]],
|
||||
format_type: str = "claude",
|
||||
pretty: bool = True
|
||||
) -> str:
|
||||
def export_to_string(cls, servers: List[Dict[str, Any]], format_type: str = "claude", pretty: bool = True) -> str:
|
||||
"""导出配置为 JSON 字符串
|
||||
|
||||
|
||||
Args:
|
||||
servers: MaiBot 格式的服务器列表
|
||||
format_type: 导出格式 ("claude", "kiro", "maibot")
|
||||
pretty: 是否格式化输出
|
||||
|
||||
|
||||
Returns:
|
||||
JSON 字符串
|
||||
"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -23,22 +23,22 @@ from mcp_client import (
|
||||
async def test_stats():
|
||||
"""测试统计类"""
|
||||
print("\n=== 测试统计类 ===")
|
||||
|
||||
|
||||
# 测试 ToolCallStats
|
||||
stats = ToolCallStats(tool_key="test_tool")
|
||||
stats.record_call(True, 100.0)
|
||||
stats.record_call(True, 200.0)
|
||||
stats.record_call(False, 50.0, "timeout")
|
||||
|
||||
|
||||
assert stats.total_calls == 3
|
||||
assert stats.success_calls == 2
|
||||
assert stats.failed_calls == 1
|
||||
assert stats.success_rate == (2/3) * 100
|
||||
assert stats.success_rate == (2 / 3) * 100
|
||||
assert stats.avg_duration_ms == 150.0
|
||||
assert stats.last_error == "timeout"
|
||||
|
||||
|
||||
print(f"✅ ToolCallStats: {stats.to_dict()}")
|
||||
|
||||
|
||||
# 测试 ServerStats
|
||||
server_stats = ServerStats(server_name="test_server")
|
||||
server_stats.record_connect()
|
||||
@@ -46,133 +46,138 @@ async def test_stats():
|
||||
server_stats.record_disconnect()
|
||||
server_stats.record_failure()
|
||||
server_stats.record_failure()
|
||||
|
||||
|
||||
assert server_stats.connect_count == 1
|
||||
assert server_stats.disconnect_count == 1
|
||||
assert server_stats.consecutive_failures == 2
|
||||
|
||||
|
||||
print(f"✅ ServerStats: {server_stats.to_dict()}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def test_manager_basic():
|
||||
"""测试管理器基本功能"""
|
||||
print("\n=== 测试管理器基本功能 ===")
|
||||
|
||||
|
||||
# 创建新的管理器实例(绕过单例)
|
||||
manager = MCPClientManager.__new__(MCPClientManager)
|
||||
manager._initialized = False
|
||||
manager.__init__()
|
||||
|
||||
|
||||
# 配置
|
||||
manager.configure({
|
||||
"tool_prefix": "mcp",
|
||||
"call_timeout": 30.0,
|
||||
"retry_attempts": 1,
|
||||
"retry_interval": 1.0,
|
||||
"heartbeat_enabled": False,
|
||||
})
|
||||
|
||||
manager.configure(
|
||||
{
|
||||
"tool_prefix": "mcp",
|
||||
"call_timeout": 30.0,
|
||||
"retry_attempts": 1,
|
||||
"retry_interval": 1.0,
|
||||
"heartbeat_enabled": False,
|
||||
}
|
||||
)
|
||||
|
||||
# 测试状态
|
||||
status = manager.get_status()
|
||||
assert status["total_servers"] == 0
|
||||
assert status["connected_servers"] == 0
|
||||
print(f"✅ 初始状态: {status}")
|
||||
|
||||
|
||||
# 测试添加禁用的服务器
|
||||
config = MCPServerConfig(
|
||||
name="disabled_server",
|
||||
enabled=False,
|
||||
transport=TransportType.HTTP,
|
||||
url="https://example.com/mcp"
|
||||
name="disabled_server", enabled=False, transport=TransportType.HTTP, url="https://example.com/mcp"
|
||||
)
|
||||
result = await manager.add_server(config)
|
||||
assert result == True
|
||||
assert "disabled_server" in manager._clients
|
||||
assert manager._clients["disabled_server"].is_connected == False
|
||||
print("✅ 添加禁用服务器成功")
|
||||
|
||||
|
||||
# 测试重复添加
|
||||
result = await manager.add_server(config)
|
||||
assert result == False
|
||||
print("✅ 重复添加被拒绝")
|
||||
|
||||
|
||||
# 测试移除
|
||||
result = await manager.remove_server("disabled_server")
|
||||
assert result == True
|
||||
assert "disabled_server" not in manager._clients
|
||||
print("✅ 移除服务器成功")
|
||||
|
||||
|
||||
# 清理
|
||||
await manager.shutdown()
|
||||
print("✅ 管理器关闭成功")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def test_http_connection():
|
||||
"""测试 HTTP 连接(使用真实的 MCP 服务器)"""
|
||||
print("\n=== 测试 HTTP 连接 ===")
|
||||
|
||||
|
||||
# 创建新的管理器实例
|
||||
manager = MCPClientManager.__new__(MCPClientManager)
|
||||
manager._initialized = False
|
||||
manager.__init__()
|
||||
|
||||
manager.configure({
|
||||
"tool_prefix": "mcp",
|
||||
"call_timeout": 30.0,
|
||||
"retry_attempts": 2,
|
||||
"retry_interval": 2.0,
|
||||
"heartbeat_enabled": False,
|
||||
})
|
||||
|
||||
|
||||
manager.configure(
|
||||
{
|
||||
"tool_prefix": "mcp",
|
||||
"call_timeout": 30.0,
|
||||
"retry_attempts": 2,
|
||||
"retry_interval": 2.0,
|
||||
"heartbeat_enabled": False,
|
||||
}
|
||||
)
|
||||
|
||||
# 使用 HowToCook MCP 服务器测试
|
||||
config = MCPServerConfig(
|
||||
name="howtocook",
|
||||
enabled=True,
|
||||
transport=TransportType.HTTP,
|
||||
url="https://mcp.api-inference.modelscope.net/c9b55951d4ed47/mcp"
|
||||
url="https://mcp.api-inference.modelscope.net/c9b55951d4ed47/mcp",
|
||||
)
|
||||
|
||||
|
||||
print(f"正在连接 {config.url} ...")
|
||||
result = await manager.add_server(config)
|
||||
|
||||
|
||||
if result:
|
||||
print(f"✅ 连接成功!")
|
||||
|
||||
print("✅ 连接成功!")
|
||||
|
||||
# 检查工具
|
||||
tools = manager.all_tools
|
||||
print(f"✅ 发现 {len(tools)} 个工具:")
|
||||
for tool_key in tools:
|
||||
print(f" - {tool_key}")
|
||||
|
||||
|
||||
# 测试心跳
|
||||
client = manager._clients["howtocook"]
|
||||
healthy = await client.check_health()
|
||||
print(f"✅ 心跳检测: {'健康' if healthy else '异常'}")
|
||||
|
||||
|
||||
# 测试工具调用
|
||||
if "mcp_howtocook_whatToEat" in tools:
|
||||
print("\n正在调用 whatToEat 工具...")
|
||||
call_result = await manager.call_tool("mcp_howtocook_whatToEat", {})
|
||||
if call_result.success:
|
||||
print(f"✅ 工具调用成功 (耗时: {call_result.duration_ms:.0f}ms)")
|
||||
print(f" 结果: {call_result.content[:200]}..." if len(str(call_result.content)) > 200 else f" 结果: {call_result.content}")
|
||||
print(
|
||||
f" 结果: {call_result.content[:200]}..."
|
||||
if len(str(call_result.content)) > 200
|
||||
else f" 结果: {call_result.content}"
|
||||
)
|
||||
else:
|
||||
print(f"❌ 工具调用失败: {call_result.error}")
|
||||
|
||||
|
||||
# 查看统计
|
||||
stats = manager.get_all_stats()
|
||||
print(f"\n📊 统计信息:")
|
||||
print("\n📊 统计信息:")
|
||||
print(f" 全局调用: {stats['global']['total_tool_calls']}")
|
||||
print(f" 成功: {stats['global']['successful_calls']}")
|
||||
print(f" 失败: {stats['global']['failed_calls']}")
|
||||
|
||||
|
||||
else:
|
||||
print(f"❌ 连接失败")
|
||||
|
||||
print("❌ 连接失败")
|
||||
|
||||
# 清理
|
||||
await manager.shutdown()
|
||||
return result
|
||||
@@ -181,55 +186,57 @@ async def test_http_connection():
|
||||
async def test_heartbeat():
|
||||
"""测试心跳检测功能"""
|
||||
print("\n=== 测试心跳检测 ===")
|
||||
|
||||
|
||||
# 创建新的管理器实例
|
||||
manager = MCPClientManager.__new__(MCPClientManager)
|
||||
manager._initialized = False
|
||||
manager.__init__()
|
||||
|
||||
manager.configure({
|
||||
"tool_prefix": "mcp",
|
||||
"call_timeout": 30.0,
|
||||
"retry_attempts": 1,
|
||||
"retry_interval": 1.0,
|
||||
"heartbeat_enabled": True,
|
||||
"heartbeat_interval": 5.0, # 5秒间隔用于测试
|
||||
"auto_reconnect": True,
|
||||
"max_reconnect_attempts": 2,
|
||||
})
|
||||
|
||||
|
||||
manager.configure(
|
||||
{
|
||||
"tool_prefix": "mcp",
|
||||
"call_timeout": 30.0,
|
||||
"retry_attempts": 1,
|
||||
"retry_interval": 1.0,
|
||||
"heartbeat_enabled": True,
|
||||
"heartbeat_interval": 5.0, # 5秒间隔用于测试
|
||||
"auto_reconnect": True,
|
||||
"max_reconnect_attempts": 2,
|
||||
}
|
||||
)
|
||||
|
||||
# 添加一个测试服务器
|
||||
config = MCPServerConfig(
|
||||
name="heartbeat_test",
|
||||
enabled=True,
|
||||
transport=TransportType.HTTP,
|
||||
url="https://mcp.api-inference.modelscope.net/c9b55951d4ed47/mcp"
|
||||
url="https://mcp.api-inference.modelscope.net/c9b55951d4ed47/mcp",
|
||||
)
|
||||
|
||||
|
||||
print("正在连接服务器...")
|
||||
result = await manager.add_server(config)
|
||||
|
||||
|
||||
if result:
|
||||
print("✅ 服务器连接成功")
|
||||
|
||||
|
||||
# 启动心跳检测
|
||||
await manager.start_heartbeat()
|
||||
print("✅ 心跳检测已启动")
|
||||
|
||||
|
||||
# 等待一个心跳周期
|
||||
print("等待心跳检测...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
|
||||
# 检查状态
|
||||
status = manager.get_status()
|
||||
print(f"✅ 心跳运行状态: {status['heartbeat_running']}")
|
||||
|
||||
|
||||
# 停止心跳
|
||||
await manager.stop_heartbeat()
|
||||
print("✅ 心跳检测已停止")
|
||||
else:
|
||||
print("❌ 服务器连接失败,跳过心跳测试")
|
||||
|
||||
|
||||
await manager.shutdown()
|
||||
return True
|
||||
|
||||
@@ -239,30 +246,31 @@ async def main():
|
||||
print("=" * 50)
|
||||
print("MCP 客户端测试")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
try:
|
||||
# 基础测试
|
||||
await test_stats()
|
||||
await test_manager_basic()
|
||||
|
||||
|
||||
# 网络测试
|
||||
print("\n是否进行网络连接测试? (需要网络) [y/N]: ", end="")
|
||||
# 自动进行网络测试
|
||||
await test_http_connection()
|
||||
|
||||
|
||||
# 心跳测试
|
||||
await test_heartbeat()
|
||||
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ 所有测试通过!")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试失败: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user