feat(api): Phase 13.2 #80 Kubernetes MCP Tool real implementation

- Integrate real ActionExecutor instead of mock responses
- kubectl_get: Execute real kubectl get with JSON output
- kubectl_delete: Dry-run validation + actual pod deletion
- kubectl_scale: Real kubectl scale command
- kubectl_restart: Deployment rollout restart with validation
- Database query placeholder for #81

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-25 12:30:13 +08:00
parent b8f9cd315c
commit d31160f4e1

View File

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