e140477c0b
CI / nodejs-build (push) Successful in 3m12s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 39s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 15s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 12s
CI / validate-canonical-controls (push) Successful in 12s
CI / loc-budget (push) Successful in 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
(2) Wir als MCP-Client zum compliance-scanner-agent: - scanner_mcp_client.fetch_findings(): streamablehttp_client + ClientSession → list_findings, parst JSON-Text zu Finding-Dicts. Config via SCANNER_MCP_URL/ SCANNER_MCP_TOKEN (unset = leer → UI behält Demo). Transport lazy-importiert. - POST /v1/cra/assess-from-scanner: rohe Scanner-Dicts → toleranter Mapper (behält scan_type/cvss_score/file_path) → assess + Breadth. - Tests: parse_findings_text + no-config-Pfad. Live-Verdrahtung der UI folgt, sobald ihr Endpoint+Token stehen (dann nur Env setzen + useCRA auf /assess-from-scanner zeigen). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
60 lines
2.2 KiB
Python
60 lines
2.2 KiB
Python
"""MCP client to the external compliance-scanner-agent (pull-flow).
|
|
|
|
We connect to THEIR MCP server (Streamable HTTP + Bearer), pull the findings they
|
|
already produced (list_findings), and feed them into our deterministic CRA
|
|
assessment. Their tool returns a JSON array of Finding docs as text content; the
|
|
field shape is bridged by ScannerFinding.from_dict (scan_type/cvss_score/...).
|
|
|
|
Config via env (SCANNER_MCP_URL, SCANNER_MCP_TOKEN) or per-call override. When no
|
|
URL is configured, fetch_findings returns [] — callers fall back to their demo.
|
|
"""
|
|
import json
|
|
import os
|
|
from typing import Optional
|
|
|
|
SCANNER_MCP_URL = os.getenv("SCANNER_MCP_URL", "")
|
|
SCANNER_MCP_TOKEN = os.getenv("SCANNER_MCP_TOKEN", "")
|
|
|
|
|
|
def parse_findings_text(text: str) -> list:
|
|
"""Parse the list_findings tool result (a JSON array, or {findings|results:[...]})."""
|
|
try:
|
|
data = json.loads(text)
|
|
except (json.JSONDecodeError, TypeError):
|
|
return []
|
|
if isinstance(data, dict):
|
|
data = data.get("findings") or data.get("results") or []
|
|
return data if isinstance(data, list) else []
|
|
|
|
|
|
async def fetch_findings(
|
|
repo_id: Optional[str] = None,
|
|
severity: Optional[str] = None,
|
|
limit: int = 200,
|
|
base_url: Optional[str] = None,
|
|
token: Optional[str] = None,
|
|
) -> list:
|
|
"""Pull findings from the scanner's MCP server. Returns [] if unconfigured or on error."""
|
|
url = (base_url or SCANNER_MCP_URL).rstrip("/")
|
|
tok = token or SCANNER_MCP_TOKEN
|
|
if not url:
|
|
return []
|
|
|
|
from mcp.client.session import ClientSession
|
|
from mcp.client.streamable_http import streamablehttp_client
|
|
|
|
headers = {"Authorization": f"Bearer {tok}"} if tok else None
|
|
params: dict = {"limit": limit}
|
|
if repo_id:
|
|
params["repo_id"] = repo_id
|
|
if severity:
|
|
params["severity"] = severity
|
|
|
|
async with streamablehttp_client(url, headers=headers) as (read, write, _):
|
|
async with ClientSession(read, write) as session:
|
|
await session.initialize()
|
|
result = await session.call_tool("list_findings", params)
|
|
|
|
texts = [c.text for c in (result.content or []) if getattr(c, "type", "") == "text"]
|
|
return parse_findings_text(texts[0]) if texts else []
|