diff --git a/opax-mcp/server.py b/opax-mcp/server.py index 7fcb7c4..664cd99 100644 --- a/opax-mcp/server.py +++ b/opax-mcp/server.py @@ -66,10 +66,10 @@ logger.info(f"OLLAMA_BASE_URL: {OLLAMA_BASE_URL}") # --------------------------------------------------------------------------- async def _verify_auth(request: Request) -> None: - secret = os.getenv("MCP_SECRET") + secret = os.getenv("MCPSECRET", "").strip() if not secret: - logger.error("MCP_SECRET is not configured on the server.") - raise HTTPException(status_code=500, detail="MCP_SECRET not configured") + logger.error("MCPSECRET is not configured on the server.") + raise HTTPException(status_code=500, detail="MCPSECRET not configured") # Perplexity sends 'api-key', others might send 'X-MCP-Secret' or 'Authorization: Bearer ...' token = request.headers.get("api-key") @@ -80,14 +80,10 @@ async def _verify_auth(request: Request) -> None: if auth_header.startswith("Bearer "): token = auth_header[7:] + print(f"DEBUG: token={repr(token)} secret={repr(secret)} match={token==secret if token and secret else False}") + if not token or token != secret: raise HTTPException(status_code=401, detail="Invalid or missing API key") - - -# --------------------------------------------------------------------------- -# Backend Agent helpers (for kall VIDERE til osvauco-agent) -# --------------------------------------------------------------------------- - def _agent_headers() -> dict: """Headers for maskin-til-maskin kall videre til osvauco-agent via X-Internal-Key.""" return { @@ -369,6 +365,40 @@ async def list_commits(p): return await _gitea_get(f"/repos/{p.g async def get_file(p): return await _gitea_get(f"/repos/{p.get('repo', GITEA_REPO)}/contents/{p.get('path', '')}?ref={p.get('ref', 'main')}") async def list_open_issues(p): return await _gitea_get(f"/repos/{p.get('repo', GITEA_REPO)}/issues?state=open&limit=20") async def create_issue(p): return await _gitea_post(f"/repos/{p.get('repo', GITEA_REPO)}/issues", {"title": p.get("title"), "body": p.get("body", "")}) + +async def create_a2h2a_ticket(p: dict) -> dict: + """Creates an A2H2A governance ticket.""" + from google.cloud import firestore + import uuid, hashlib, hmac, os + + db = firestore.Client(project="propane-will-491900-m5") + ticket_id = p.get("ticket_id", f"a2h2a-{uuid.uuid4().hex[:8]}") + + # Generate review token + secret = os.getenv("MCPSECRET", "") + review_token = hmac.new(secret.encode(), ticket_id.encode(), hashlib.sha256).hexdigest()[:32] + review_url = f"https://opax-mcp-zjbqp3prqq-uc.a.run.app/a2h2a/review/{ticket_id}?token={review_token}" + + # Persist to Firestore + db.collection("a2h2a_tickets").document(ticket_id).set({ + "ticket_id": ticket_id, + "source": p.get("source", {}), + "proposed_action": p.get("proposed_action", {}), + "context": p.get("context", {}), + "severity": p.get("severity", "MEDIUM"), + "governance": p.get("governance", {}), + "status": "PENDING", + "review_token": review_token, + "review_url": review_url, + "created_at": firestore.SERVER_TIMESTAMP, + }) + + return { + "status": "created", + "ticket_id": ticket_id, + "review_url": review_url, + "message": "A2H2A ticket created. Awaiting approval." + } async def push_file(p): # ... (beholdt uendret) repo, path = p.get("repo", GITEA_REPO), p.get("path") @@ -462,6 +492,22 @@ TOOLS = { "list_workspace_users": (list_workspace_users, "List alle brukere i Workspace-domenet", {}), "get_emails": (get_emails, "Søk i en brukers e-post", {"type":"object","properties":{"user_key":{"type":"string"},"query":{"type":"string"}},"required":["user_key"]}), "list_calendar_events": (list_calendar_events, "List en brukers kalenderhendelser", {"type":"object","properties":{"user_key":{"type":"string"},"days_ahead":{"type":"integer"}},"required":["user_key"]}), + "create_a2h2a_ticket": ( + create_a2h2a_ticket, + "Creates an Agent-to-Human-to-Agent (A2H2A) governance ticket for system actions requiring approval.", + { + "type": "object", + "properties": { + "ticket_id": {"type": "string"}, + "source": {"type": "object"}, + "proposed_action": {"type": "object"}, + "context": {"type": "object"}, + "severity": {"type": "string", "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"]}, + "governance": {"type": "object"} + }, + "required": ["source", "proposed_action", "context", "governance"] + } + ), } # ---------------------------------------------------------------------------