Ruff format

This commit is contained in:
墨梓柒
2025-12-13 17:14:09 +08:00
parent ef377bb0cd
commit e680a4d1f5
60 changed files with 1546 additions and 1532 deletions

View File

@@ -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

View File

@@ -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