diff --git a/apps/api/src/plugins/mcp/mcp_bridge.py b/apps/api/src/plugins/mcp/mcp_bridge.py index d83fdbf1..c59a2d57 100644 --- a/apps/api/src/plugins/mcp/mcp_bridge.py +++ b/apps/api/src/plugins/mcp/mcp_bridge.py @@ -305,6 +305,19 @@ class MCPBridge: }, server_name=server.name, ), + MCPTool( + name="kubectl_restart", + description="Restart Kubernetes deployment (rollout restart)", + input_schema={ + "type": "object", + "properties": { + "deployment": {"type": "string"}, + "namespace": {"type": "string"}, + }, + "required": ["deployment"], + }, + server_name=server.name, + ), ], "database": [ MCPTool( @@ -465,18 +478,112 @@ class MCPBridge: tool_name: str, parameters: dict[str, Any], ) -> Any: - """HTTP 方式執行工具 (Mock 實作)""" - # Phase 3: Mock 執行,實際連接待 MCP Server 部署 - logger.info(f"[MOCK] HTTP call to {server.endpoint}: {tool_name}({parameters})") + """ + HTTP 方式執行工具 - # 模擬不同工具的回傳 - mock_responses = { - "kubectl_get": {"items": [{"name": "pod-1"}, {"name": "pod-2"}]}, - "kubectl_delete": {"deleted": True, "resource": parameters.get("name")}, - "kubectl_scale": {"scaled": True, "replicas": parameters.get("replicas")}, - "query": {"rows": [], "affected": 0}, - } - return mock_responses.get(tool_name, {"status": "ok"}) + Phase 13.2: 整合真實 K8s Executor (不再是 Mock) + """ + # ============================================= + # Kubernetes: 使用真實 ActionExecutor + # ============================================= + if server.name == "kubernetes": + from src.services.executor import get_executor + + executor = get_executor() + + if tool_name == "kubectl_get": + # 使用 kubectl 指令查詢 + namespace = parameters.get("namespace", "awoooi-prod") + resource = parameters.get("resource", "pods") + name = parameters.get("name", "") + cmd = f"kubectl get {resource} {name} -n {namespace} -o json".strip() + result = await executor.execute_kubectl_command(cmd) + if result.success and result.k8s_response: + return result.k8s_response.get("stdout", "") + return {"error": result.error} + + elif tool_name == "kubectl_delete": + namespace = parameters.get("namespace", "awoooi-prod") + resource = parameters.get("resource", "pod") + name = parameters.get("name", "") + if not name: + return {"error": "Missing 'name' parameter"} + + # Dry-run 驗證 + if resource == "pod": + dry_run = await executor.validate_pod_exists(name, namespace) + else: + dry_run = await executor.validate_deployment_exists(name, namespace) + + if not dry_run.passed: + return {"error": dry_run.message, "dry_run": False} + + # 執行刪除 + if resource == "pod": + result = await executor.delete_pod(name, namespace) + else: + # deployment 不支援直接刪除,改用 restart + return {"error": "Direct deployment deletion not supported, use restart"} + + return { + "success": result.success, + "message": result.message, + "duration_ms": result.duration_ms, + } + + elif tool_name == "kubectl_scale": + namespace = parameters.get("namespace", "awoooi-prod") + deployment = parameters.get("deployment", "") + replicas = parameters.get("replicas", 1) + if not deployment: + return {"error": "Missing 'deployment' parameter"} + + cmd = f"kubectl scale deployment/{deployment} --replicas={replicas} -n {namespace}" + result = await executor.execute_kubectl_command(cmd) + return { + "success": result.success, + "scaled": result.success, + "replicas": replicas, + "message": result.message, + } + + elif tool_name == "kubectl_restart": + namespace = parameters.get("namespace", "awoooi-prod") + deployment = parameters.get("deployment", "") + if not deployment: + return {"error": "Missing 'deployment' parameter"} + + dry_run = await executor.validate_deployment_exists(deployment, namespace) + if not dry_run.passed: + return {"error": dry_run.message, "dry_run": False} + + result = await executor.restart_deployment(deployment, namespace) + return { + "success": result.success, + "restarted": result.success, + "message": result.message, + "duration_ms": result.duration_ms, + } + + else: + return {"error": f"Unknown kubernetes tool: {tool_name}"} + + # ============================================= + # Database: 查詢 incident/approval 歷史 (Phase 13.2 #81) + # ============================================= + elif server.name == "database": + if tool_name == "query": + # TODO: 整合真實 PostgreSQL 查詢 + logger.info(f"[TODO] Database query: {parameters.get('sql', '')[:50]}") + return {"rows": [], "affected": 0, "note": "Phase 13.2 #81 待實作"} + else: + return {"error": f"Unknown database tool: {tool_name}"} + + # ============================================= + # Mock fallback + # ============================================= + logger.info(f"[MOCK] HTTP call to {server.endpoint}: {tool_name}") + return {"status": "ok", "mock": True} async def _execute_stdio( self,