244 lines
7.8 KiB
Python
Raw Normal View History

2025-06-10 08:40:47 +08:00
#!/usr/bin/env python3
"""
MCP 客戶端模擬器 - 簡化版本
"""
import asyncio
import json
import subprocess
from pathlib import Path
2025-06-11 03:25:08 +08:00
from typing import Any
2025-06-10 08:40:47 +08:00
2025-06-11 03:25:08 +08:00
from .test_utils import PerformanceTimer
2025-06-10 08:40:47 +08:00
class SimpleMCPClient:
"""簡化的 MCP 客戶端模擬器"""
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
def __init__(self, timeout: int = 30):
self.timeout = timeout
2025-06-11 03:25:08 +08:00
self.server_process: subprocess.Popen | None = None
2025-06-11 06:11:29 +08:00
self.stdin: Any = None
self.stdout: Any = None
self.stderr: Any = None
2025-06-10 08:40:47 +08:00
self.initialized = False
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
async def start_server(self) -> bool:
"""啟動 MCP 服務器"""
try:
# 使用當前專案的 MCP 服務器
2025-06-11 06:11:29 +08:00
cmd = ["python", "-m", "mcp_feedback_enhanced.server"]
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
self.server_process = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=0,
2025-06-11 03:25:08 +08:00
cwd=Path.cwd(),
2025-06-10 08:40:47 +08:00
)
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
self.stdin = self.server_process.stdin
self.stdout = self.server_process.stdout
self.stderr = self.server_process.stderr
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
# 等待服務器啟動
await asyncio.sleep(2)
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
if self.server_process.poll() is not None:
return False
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
return True
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
except Exception as e:
print(f"啟動 MCP 服務器失敗: {e}")
return False
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
async def initialize(self) -> bool:
"""初始化 MCP 連接"""
if not self.server_process or self.server_process.poll() is not None:
return False
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
try:
# 發送初始化請求
init_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
2025-06-11 03:25:08 +08:00
"capabilities": {"roots": {"listChanged": True}, "sampling": {}},
"clientInfo": {"name": "test-client", "version": "1.0.0"},
},
2025-06-10 08:40:47 +08:00
}
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
await self._send_request(init_request)
response = await self._read_response()
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
if response and "result" in response:
self.initialized = True
return True
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
except Exception as e:
print(f"MCP 初始化失敗: {e}")
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
return False
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
async def call_interactive_feedback(
2025-06-11 03:25:08 +08:00
self, project_directory: str, summary: str, timeout: int = 30
) -> dict[str, Any]:
2025-06-10 08:40:47 +08:00
"""調用 interactive_feedback 工具"""
if not self.initialized:
return {"error": "MCP 客戶端未初始化"}
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
try:
request = {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "interactive_feedback",
"arguments": {
"project_directory": project_directory,
"summary": summary,
2025-06-11 03:25:08 +08:00
"timeout": timeout,
},
},
2025-06-10 08:40:47 +08:00
}
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
with PerformanceTimer() as timer:
await self._send_request(request)
response = await self._read_response(timeout=timeout + 5)
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
if response and "result" in response:
result = response["result"]
result["performance"] = {"duration": timer.duration}
2025-06-11 06:11:29 +08:00
# 修復 no-any-return 錯誤 - 確保返回明確類型
return dict(result) # 明確返回 dict[str, Any] 類型
2025-06-11 03:25:08 +08:00
return {"error": "無效的回應格式", "response": response}
except TimeoutError:
2025-06-10 08:40:47 +08:00
return {"error": "調用超時"}
except Exception as e:
2025-06-11 03:25:08 +08:00
return {"error": f"調用失敗: {e!s}"}
async def _send_request(self, request: dict[str, Any]):
2025-06-10 08:40:47 +08:00
"""發送請求"""
if not self.stdin:
raise RuntimeError("stdin 不可用")
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
request_str = json.dumps(request) + "\n"
self.stdin.write(request_str)
self.stdin.flush()
2025-06-11 03:25:08 +08:00
async def _read_response(self, timeout: int = 30) -> dict[str, Any] | None:
2025-06-10 08:40:47 +08:00
"""讀取回應"""
if not self.stdout:
raise RuntimeError("stdout 不可用")
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
try:
# 使用 asyncio 超時
response_line = await asyncio.wait_for(
2025-06-11 03:25:08 +08:00
asyncio.to_thread(self.stdout.readline), timeout=timeout
2025-06-10 08:40:47 +08:00
)
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
if response_line:
2025-06-11 06:11:29 +08:00
response_data = json.loads(response_line.strip())
# 修復 no-any-return 錯誤 - 確保返回明確類型
return (
dict(response_data)
if isinstance(response_data, dict)
else response_data
)
2025-06-10 08:40:47 +08:00
return None
2025-06-11 03:25:08 +08:00
except TimeoutError:
2025-06-10 08:40:47 +08:00
raise
except json.JSONDecodeError as e:
print(f"JSON 解析錯誤: {e}, 原始數據: {response_line}")
return None
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
async def cleanup(self):
"""清理資源"""
if self.server_process:
try:
# 嘗試正常終止
self.server_process.terminate()
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
# 等待進程結束
try:
await asyncio.wait_for(
2025-06-11 03:25:08 +08:00
asyncio.to_thread(self.server_process.wait), timeout=5
2025-06-10 08:40:47 +08:00
)
2025-06-11 03:25:08 +08:00
except TimeoutError:
2025-06-10 08:40:47 +08:00
# 強制終止
self.server_process.kill()
await asyncio.to_thread(self.server_process.wait)
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
except Exception as e:
print(f"清理 MCP 服務器失敗: {e}")
finally:
self.server_process = None
self.stdin = None
self.stdout = None
self.stderr = None
self.initialized = False
class MCPWorkflowTester:
"""MCP 工作流程測試器"""
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
def __init__(self, timeout: int = 60):
self.timeout = timeout
self.client = SimpleMCPClient(timeout)
2025-06-11 03:25:08 +08:00
async def test_basic_workflow(
self, project_dir: str, summary: str
) -> dict[str, Any]:
2025-06-10 08:40:47 +08:00
"""測試基本工作流程"""
2025-06-11 06:11:29 +08:00
result: dict[str, Any] = {
"success": False,
"steps": {},
"errors": [],
"performance": {},
}
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
with PerformanceTimer() as timer:
try:
# 1. 啟動服務器
if await self.client.start_server():
result["steps"]["server_started"] = True
else:
result["errors"].append("服務器啟動失敗")
return result
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
# 2. 初始化連接
if await self.client.initialize():
result["steps"]["initialized"] = True
else:
result["errors"].append("初始化失敗")
return result
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
# 3. 調用 interactive_feedback
feedback_result = await self.client.call_interactive_feedback(
project_dir, summary, timeout=10
)
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
if "error" not in feedback_result:
result["steps"]["interactive_feedback_called"] = True
result["feedback_result"] = feedback_result
result["success"] = True
else:
2025-06-11 03:25:08 +08:00
result["errors"].append(
f"interactive_feedback 調用失敗: {feedback_result['error']}"
)
2025-06-10 08:40:47 +08:00
except Exception as e:
2025-06-11 03:25:08 +08:00
result["errors"].append(f"測試異常: {e!s}")
2025-06-10 08:40:47 +08:00
finally:
await self.client.cleanup()
result["performance"]["total_duration"] = timer.duration
2025-06-11 03:25:08 +08:00
2025-06-10 08:40:47 +08:00
return result