feat(cra): Pull-Flow — Findings vom Scanner-MCP ziehen + assessen
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
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>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""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 []
|
||||
Reference in New Issue
Block a user