Python client SDK for OpenAdTrace MCP server — advertising audit trails for agentic AI
Project description
OpenAdTrace Python SDK
Python client for the OpenAdTrace MCP server. Zero external dependencies.
Install
pip install openadtrace-sdk
Setup
You need a running OpenAdTrace server. Quickest way:
# Clone and start with Docker
git clone https://github.com/Samrajtheailyceum/open-ad-trace.git
cd open-ad-trace && docker compose up -d
# Verify it's running
curl http://localhost:8787/health
Or without Docker:
cd server && npm install && npm run migrate:local && npx wrangler dev
Quick Start
from openadtrace_sdk import OpenAdTraceClient
with OpenAdTraceClient("http://localhost:8787/mcp") as client:
# Record an event
result = client.trace_event(
protocol="openrtb",
layer="bid_request",
actor_role="dsp",
actor_id="my-dsp-001",
event_type="bid",
action="submit_bid",
status="success",
latency_ms=12,
bid_price_cpm=4.50,
)
print(f"Recorded: trace_id={result.trace_id}, span_id={result.span_id}")
The client handles MCP session management automatically — no setup, no handshake code, no reconnection logic.
Async Quick Start
import asyncio
from openadtrace_sdk import AsyncOpenAdTraceClient
async def main():
async with AsyncOpenAdTraceClient("http://localhost:8787/mcp") as client:
result = await client.trace_event(
protocol="openrtb",
layer="bid_request",
actor_role="dsp",
actor_id="my-dsp-001",
event_type="bid",
action="submit_bid",
status="success",
)
print(result.trace_id)
asyncio.run(main())
Authentication
If your server has OAT_API_TOKEN set:
client = OpenAdTraceClient("http://localhost:8787/mcp", token="your-api-token")
Real-World Integration Examples
DSP — Log every bid
import time
from openadtrace_sdk import OpenAdTraceClient
with OpenAdTraceClient("http://localhost:8787/mcp", token="your-token") as oat:
# Your existing bid logic
start = time.time()
bid_result = your_bidder.submit_bid(bid_request)
latency = int((time.time() - start) * 1000)
# Add one call to create an audit trail
oat.trace_event(
protocol="openrtb",
layer="bid_request",
actor_role="dsp",
actor_id="your-dsp-id",
event_type="bid",
action="submit_bid",
status="success" if bid_result.won else "failure",
latency_ms=latency,
bid_price_cpm=bid_result.price,
campaign_id=bid_result.campaign_id,
rationale_visible="Floor price met, brand safety passed",
)
SSP — Log supply curation decisions
oat.trace_event(
protocol="openrtb",
layer="auction",
actor_role="ssp",
actor_id="your-ssp-id",
event_type="decision",
action="curate_supply",
status="success",
rationale_visible="Filtered 12 bid requests to 3 premium-only opportunities",
)
Agency — Log campaign activation
oat.trace_event(
protocol="adcp",
layer="execution",
actor_role="buyer_agent",
actor_id="agency-agent-001",
event_type="decision",
action="activate_campaign",
status="success",
campaign_id="client-campaign-q4",
media_cost=25000.00,
currency="USD",
rationale_visible="Activated CTV campaign per client-approved media plan",
policy_basis="governance_check: PASSED",
)
All 11 Methods
| Method | What it does |
|---|---|
trace_event(...) |
Record an advertising event |
validate_event(...) |
Check an event against schema without storing |
query_traces(...) |
Search events by actor, campaign, time range |
analyse_traces(...) |
Get quality scores and insights |
redact_event(...) |
Strip PII and secrets from an event |
scan_protocols(...) |
Check domains for protocol discovery files |
verify_supply_chain(...) |
Validate ads.txt / sellers.json |
trust_score(...) |
Get reliability score (0-100) for an actor |
correlate_traces(...) |
Find related events across agents |
compliance_check(...) |
Check against protocol guidelines |
risk_signals(...) |
Detect risk flags for a campaign or actor |
Builder Pattern
For complex events, use the fluent builder:
from openadtrace_sdk import TraceEventBuilder, Protocol, Layer, Status
event = (
TraceEventBuilder()
.protocol(Protocol.OPENRTB)
.layer(Layer.BID_DECISION)
.actor("dsp", "my-dsp-001")
.event("bid", "evaluate")
.status(Status.SUCCESS)
.with_campaign("camp-q4-001")
.latency(8)
.bid_price(4.50)
.bid_floor(2.00)
.rationale("CPM within target range and brand-safe context")
.build()
)
with OpenAdTraceClient("http://localhost:8787/mcp") as client:
result = client.trace_event(**event)
Batch Requests
Send up to 20 calls in a single HTTP request:
with OpenAdTraceClient("http://localhost:8787/mcp") as client:
results = (
client.batch()
.trace_event(
protocol="openrtb", layer="bid_request",
actor_role="dsp", actor_id="my-dsp-001",
event_type="bid", action="submit_bid", status="success",
)
.trust_score(actor_id="my-dsp-001")
.risk_signals(actor_id="my-dsp-001", period_days=7)
.execute()
)
trace_result, trust_result, risk_result = results
print(f"Trust score: {trust_result.trust_score}")
Error Handling
from openadtrace_sdk import (
OpenAdTraceClient, OpenAdTraceError,
AuthenticationError, RateLimitError, NetworkError, ToolError,
)
try:
with OpenAdTraceClient("http://localhost:8787/mcp", token="key") as client:
result = client.trust_score(actor_id="my-dsp-001")
except AuthenticationError:
print("Invalid API token")
except RateLimitError as e:
print(f"Rate limited — retry after {e.retry_after_seconds}s")
except NetworkError as e:
print(f"Connection problem: {e}")
except ToolError as e:
print(f"Tool '{e.tool_name}' failed: {e}")
except OpenAdTraceError as e:
print(f"SDK error: {e}")
Enums (optional)
Use enums for type safety, or just pass strings — both work:
from openadtrace_sdk import Protocol, Layer, Status
# These two calls are identical:
client.trace_event(protocol=Protocol.OPENRTB, layer=Layer.BID_REQUEST, status=Status.SUCCESS, ...)
client.trace_event(protocol="openrtb", layer="bid_request", status="success", ...)
Enterprise Features
Context Signals — brand safety, audience, viewability, attention
from openadtrace_sdk.context import ContextSignals, GARMFloor, GARMCategory
ctx = (
ContextSignals()
.brand_safety(floor=GARMFloor.HIGH, exclude=[GARMCategory.ALCOHOL, GARMCategory.GAMBLING],
provider="ias", verdict="brand_safe", score=92.0)
.audience(segments=["auto_intenders", "luxury_travel"], min_age=25, first_party=True)
.viewability(target_rate=0.70, provider="moat")
.attention(target_seconds=8.0, provider="adelaide")
.geo(country="US", region="northeast", dma="501")
.device(types=["ctv", "mobile"])
.content(category="entertainment", publisher="espn.com")
.supply_path(hops=1, direct=True)
)
# Attach to any trace event
client.trace_event(..., meta=ctx.to_dict())
Campaign Lifecycle Tracking
from openadtrace_sdk.campaign import CampaignTracker
campaign = CampaignTracker(
client,
campaign_id="brand-q4-2025",
advertiser="Acme Corp",
brand="Acme Premium",
budget=500_000.00,
currency="USD",
context=ctx, # auto-attached to every event
)
campaign.plan("CTV + premium digital, 25-54 auto-intenders")
campaign.activate()
campaign.log_spend(25_000, vendor="the-trade-desk", channel="ctv")
campaign.log_spend(15_000, vendor="dv360", channel="display")
campaign.bid(price_cpm=4.50, floor_cpm=2.00, won=True)
campaign.governance_check("brand_safety", passed=True, details="All placements verified")
campaign.complete()
report = campaign.summary()
# {'total_spend': 40000, 'budget_remaining': 460000, 'spend_by_vendor': {...}}
Governance Workflows — human approval chains
from openadtrace_sdk.governance import GovernanceWorkflow, ApprovalGate
gov = GovernanceWorkflow(client, workflow_id="launch-approval", campaign_id="brand-q4")
gov.add_gate(ApprovalGate(name="brand_safety", approver="brand_team", required=True))
gov.add_gate(ApprovalGate(name="age_gate", approver="compliance", regulations=["ftc", "ttb"]))
gov.add_gate(ApprovalGate(name="budget", approver="finance", threshold=100_000))
gov.approve("brand_safety", approved_by="jane@brand.com", notes="All clear")
gov.approve("age_gate", approved_by="compliance@brand.com", notes="21+ confirmed")
gov.approve("budget", approved_by="cfo@brand.com")
if gov.all_approved():
campaign.activate()
Multi-Market Support — 22 markets with regulatory presets
from openadtrace_sdk.markets import get_market, markets_restricting, list_markets
us = get_market("US") # alcohol_min_age=21, regulations=['ftc','coppa','ccpa',...]
uk = get_market("GB") # alcohol_min_age=18, regulations=['uk_gdpr','asa','ofcom',...]
jp = get_market("JP") # alcohol_min_age=20, regulations=['appi','jaro']
# All markets restricting alcohol
markets_restricting("alcohol") # 18 markets
# Verify age-gating
us.age_gate_check(min_age=21) # True
us.age_gate_check(min_age=18) # False — US requires 21+
Available markets: US, CA, MX, GB, DE, FR, ES, IT, NL, SE, JP, AU, SG, IN, KR, CN, AE, SA, ZA, NG, BR, AR.
Requirements
- Python 3.9+
- No external dependencies (stdlib only)
License
Apache-2.0. See the main repository for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openadtrace_sdk-0.3.0.tar.gz.
File metadata
- Download URL: openadtrace_sdk-0.3.0.tar.gz
- Upload date:
- Size: 44.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6e3b913d9d3a038a52dd9c27815e309f77750ec2e35e504dec2a2ba3e80a97b
|
|
| MD5 |
8c068447796817a262c64ebcec574c06
|
|
| BLAKE2b-256 |
4445d067d9dc333e78ea982a5e2ce18d7b64bf851b078fd18fb5ce6db749099f
|
File details
Details for the file openadtrace_sdk-0.3.0-py3-none-any.whl.
File metadata
- Download URL: openadtrace_sdk-0.3.0-py3-none-any.whl
- Upload date:
- Size: 46.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1e3e0e652b9cf4dc32828e5512e80de5fbb441766bd7a0099feee6bd37ef205
|
|
| MD5 |
35883038fc9c5959d308d9680ac4b639
|
|
| BLAKE2b-256 |
c3dc272fc7cb66a43af814fdc095e49ccb68c8e7f17d2d5eebc8449a790c1108
|