Guides

Python

There is no Python MCP host yet, but the REST API is small and the policy-layer pattern fits any orchestration framework — LangChain, LlamaIndex, custom — equally well.

Prerequisites

  • Python 3.10+.
  • requests for sync, httpx for async. We’ll show both.
  • AC_AGENT_KEY in env.

Sync — requests

ac_client.py
import os
import uuid
import requests

BASE = os.environ.get("AC_API_BASE", "https://api.advisorscrypto.com")
KEY = os.environ["AC_AGENT_KEY"]


def _call(method, path, body=None, idempotency_key=None):
    headers = {
        "Authorization": f"Bearer {KEY}",
        "Content-Type": "application/json",
    }
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key

    res = requests.request(
        method,
        f"{BASE}{path}",
        headers=headers,
        json=body,
        timeout=20,
    )
    res.raise_for_status()
    return res.json() if res.content else None


def get_mandate():
    return _call("GET", "/api/agent/v1/mandate")


def validate(payload):
    return _call("POST", "/api/agent/v1/policy/validate", body=payload)


def propose(payload):
    return _call(
        "POST",
        "/api/agent/v1/trades/propose",
        body=payload,
        idempotency_key=str(uuid.uuid4()),
    )

Usage

python
from ac_client import get_mandate, validate, propose

mandate = get_mandate()
print("crypto sleeve %:", mandate["snapshot"]["cryptoPct"])

decision = validate({
    "action": "buy",
    "assetSymbol": "BTC",
    "assetType": "crypto",
    "amountUsd": 5000,
})

if not decision["allowed"]:
    print("Blocked:", [v["rule"] for v in decision["violations"]])
else:
    result = propose({
        "action": "buy",
        "assetSymbol": "BTC",
        "assetType": "crypto",
        "amountUsd": 5000,
        "rationale": "Add a 1% nudge while crypto sleeve has headroom.",
    })
    print("Pending attestation #", result["serviceRequestId"])

Async — httpx

ac_async.py
import os
import uuid
import httpx

BASE = os.environ.get("AC_API_BASE", "https://api.advisorscrypto.com")
KEY = os.environ["AC_AGENT_KEY"]


class AcClient:
    """Async REST client. Reuses one underlying httpx.AsyncClient."""

    def __init__(self) -> None:
        self._client = httpx.AsyncClient(
            timeout=20,
            headers={
                "Authorization": f"Bearer {KEY}",
                "Content-Type": "application/json",
            },
        )

    async def close(self) -> None:
        await self._client.aclose()

    async def _call(self, method, path, body=None, idempotency_key=None):
        headers = {}
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key
        res = await self._client.request(
            method, f"{BASE}{path}", json=body, headers=headers,
        )
        res.raise_for_status()
        return res.json() if res.content else None

    async def validate(self, payload):
        return await self._call("POST", "/api/agent/v1/policy/validate", body=payload)

    async def propose(self, payload):
        return await self._call(
            "POST",
            "/api/agent/v1/trades/propose",
            body=payload,
            idempotency_key=str(uuid.uuid4()),
        )

Patterns that pay off

  • One client per process. Both libraries pool connections — instantiate once at startup, reuse for the lifetime of the process.
  • Treat 4xx as data, 5xx as alarms. Policy blocks come back as 200 with allowed: false; real 4xx errors mean malformed payloads. Pageable failures are 5xx.
  • Surface attestation status to the user. After a successful propose, tell them the trade is pending attestation in the AC app — not that it happened.
Last updated 2026-06-15