feat(aiops): add mcp agent loop foundation
Some checks failed
CD Pipeline / tests (push) Successful in 1m59s
Code Review / ai-code-review (push) Successful in 28s
run-migration / migrate (push) Failing after 24s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-05-01 13:21:19 +08:00
parent 9db87f177e
commit 7e4d995e4b
15 changed files with 1162 additions and 28 deletions

View File

@@ -12,11 +12,65 @@ Provider 註冊中心,實現依賴注入 (DI) 模式:
import structlog
from src.plugins.mcp.interfaces import MCPToolProvider
from src.plugins.mcp.interfaces import MCPTool, MCPToolProvider, MCPToolResult
logger = structlog.get_logger(__name__)
class AuditedMCPToolProvider(MCPToolProvider):
"""Provider wrapper that writes every MCP tool call to the audit subsystem."""
def __init__(self, provider: MCPToolProvider) -> None:
self._provider = provider
@property
def name(self) -> str:
return self._provider.name
@property
def enabled(self) -> bool:
return self._provider.enabled
async def list_tools(self) -> list[MCPTool]:
return await self._provider.list_tools()
async def execute(
self,
tool_name: str,
parameters: dict,
) -> MCPToolResult:
from src.services.mcp_audit_service import monotonic_ms, record_mcp_call
audit_context = parameters.get("_mcp_audit") if isinstance(parameters, dict) else None
provider_parameters = {
key: value for key, value in parameters.items()
if key != "_mcp_audit"
}
started = monotonic_ms()
result: MCPToolResult | None = None
try:
result = await self._provider.execute(tool_name, provider_parameters)
return result
finally:
duration_ms = monotonic_ms() - started
await record_mcp_call(
mcp_server=self.name,
tool_name=tool_name,
input_params=parameters,
output_result=result.output if result else None,
duration_ms=duration_ms,
success=bool(result.success) if result else False,
error_message=result.error if result else "provider_exception",
session_id=audit_context.get("session_id") if isinstance(audit_context, dict) else None,
flywheel_node=audit_context.get("flywheel_node") if isinstance(audit_context, dict) else None,
incident_id=audit_context.get("incident_id") if isinstance(audit_context, dict) else None,
agent_role=audit_context.get("agent_role") if isinstance(audit_context, dict) else None,
)
async def health_check(self) -> bool:
return await self._provider.health_check()
class ProviderRegistry:
"""
MCP Tool Provider 註冊中心
@@ -52,7 +106,7 @@ class ProviderRegistry:
if provider.name in self._providers:
raise ValueError(f"Provider '{provider.name}' already registered")
self._providers[provider.name] = provider
self._providers[provider.name] = AuditedMCPToolProvider(provider)
logger.info(
"provider_registered",
name=provider.name,