┌─────────────────────────────────────────────────────┐
│ Admin UI (Flask) │
│ Port 8001 │
└─────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────┐
│ MCP Server (FastMCP) │
│ Port 8080 │
└─────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────────────┐ ┌────────────────┐ ┌──────────────┐
│ PostgreSQL │ │ Ad Server │ │ Gemini │
│ Database │ │ Adapters │ │ AI API │
└───────────────┘ └────────────────┘ └──────────────┘
Database-backed tenant isolation with:
- Tenants - Publishers with isolated data
- Principals - Advertisers within tenants
- Products - Inventory offerings
- Media Buys - Active campaigns
- MCP API - Token-based via x-adcp-auth header
- Admin UI - Google OAuth with role-based access
- Principal Resolution - Token → Principal → Tenant → Adapter
-- Publishers
tenants (
tenant_id UUID PRIMARY KEY,
name, subdomain, config JSONB,
created_at, updated_at
)
-- Advertisers
principals (
principal_id UUID PRIMARY KEY,
tenant_id FK, name, access_token,
platform_mappings JSONB
)
-- Inventory
products (
product_id UUID PRIMARY KEY,
tenant_id FK, name, description,
formats JSONB, pricing_type,
base_price, targeting_template JSONB
)
-- Campaigns
media_buys (
media_buy_id UUID PRIMARY KEY,
tenant_id FK, principal_id FK,
status, config JSONB, total_budget,
flight_start_date, flight_end_date
)
-- Creatives
creatives (
creative_id UUID PRIMARY KEY,
tenant_id FK, principal_id FK,
format, status, content JSONB
)
-- Audit Trail
audit_logs (
id SERIAL PRIMARY KEY,
tenant_id FK, operation, principal_id,
success, details JSONB, timestamp
)- Request → MCP Server receives API call
- Auth → Resolve principal from token
- Tenant → Load tenant configuration
- Adapter → Instantiate platform adapter
- Operation → Execute business logic
- Audit → Log to database
- Response → Return to client
class AdServerAdapter(ABC):
@abstractmethod
def get_avails(self, request: GetAvailsRequest) -> GetAvailsResponse:
"""Check inventory availability"""
@abstractmethod
def create_media_buy(self, request, packages, start_time, end_time):
"""Create campaign/order"""
@abstractmethod
def activate_media_buy(self, media_buy_id: str):
"""Activate pending campaign"""- GoogleAdManagerAdapter - Full GAM integration
- KevelAdapter - Kevel ad server
- MockAdapter - Testing and development
- TritonAdapter - Audio advertising
Each adapter handles:
- Platform authentication
- API translation
- Error handling
- Dry-run simulation
from fastmcp import Context, app
@app.tool
async def get_products(
context: Context,
brief: Optional[str] = None
) -> GetProductsResponse:
# Tool implementation- HTTP transport with SSE support
- Header-based authentication
- JSON request/response format
- Streaming for large responses
-
Overlay Dimensions (Principal Access)
- Geography (country, state, city, DMA)
- Demographics (age, gender, income)
- Interests and behaviors
- Devices and platforms
- AEE signals
-
Managed-Only Dimensions (Internal)
- First-party segments
- Lookalike audiences
- Platform optimizations
- Reserved inventory
# AdCP Request
{
"targeting_overlay": {
"geo_country_any_of": ["US"],
"signals": ["sports_enthusiasts", "auto_intenders"]
}
}
# Platform Translation (GAM)
{
"geoTargeting": {"targetedLocations": [{"id": "2840"}]},
"customTargeting": {
"logicalOperator": "OR",
"children": [
{"key": "aee_signal", "values": ["sports_enthusiasts"]},
{"key": "aee_signal", "values": ["auto_intenders"]}
]
}
}Used for:
- Product configuration suggestions
- Targeting optimization
- Creative analysis
- Natural language processing
- Input - Product description
- Analysis - Extract key attributes
- Matching - Find similar products
- Configuration - Generate settings
- Validation - Check constraints
- Super Admin - Environment variable whitelist
- OAuth - Google identity verification
- Tenant Admin - Publisher-level access
- Principal - Advertiser API tokens
- Database-backed logging
- Principal context tracking
- Operation success/failure
- Security violation detection
- Compliance reporting
- Tenant-scoped queries
- Principal validation
- Cross-tenant protection
- SQL injection prevention
- Connection pooling
- Index optimization
- JSONB for PostgreSQL
- Query result caching
- In-memory product cache
- Redis for session storage (optional)
- CDN for static assets
- API response caching
- Background task queue
- Webhook notifications
- Batch processing
- Long-running operations
services:
postgres:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
adcp-server:
build: .
depends_on: [postgres]
ports: ["8080:8080"]
admin-ui:
build: .
command: python admin_ui.py
ports: ["8001:8001"]Single-machine architecture with:
- Proxy router (port 8000)
- Internal services (8080, 8001)
- Managed PostgreSQL
- Persistent volume
- Health checks on all services
- Graceful shutdown handling
- Log aggregation
- Metric collection
- Error tracking
- Backup automation
- New MCP Tools - Add to main.py
- Admin UI Pages - Extend Flask routes
- Database Tables - Create migrations
- API Endpoints - Add to appropriate module
- Webhook notifications
- External API calls
- Custom adapters
- Plugin system (future)