#!/usr/bin/env python3
"""Dependency-free MCP stdio adapter for the private Strix Portal API."""
from __future__ import annotations

import json
import os
import sys
import urllib.error
import urllib.request
from typing import Any

BASE = os.environ.get("STRIX_PORTAL_URL", "https://strix.greener-business.com").rstrip("/")
TOKEN = os.environ.get("STRIX_PORTAL_TOKEN", "")

TOOLS = [
    {"name":"strix_capabilities","description":"Discover live Strix tools, providers, modes and artifacts.","inputSchema":{"type":"object","properties":{},"additionalProperties":False}},
    {"name":"strix_list_scans","description":"List recent Strix scans and their state.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":200}},"additionalProperties":False}},
    {"name":"strix_get_scan","description":"Read one scan status, summary, and log tail.","inputSchema":{"type":"object","properties":{"scan_id":{"type":"string"}},"required":["scan_id"],"additionalProperties":False}},
    {"name":"strix_get_findings","description":"Read validated developer-ready findings for a scan.","inputSchema":{"type":"object","properties":{"scan_id":{"type":"string"}},"required":["scan_id"],"additionalProperties":False}},
    {"name":"strix_list_artifacts","description":"List the report, JSON, CSV, SARIF and run metadata artifacts available for a scan.","inputSchema":{"type":"object","properties":{"scan_id":{"type":"string"}},"required":["scan_id"],"additionalProperties":False}},
    {"name":"strix_get_artifact","description":"Read one text-based scan artifact by its allowlisted name.","inputSchema":{"type":"object","properties":{"scan_id":{"type":"string"},"name":{"type":"string","enum":["penetration_test_report.md","vulnerabilities.json","vulnerabilities.csv","findings.sarif","run.json"]}},"required":["scan_id","name"],"additionalProperties":False}},
    {"name":"strix_start_scan","description":"Queue an authorized, non-destructive autonomous security scan. Authorization and backup attestations must be true in reality.","inputSchema":{"type":"object","properties":{"targets":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":4},"mode":{"type":"string","enum":["quick","standard","deep"]},"provider":{"type":"string","enum":["codex","smart-router"]},"instruction":{"type":"string"},"authorized":{"type":"boolean"},"backup_confirmed":{"type":"boolean"}},"required":["targets","mode","provider","authorized","backup_confirmed"],"additionalProperties":False}},
    {"name":"strix_cancel_scan","description":"Cancel a queued or running Strix scan.","inputSchema":{"type":"object","properties":{"scan_id":{"type":"string"}},"required":["scan_id"],"additionalProperties":False}},
]

def request(method: str, path: str, body: dict[str, Any] | None = None) -> Any:
    if not TOKEN:
        raise RuntimeError("STRIX_PORTAL_TOKEN is not configured")
    payload = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(
        BASE + path,
        data=payload,
        method=method,
        headers={"Authorization": f"Bearer {TOKEN}", "Content-Type":"application/json", "Accept":"application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            return json.loads(response.read())
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")[:2000]
        raise RuntimeError(f"Portal HTTP {exc.code}: {detail}") from exc

def request_text(path: str) -> str:
    if not TOKEN:
        raise RuntimeError("STRIX_PORTAL_TOKEN is not configured")
    req = urllib.request.Request(
        BASE + path,
        headers={"Authorization": f"Bearer {TOKEN}", "Accept":"text/plain, application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            return response.read(5_000_000).decode("utf-8", errors="replace")
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")[:2000]
        raise RuntimeError(f"Portal HTTP {exc.code}: {detail}") from exc

def call(name: str, args: dict[str, Any]) -> Any:
    scan_id = str(args.get("scan_id", ""))
    if name == "strix_capabilities": return request("GET", "/api/v1/capabilities")
    if name == "strix_list_scans": return request("GET", f"/api/v1/scans?limit={int(args.get('limit',50))}")
    if name == "strix_get_scan": return request("GET", f"/api/v1/scans/{scan_id}")
    if name == "strix_get_findings": return request("GET", f"/api/v1/scans/{scan_id}/findings")
    if name == "strix_list_artifacts": return request("GET", f"/api/v1/scans/{scan_id}/artifacts")
    if name == "strix_get_artifact": return {"name":str(args.get("name", "")),"content":request_text(f"/api/v1/scans/{scan_id}/artifacts/{str(args.get('name', ''))}")}
    if name == "strix_start_scan": return request("POST", "/api/v1/scans", args)
    if name == "strix_cancel_scan": return request("POST", f"/api/v1/scans/{scan_id}/cancel", {})
    raise RuntimeError(f"Unknown tool: {name}")

def respond(message_id: Any, result: Any = None, error: Any = None) -> None:
    payload={"jsonrpc":"2.0","id":message_id}
    payload["error" if error else "result"] = error if error else result
    sys.stdout.write(json.dumps(payload,separators=(",",":"))+"\n")
    sys.stdout.flush()

def main() -> None:
    for line in sys.stdin:
        message: dict[str, Any] = {}
        try:
            message=json.loads(line); method=message.get("method"); message_id=message.get("id")
            if method=="initialize": respond(message_id,{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":False}},"serverInfo":{"name":"strix-security-portal","version":"1.0.0"}})
            elif method=="ping": respond(message_id,{})
            elif method=="tools/list": respond(message_id,{"tools":TOOLS})
            elif method=="tools/call":
                params=message.get("params") or {}; result=call(str(params.get("name")),params.get("arguments") or {})
                respond(message_id,{"content":[{"type":"text","text":json.dumps(result,ensure_ascii=False,indent=2)}],"isError":False})
            elif message_id is not None: respond(message_id,error={"code":-32601,"message":"Method not found"})
        except Exception as exc:
            if message.get("id") is not None:
                respond(message.get("id"),{"content":[{"type":"text","text":str(exc)}],"isError":True})

if __name__=="__main__": main()
