#!/usr/bin/env python3 """Prove one Fleet Ops demo-token MCP call, then prove its revocation.""" from __future__ import annotations import argparse import json import sys import urllib.error import urllib.request from dataclasses import dataclass from pathlib import Path from typing import Any from scripts.revoke_demo_token import revoke from services.runtime.demo_token import load_demo_token_signing_key, mint_demo_token @dataclass(frozen=True) class HttpResult: status: int content_type: str body: bytes def post_tool_call(endpoint: str, token: str, *, timeout_seconds: float) -> HttpResult: request = urllib.request.Request( # noqa: S310 - operator supplies an explicit HTTP(S) MCP endpoint endpoint, data=json.dumps( { "jsonrpc": "2.0", "id": "demo-token-flow-verification", "method": "tools/call", "params": {"name": "mobilityops.operations.summary", "arguments": {}}, }, separators=(",", ":"), ).encode("utf-8"), headers={ "Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json", "MCP-Protocol-Version": "2025-11-25", }, method="POST", ) try: with urllib.request.urlopen(request, timeout=timeout_seconds) as response: # noqa: S310 return HttpResult(response.status, response.headers.get_content_type(), response.read()) except urllib.error.HTTPError as error: try: body = error.read() except (OSError, ConnectionError): # The status/header proof remains authoritative when the peer closes # the optional error-body stream immediately after sending its 401. body = b"" return HttpResult(error.code, error.headers.get_content_type(), body) def verify_flow( endpoint: str, key_file: Path, revocations_file: Path, *, timeout_seconds: float = 15.0, ) -> tuple[HttpResult, HttpResult]: """Run a bounded live proof and revoke its generated bearer in all cases.""" key = load_demo_token_signing_key(key_file) token, jti = mint_demo_token(key, ttl_hours=1) try: valid = post_tool_call(endpoint, token, timeout_seconds=timeout_seconds) if valid.status != 200: raise RuntimeError(f"valid demo-token call returned HTTP {valid.status}") if valid.content_type != "application/json": raise RuntimeError("valid demo-token call did not return application/json") try: document: Any = json.loads(valid.body) except (UnicodeDecodeError, ValueError) as exc: raise RuntimeError("valid demo-token call returned invalid JSON") from exc if not isinstance(document, dict): raise RuntimeError("valid demo-token call returned a non-object JSON payload") finally: revoke(revocations_file, jti) revoked = post_tool_call(endpoint, token, timeout_seconds=timeout_seconds) if revoked.status != 401: raise RuntimeError(f"revoked demo-token call returned HTTP {revoked.status}, expected 401") return valid, revoked def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--endpoint", required=True) parser.add_argument("--key-file", type=Path, required=True) parser.add_argument("--revocations-file", type=Path, required=True) parser.add_argument("--timeout-seconds", type=float, default=15.0) args = parser.parse_args() if not 1 <= args.timeout_seconds <= 30: raise ValueError("timeout-seconds must be between 1 and 30") valid, revoked = verify_flow( args.endpoint, args.key_file, args.revocations_file, timeout_seconds=args.timeout_seconds, ) print(f"valid_call_http={valid.status}") print(f"valid_call_content_type={valid.content_type}") print(f"valid_call_bytes={len(valid.body)}") print(f"revoked_call_http={revoked.status}") return 0 if __name__ == "__main__": sys.exit(main())