"""RedactTool MCP JSON-RPC example. Supply your own endpoint and key.
No network activity occurs until this script is explicitly executed.
Requires: pip install requests
"""
import os
import time
import uuid
from pathlib import Path
from urllib.parse import urlparse
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

base = os.environ["REDACTTOOL_BASE_URL"].rstrip("/")
key = os.environ["REDACTTOOL_API_KEY"]
source = Path(os.environ.get("REDACTTOOL_FILE", "sample.txt"))
parsed = urlparse(base)
if parsed.scheme != "https" and parsed.hostname not in {"localhost", "127.0.0.1"} and os.environ.get("REDACTTOOL_ALLOW_HTTP_TRIAL") != "1":
    raise ValueError("Use HTTPS, or explicitly set REDACTTOOL_ALLOW_HTTP_TRIAL=1 for non-confidential trial files")
output = source.with_name(source.stem + "-redacted" + source.suffix)
if output.exists():
    raise FileExistsError(output)
session = requests.Session()
# Retry safe reads when an idle connection closes. Do not replay POST bodies.
retry = Retry(total=3, connect=3, read=3, backoff_factor=0.5,
              allowed_methods=frozenset({"GET"}), status_forcelist=(502, 503, 504))
for scheme in ("https://", "http://"):
    session.mount(scheme, HTTPAdapter(max_retries=retry))
session.headers["Authorization"] = "Bearer " + key

def checked(response):
    response.raise_for_status()
    return response.json()

with source.open("rb") as content:
    uploaded = checked(session.post(base + "/v1/files",
        files={"file": (source.name, content)}, timeout=(15, 120)))
# Keep this ID if you retry this same creation request.
request_id = uuid.uuid4().hex
print("Keep file_id and Idempotency-Key for recovery:", uploaded["file_id"], request_id)
def rpc(method, params, identity):
    # All calls here are reads, initialization, or redact_file with a stable
    # request_id. A transport retry replays the exact same idempotent arguments.
    for attempt in range(3):
        try:
            response = session.post(base + "/mcp", json={"jsonrpc":"2.0", "id":identity,
                "method":method, "params":params}, timeout=(15,60))
            if response.status_code in (502,503,504):
                response.close()
                raise requests.ConnectionError("temporary service response")
            value = checked(response)
            break
        except (requests.ConnectionError, requests.Timeout):
            if attempt == 2: raise
            time.sleep(attempt + 1)
    if "error" in value: raise RuntimeError(value["error"])
    result = value["result"]
    if result.get("isError"): raise RuntimeError(result.get("content"))
    return result

import json
rpc("initialize", {"protocolVersion":"2025-03-26", "capabilities":{},
    "clientInfo":{"name":"maskmaster-example","version":"1.0"}}, 1)
rpc("tools/list", {}, 2)
def tool(name,args,identity):
    return json.loads(rpc("tools/call", {"name":name,"arguments":args},identity)["content"][0]["text"])
job = tool("redact_file", {"file_id":uploaded["file_id"],"mode":"placeholder","request_id":request_id},3)
print("Job:", job["id"])
# A local wait timeout does not cancel server processing.
deadline = time.monotonic() + 3600
while job["status"] not in {"completed", "failed"}:
    if time.monotonic() >= deadline:
        raise TimeoutError("Resume polling this job ID; do not create a new job")
    time.sleep(5)
    job = tool("get_result", {"job_id":job["id"]}, 4)
if job["status"] == "failed":
    raise RuntimeError(job.get("error") or "job_failed")
# Use a known same-origin path, never an arbitrary returned host.
with session.get(base + "/v1/redactions/" + job["id"] + "/file",
                 stream=True, timeout=(15, 120)) as result:
    result.raise_for_status()
    with output.open("xb") as saved:
        for chunk in result.iter_content(65536):
            saved.write(chunk)
print("Saved:", output)
