feat(awooop): bridge legacy mcp audit into gateway timeline
This commit is contained in:
@@ -9,6 +9,7 @@ before every runtime model has been refreshed.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import hashlib
|
||||
import time
|
||||
import uuid
|
||||
from typing import Any
|
||||
@@ -22,6 +23,8 @@ from src.services.mcp_audit_context import normalize_mcp_audit_session_id
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
_REDACT_KEYS = {"token", "password", "secret", "api_key", "authorization", "key"}
|
||||
_LEGACY_PROJECT_ID = "awoooi"
|
||||
_REAL_GATEWAY_PATH = "awooop_mcp_gateway"
|
||||
|
||||
|
||||
def infer_flywheel_node(mcp_server: str, tool_name: str) -> str:
|
||||
@@ -79,6 +82,109 @@ def _extract_audit_context(parameters: dict[str, Any]) -> dict[str, Any]:
|
||||
return audit_context if isinstance(audit_context, dict) else {}
|
||||
|
||||
|
||||
def _extract_project_id(
|
||||
parameters: dict[str, Any],
|
||||
audit_context: dict[str, Any],
|
||||
) -> str:
|
||||
"""Legacy MCP calls predate AwoooP tenancy; keep the bridge explicit."""
|
||||
for value in (audit_context.get("project_id"), parameters.get("project_id")):
|
||||
if value:
|
||||
return str(value)
|
||||
return _LEGACY_PROJECT_ID
|
||||
|
||||
|
||||
def _extract_run_id(audit_context: dict[str, Any]) -> uuid.UUID | None:
|
||||
value = audit_context.get("run_id")
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return uuid.UUID(str(value))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _compact(value: Any, limit: int) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
return str(value)[:limit]
|
||||
|
||||
|
||||
def _stable_hash(value: Any) -> str:
|
||||
canonical = json.dumps(value, sort_keys=True, ensure_ascii=False, default=str)
|
||||
return hashlib.sha256(canonical.encode()).hexdigest()
|
||||
|
||||
|
||||
def _bridge_tool_name(mcp_server: str, tool_name: str) -> str:
|
||||
combined = f"legacy:{mcp_server}:{tool_name}"
|
||||
if len(combined) <= 128:
|
||||
return combined
|
||||
return str(tool_name)[:128]
|
||||
|
||||
|
||||
def _should_bridge_to_awooop(audit_context: dict[str, Any]) -> bool:
|
||||
"""Skip real Gateway calls; they already write first-class audit rows."""
|
||||
return audit_context.get("gateway_path") != _REAL_GATEWAY_PATH
|
||||
|
||||
|
||||
async def _record_awooop_gateway_bridge(
|
||||
db: Any,
|
||||
*,
|
||||
project_id: str,
|
||||
run_id: uuid.UUID | None,
|
||||
trace_id: str | None,
|
||||
agent_id: str | None,
|
||||
mcp_server: str,
|
||||
tool_name: str,
|
||||
input_params: dict[str, Any],
|
||||
output_result: Any | None,
|
||||
duration_ms: int,
|
||||
success: bool,
|
||||
error_message: str | None,
|
||||
flywheel_node: str | None,
|
||||
audit_context: dict[str, Any],
|
||||
) -> None:
|
||||
"""Mirror legacy direct provider calls into AwoooP audit as bridge rows."""
|
||||
result_status = "success" if success else "failed"
|
||||
gate_result = {
|
||||
"schema_version": "legacy_mcp_bridge_v1",
|
||||
"gateway_path": audit_context.get("gateway_path") or "legacy_registry_provider",
|
||||
"policy_enforced": False,
|
||||
"not_used_reason": "legacy direct provider path; bridge audit only",
|
||||
"legacy_mcp_server": mcp_server,
|
||||
"legacy_tool_name": tool_name,
|
||||
"flywheel_node": flywheel_node,
|
||||
}
|
||||
await db.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO awooop_mcp_gateway_audit (
|
||||
project_id, run_id, trace_id, agent_id,
|
||||
tool_name, input_hash, output_hash, gate_result,
|
||||
result_status, block_gate, block_reason, latency_ms
|
||||
)
|
||||
VALUES (
|
||||
:project_id, :run_id, :trace_id, :agent_id,
|
||||
:tool_name, :input_hash, :output_hash, CAST(:gate_result AS jsonb),
|
||||
:result_status, NULL, :block_reason, :latency_ms
|
||||
)
|
||||
"""
|
||||
),
|
||||
{
|
||||
"project_id": project_id,
|
||||
"run_id": run_id,
|
||||
"trace_id": _compact(trace_id, 128),
|
||||
"agent_id": _compact(agent_id or "legacy-mcp-provider", 128),
|
||||
"tool_name": _bridge_tool_name(mcp_server, tool_name),
|
||||
"input_hash": _stable_hash(input_params),
|
||||
"output_hash": _stable_hash(output_result) if output_result is not None else None,
|
||||
"gate_result": json.dumps(gate_result, ensure_ascii=False, default=str),
|
||||
"result_status": result_status,
|
||||
"block_reason": _compact(error_message, 256) if not success else None,
|
||||
"latency_ms": duration_ms,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def record_mcp_call(
|
||||
*,
|
||||
mcp_server: str,
|
||||
@@ -102,9 +208,13 @@ async def record_mcp_call(
|
||||
flywheel_node = flywheel_node or infer_flywheel_node(mcp_server, tool_name)
|
||||
incident_id = incident_id or _extract_incident_id(input_params)
|
||||
agent_role = agent_role or audit_context.get("agent_role")
|
||||
project_id = _extract_project_id(input_params, audit_context)
|
||||
run_id = _extract_run_id(audit_context)
|
||||
trace_id = audit_context.get("trace_id") or incident_id or session_id
|
||||
agent_id = audit_context.get("agent_id") or agent_role
|
||||
|
||||
try:
|
||||
async with get_db_context() as db:
|
||||
async with get_db_context(project_id) as db:
|
||||
await db.execute(
|
||||
text(
|
||||
"""
|
||||
@@ -165,6 +275,23 @@ async def record_mcp_call(
|
||||
"duration_ms": duration_ms,
|
||||
},
|
||||
)
|
||||
if _should_bridge_to_awooop(audit_context):
|
||||
await _record_awooop_gateway_bridge(
|
||||
db,
|
||||
project_id=project_id,
|
||||
run_id=run_id,
|
||||
trace_id=trace_id,
|
||||
agent_id=agent_id,
|
||||
mcp_server=mcp_server,
|
||||
tool_name=tool_name,
|
||||
input_params=input_params,
|
||||
output_result=output_result,
|
||||
duration_ms=duration_ms,
|
||||
success=success,
|
||||
error_message=error_message,
|
||||
flywheel_node=flywheel_node,
|
||||
audit_context=audit_context,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"mcp_audit_write_failed",
|
||||
|
||||
Reference in New Issue
Block a user