# Start all services
docker-compose up -d
# Services:
# - PostgreSQL database (port 5432)
# - MCP Server (port 8080)
# - Admin UI (port 8001)# Create app and database
fly apps create adcp-sales-agent
fly postgres create --name adcp-db --region iad
fly postgres attach adcp-db --app adcp-sales-agent
# Set secrets
fly secrets set GOOGLE_CLIENT_ID="..." GOOGLE_CLIENT_SECRET="..."
fly secrets set GEMINI_API_KEY="..." SUPER_ADMIN_EMAILS="..."
# Deploy
fly deploy# Install dependencies with uv
uv sync
# Run migrations
uv run python migrate.py
# Start servers
uv run python run_server.pyChoose one of three methods to provide your secrets and configuration:
Create .env.secrets in the project root directory:
# Copy template
cp .env.secrets.template .env.secrets
# Edit with your values
# API Keys
GEMINI_API_KEY=your-gemini-api-key
# OAuth Configuration
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
SUPER_ADMIN_EMAILS=admin1@example.com,admin2@example.com
SUPER_ADMIN_DOMAINS=example.com
# GAM OAuth (optional)
GAM_OAUTH_CLIENT_ID=your-gam-client-id.apps.googleusercontent.com
GAM_OAUTH_CLIENT_SECRET=your-gam-client-secretBenefits:
- ✅ Consistent across all workspaces/setups
- ✅ Never committed to git (in
.gitignore) - ✅ Team-friendly with template
Add to your ~/.zshrc or ~/.bashrc:
# AdCP Configuration
export GEMINI_API_KEY="your-gemini-api-key"
export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="your-client-secret"
export SUPER_ADMIN_EMAILS="admin1@example.com,admin2@example.com"
export GAM_OAUTH_CLIENT_ID="your-gam-client-id.apps.googleusercontent.com"
export GAM_OAUTH_CLIENT_SECRET="your-gam-client-secret"
# Reload shell
source ~/.zshrcCreate .env in your working directory:
# API Keys
GEMINI_API_KEY=your-gemini-api-key
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
SUPER_ADMIN_EMAILS=admin1@example.com,admin2@example.com
# Database (Docker handles automatically)
DATABASE_URL=postgresql://user:pass@localhost:5432/adcpThe setup system checks for configuration in this order:
- Current directory:
.env.secrets(workspace-specific) - Project root:
.env.secrets(shared across workspaces) - Environment variables: From shell profile
- Direct .env: In current directory
- Go to Google Cloud Console
- Create new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials (Web application)
- Add redirect URIs:
- Local:
http://localhost:8001/auth/google/callback - Production:
https://yourdomain.com/auth/google/callback
- Local:
- Download credentials or copy Client ID and Secret
DATABASE_URL=postgresql://user:password@host:5432/dbname
DB_TYPE=postgresqlDATABASE_URL=sqlite:///adcp_local.db
DB_TYPE=sqlite# Create publisher/tenant with access control
docker exec -it adcp-server python setup_tenant.py "Publisher Name" \
--adapter google_ad_manager \
--gam-network-code 123456 \
--domain publisher.com \
--admin-email admin@publisher.com
# Create with mock adapter for testing
docker exec -it adcp-server python setup_tenant.py "Test Publisher" \
--adapter mock \
--admin-email test@example.com--domain or --admin-email to configure access control. Without this, nobody can access the tenant.
The Admin UI provides secure web-based management at http://localhost:8001
-
Super Admin - Full system access
- Manage all tenants (publishers)
- View all operations
- System configuration
-
Tenant Admin - Publisher management
- Manage products and advertisers
- View tenant operations
- Configure integrations
-
Tenant User - Read-only access
- View products and campaigns
- Monitor performance
- Publisher Management - Create and configure tenants
- Advertiser Management - Add principals (advertisers)
- Product Catalog - Define inventory products
- Creative Approval - Review and approve creatives
- Operations Dashboard - Monitor all activity
- Audit Logs - Track all operations
Each publisher has JSON configuration:
{
"adapters": {
"google_ad_manager": {
"enabled": true,
"network_code": "123456",
"manual_approval_required": false
}
},
"creative_engine": {
"auto_approve_formats": ["display_300x250"],
"human_review_required": true
},
"features": {
"max_daily_budget": 10000,
"enable_axe_signals": true
}
}Add advertisers to publishers:
# Via Admin UI (recommended)
# 1. Login to http://localhost:8001
# 2. Navigate to tenant
# 3. Add new advertiser/principal
# 4. Configure GAM advertiser ID
# Via API
curl -X POST "http://localhost:8001/admin/tenant/{tenant_id}/principals" \
-H "Content-Type: application/json" \
-d '{
"name": "Advertiser Name",
"platform_mappings": {
"google_ad_manager": {
"advertiser_id": "123456",
"enabled": true
}
}
}'Create products with AI assistance:
# Quick create from templates
curl -X POST "/admin/tenant/{tenant_id}/products/quick-create" \
-d '{"template": "news_display", "name": "News Display Ads"}'
# Get AI suggestions
curl -X POST "/admin/tenant/{tenant_id}/products/ai-suggest" \
-d '{"description": "Video ads for sports content"}'New tenants get 6 standard products:
- Premium Display (guaranteed)
- Standard Display (non-guaranteed)
- Video Pre-Roll (guaranteed)
- Native Content (guaranteed)
- Mobile Display (non-guaranteed)
- Newsletter Sponsorship (guaranteed)
# Upload CSV
curl -X POST "/admin/tenant/{tenant_id}/products/upload" \
-F "file=@products.csv"
# JSON import
curl -X POST "/admin/tenant/{tenant_id}/products/import" \
-H "Content-Type: application/json" \
-d @products.json- Configure auto-approve formats per tenant
- Standard formats approved instantly
- Non-standard sent to review queue
- Admin reviews in UI
- Email notifications on status change
Organize creatives across campaigns:
- Group by advertiser, campaign, or theme
- Share creatives across media buys
- Track performance by group
Migrations run automatically on startup, but can be managed manually:
# Run migrations
uv run python migrate.py
# Check status
uv run python migrate.py status
# Create new migration
uv run alembic revision -m "description"Docker uses BuildKit caching with shared volumes across Conductor workspaces:
adcp_global_pip_cache- Python packagesadcp_global_uv_cache- uv dependencies
This reduces build times from ~3 minutes to ~30 seconds.
# Rebuild after changes
docker-compose build
docker-compose up -d
# View logs
docker-compose logs -f
# Enter container
docker exec -it adcp-server bash
# Backup database
docker exec postgres pg_dump -U adcp_user adcp > backup.sqlFor UI testing without OAuth:
# Enable in docker-compose.override.yml
ADCP_AUTH_TEST_MODE=true
# Test users available:
# - test_super_admin@example.com / test123
# - test_tenant_admin@example.com / test123
# - test_tenant_user@example.com / test123For Conductor users, the setup is automated:
# 1. Create .env.secrets in project root (one-time setup)
cp .env.secrets.template /path/to/project/root/.env.secrets
# Edit with your actual secrets
# 2. Create new Conductor workspace
# Conductor will automatically run setup script
# 3. Start services
docker-compose up -d- Automatic Environment Loading: Setup script finds your
.env.secretsfile - Unique Port Assignment: Each workspace gets unique ports (no conflicts)
- Docker Caching: Shared cache volumes speed up builds across workspaces
- Git Hooks: Pre-commit and pre-push hooks configured per workspace
- Port Management: Automatic port reservation system prevents conflicts
- Shared Caching: Docker volumes shared across all AdCP workspaces
- Workspace Isolation: Each workspace has independent
.envwith unique ports - Development Mode: Hot reloading with
docker-compose.override.yml
When you create a Conductor workspace, it automatically:
-
Checks for secrets in this priority order:
./env.secrets(workspace-specific)$CONDUCTOR_ROOT_PATH/.env.secrets(your main secrets file)- Environment variables (your shell profile)
-
Generates workspace config:
- Unique ports (PostgreSQL, MCP Server, Admin UI)
- Database URL with workspace-specific port
- Docker caching configuration
-
Creates development files:
.envwith secrets + unique portsdocker-compose.override.ymlfor hot reloading- Git hooks for testing and code quality
-
Sets up dependencies:
- Creates Python virtual environment
- Installs all dependencies via
uv - Configures UI test dependencies if needed
Setup script fails with "missing environment variables":
# Create .env.secrets file in project root
cp .env.secrets.template /path/to/project/root/.env.secrets
# Edit with your actual valuesPort conflicts:
# Check which ports are assigned
cat .env | grep PORT
# Recreate workspace if needed (gets new ports)Docker caching issues:
# Clear shared cache volumes
docker volume rm adcp_global_pip_cache adcp_global_uv_cache
# Restart workspace to recreate volumesMissing dependencies:
# Reinstall in workspace
uv sync --extra ui-tests# Set required Conductor variables
export CONDUCTOR_WORKSPACE_NAME="your-workspace"
export CONDUCTOR_WORKSPACE_PATH="/path/to/workspace"
export CONDUCTOR_ROOT_PATH="/path/to/project/root"
# Run setup script manually
bash scripts/setup/setup_conductor_workspace.sh# MCP Server
curl http://localhost:8080/health
# Admin UI
curl http://localhost:8001/health
# Database
docker exec postgres pg_isready