A production-grade MCP server providing AdCP creative format specifications and preview generation capabilities. This agent serves as the authoritative source for standard IAB creative formats with built-in preview generation using Tigris storage.
The AdCP Creative Agent is a stateless service that:
- Provides complete specifications for standard IAB creative formats (video, display, audio, native, DOOH)
- Generates live previews from creative manifests using AI (Gemini)
- Stores preview HTML in Tigris (Fly.io's S3-compatible global storage)
- Supports both local development (stdio) and production deployment (HTTP)
┌──────────────────────────────────┐ ┌──────────────────────────────────┐
│ MCP Clients │ │ ADCP HTTP Clients │
│ (Claude, Agents) │ │ (TypeScript, Python) │
└────────┬─────────────────────────┘ └────────┬─────────────────────────┘
│ │
│ MCP Protocol │ HTTP JSON
│ (stdio or HTTP at /mcp) │ (at /adcp/*)
│ │
┌────────▼───────────────────────────────────────▼────────────────────────┐
│ AdCP Creative Agent (Combined Server) │
│ │
│ /mcp - MCP Protocol Endpoint /adcp - ADCP HTTP Endpoints │
│ (JSON-RPC wrapped responses) (Direct ADCP JSON responses) │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ list_creative_formats - Returns all standard format specs │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ build_creative - AI-powered creative generation (Gemini) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ preview_creative - Renders creative to HTML, uploads to Tigris │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────┬───────────────────────────────────────┘
│
│ S3 API
│
┌──────────────────────────────────▼───────────────────────────────────────┐
│ Tigris Global Object Storage │
│ - Public bucket: adcp-previews │
│ - Preview HTML files cached for 1 hour │
│ - Public URLs: https://[bucket].fly.storage.tigris.dev/... │
└──────────────────────────────────────────────────────────────────────────┘
The server provides two protocols for accessing the same tools:
For MCP clients (like Claude Desktop, MCP client libraries):
- Follows MCP JSON-RPC protocol specification
- Responses wrapped in
{"result": {"content": [{"type": "text", "text": "..."}]}} - Use with MCP client libraries or Claude Desktop integration
For direct HTTP clients that expect unwrapped ADCP responses:
GET /adcp/list-creative-formats- List all available formatsPOST /adcp/list-creative-formats- List formats with filters (JSON body)POST /adcp/preview-creative- Generate creative previewPOST /adcp/build-creative- AI-powered creative generation
Returns clean ADCP JSON responses without MCP protocol wrapping.
- Python 3.12+
- uv package manager
- Fly.io account (for production deployment)
# Install dependencies
uv sync
# Run server locally (stdio mode for MCP)
uv run adcp-creative-agent
# Run smoke tests
uv run pytest tests/smoke/ -v -m smokeFor interactive testing and development:
# Start FastMCP development server
uv run fastmcp dev src/creative_agent/server.py
# Opens web UI at http://localhost:8000
# - Test list_creative_formats
# - Test preview_creative with sample manifests
# - See real-time request/response JSONAdd to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"adcp-creative-agent": {
"command": "uv",
"args": ["run", "adcp-creative-agent"],
"cwd": "/absolute/path/to/creative-agent"
}
}
}Restart Claude Desktop to load the server.
# Tigris S3 Storage (automatically set by Fly.io)
AWS_ACCESS_KEY_ID=tid_xxxxx
AWS_SECRET_ACCESS_KEY=tsec_xxxxx
AWS_ENDPOINT_URL_S3=https://fly.storage.tigris.dev
BUCKET_NAME=adcp-previews
AWS_REGION=auto
# Production flag
PRODUCTION=true
PORT=8080# Tigris credentials (from fly storage create)
fly secrets set AWS_ACCESS_KEY_ID=tid_xxxxx
fly secrets set AWS_SECRET_ACCESS_KEY=tsec_xxxxx
fly secrets set AWS_ENDPOINT_URL_S3=https://fly.storage.tigris.dev
fly secrets set BUCKET_NAME=adcp-previewsReturns all available AdCP creative format specifications.
Response: JSON array of AdCP CreativeFormat objects.
See the AdCP Creative Formats specification for complete schema and field definitions.
Generate a creative using AI (Gemini) based on brand assets and a user message.
Input:
message(string, required) - User's advertising message/goalformat_id(string, required) - Target format ID fromlist_creative_formatsgemini_api_key(string, required) - User's Gemini API key (we don't store or pay for API calls)brand_card(object, optional) - Brand information including colors, assets, guidelines
Output:
status- "success" or "error"creative_output- Generated creative manifest ready for previewcontext_id- Unique identifier for this generation session
Example Request:
{
"message": "I want to advertise to tired dads with cats",
"format_id": "display_300x250_image",
"gemini_api_key": "AIzaSy...",
"brand_card": {
"name": "Kraft Mac & Cheese",
"colors": ["#FFD700", "#FF6600"],
"assets": [
{"type": "logo", "url": "https://example.com/logo.png"}
]
}
}Generate HTML previews from a creative manifest and upload to Tigris.
Input:
format_id(string, required) - Format identifiercreative_manifest(object, required) - Creative manifest with assetsinputs(array, optional) - Preview variations with macro substitutions
Output:
preview_id- Unique preview session IDpreviews- Array of preview objects with URLsexpires_at- ISO timestamp when previews expire (24 hours)
Example Request:
{
"format_id": "display_300x250_image",
"creative_manifest": {
"format_id": "display_300x250_image",
"assets": {
"image": {
"asset_type": "image",
"url": "https://example.com/banner.jpg"
},
"click_url": {
"asset_type": "url",
"url": "https://example.com/landing"
}
}
},
"inputs": [
{"name": "Desktop", "macros": {"DEVICE_TYPE": "desktop"}},
{"name": "Mobile", "macros": {"DEVICE_TYPE": "mobile"}}
]
}Example Response:
{
"preview_id": "550e8400-e29b-41d4-a716-446655440000",
"previews": [
{
"input": {"name": "Desktop", "macros": {"DEVICE_TYPE": "desktop"}},
"preview_url": "https://adcp-previews.fly.storage.tigris.dev/previews/550e8400.../Desktop.html",
"hints": {
"estimated_dimensions": {"width": 300, "height": 250},
"primary_media_type": "image",
"contains_audio": false,
"contains_video": false
}
}
],
"expires_at": "2025-10-12T13:00:00Z"
}Use the list_creative_formats MCP tool to see all available formats with complete specifications.
The agent supports 38 AdCP-compliant formats across video, display, audio, native, DOOH, and generative categories. All formats include standard IAB macro support for privacy compliance (GDPR, CCPA, GPP), device info, and tracking.
Creative formats are defined in /src/creative_agent/data/standard_formats.py. To add a new format:
Add a new CreativeFormat object to the appropriate format list (VIDEO_FORMATS, DISPLAY_FORMATS, etc.):
CreativeFormat(
format_id="display_300x600_image", # Unique ID (use descriptive naming)
agent_url=AGENT_URL, # Keep this as AGENT_URL
name="Half Page - Image", # Human-readable name
type="display", # video|display|audio|native|dooh|interactive
category="standard", # standard|custom
is_standard=True, # True for IAB standard formats
description="300x600 half-page display ad with static image",
dimensions="300x600", # Width x Height (if applicable)
accepts_3p_tags=False, # True if format accepts third-party tags
supported_macros=COMMON_MACROS, # Macro support (privacy, device, tracking)
requirements=FormatRequirements( # Optional technical requirements
max_file_size_mb=1.0,
acceptable_formats=["jpg", "png", "gif"],
),
assets_required=[ # List of required/optional assets
AssetRequirement(
asset_role="image", # Unique identifier for this asset
asset_type="image", # image|video|audio|text|html|url|vast_tag
required=True, # Is this asset required?
width=300, # Asset width (optional)
height=600, # Asset height (optional)
max_file_size_mb=1.0, # Max file size (optional)
acceptable_formats=["jpg", "png", "gif"], # Acceptable formats
description="Main display image for 300x600 half-page ad",
),
AssetRequirement(
asset_role="click_url",
asset_type="url",
required=True,
description="Landing page URL for click-through",
),
],
)Ensure your format is added to the appropriate list:
# At the bottom of standard_formats.py
STANDARD_FORMATS = (
VIDEO_FORMATS
+ DISPLAY_FORMATS # Your format should be in one of these
+ NATIVE_FORMATS
+ AUDIO_FORMATS
+ DOOH_FORMATS
)# Test that the format appears in list_creative_formats
uv run fastmcp dev src/creative_agent/server.py
# In the web UI, call list_creative_formats and search for your format_idimage- Static image (JPG, PNG, GIF, WebP)video- Video file (MP4, MOV, WebM)audio- Audio file (MP3, AAC, WAV)text- Text content (headline, body, CTA)html- HTML5 creative codeurl- URL string (click-through, landing page)vast_tag- VAST XML tag URL
Standard macros (included in COMMON_MACROS):
- Privacy:
GDPR,GDPR_CONSENT,US_PRIVACY,GPP_STRING - Tracking:
MEDIA_BUY_ID,CREATIVE_ID,IMPRESSION_URL,CLICK_URL - Device:
DEVICE_TYPE,OS,OS_VERSION,USER_AGENT - Context:
CACHEBUSTER
Add custom macros for specific format needs:
supported_macros=COMMON_MACROS + ["VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"]For standard IAB formats, include specification URLs:
- Video:
https://iabtechlab.com/standards/vast/ - Native:
https://iabtechlab.com/standards/openrtb-native/ - Display:
https://iabtechlab.com/standards/iab-new-ad-portfolio-guidelines/
# 1. Create Fly.io app
fly launch
# 2. Create public Tigris bucket
fly storage create --name adcp-previews --org personal --public -y
# 3. Set environment variables
fly secrets set AWS_ACCESS_KEY_ID=tid_xxxxx
fly secrets set AWS_SECRET_ACCESS_KEY=tsec_xxxxx
fly secrets set AWS_ENDPOINT_URL_S3=https://fly.storage.tigris.dev
fly secrets set BUCKET_NAME=adcp-previews
fly secrets set AWS_REGION=auto# Deploy to production
fly deploy
# Check logs
fly logs
# Check app status
fly statusThe server includes automatic health checks configured in fly.toml:
- TCP health checks every 30s
- 60s grace period for startup
- Auto-start/stop for cost optimization
.
├── src/creative_agent/
│ ├── server.py # Main MCP server (basic tools)
│ ├── server_v2.py # Extended server with AI generation
│ ├── storage.py # Tigris storage integration
│ ├── data/
│ │ ├── formats.py # Format utilities
│ │ └── standard_formats.py # All format definitions
│ └── schemas/
│ ├── format.py # Format data models
│ ├── manifest.py # Creative manifest models
│ ├── assets.py # Asset models
│ ├── preview.py # Preview request/response models
│ └── build.py # AI generation models
├── tests/
│ └── smoke/
│ └── test_server_startup.py
├── pyproject.toml # Project dependencies
├── fly.toml # Fly.io deployment config
└── Dockerfile.fly # Production container image
# Run linter
uv run ruff check src/
# Run type checker
uv run mypy src/
# Run tests with coverage
uv run pytest --cov=src/creative_agent tests/# Install pre-commit hooks
uv run pre-commit install
# Run hooks manually
uv run pre-commit run --all-filesNEVER commit these to version control:
- AWS/Tigris credentials
- Gemini API keys
- Any user-provided API keys
Use environment variables and .env files (gitignored):
# .env (local development)
AWS_ACCESS_KEY_ID=tid_xxxxx
AWS_SECRET_ACCESS_KEY=tsec_xxxxx
AWS_ENDPOINT_URL_S3=https://fly.storage.tigris.dev
BUCKET_NAME=adcp-previews- All secrets set via
fly secrets set - Tigris bucket created with
--publicflag - Preview URLs tested and accessible
- Health checks passing
- Logs monitoring configured
- Error tracking enabled (Sentry/similar)
Problem: Presigned URLs returning access denied errors.
Solution: The bucket was created with --public flag, so we use direct virtual-hosted-style URLs instead of presigned URLs:
https://[bucket-name].fly.storage.tigris.dev/[object-key]
This is already implemented in storage.py (v2).
Check logs:
fly logsCommon issues:
- Missing environment variables
- Port mismatch (should be 8080)
- Dockerfile build errors
Ensure dependencies are installed:
uv syncFor Tigris-dependent tests, set environment variables in .env.
- Create feature branch from
main - Add tests for new functionality
- Run linter and tests
- Create pull request with description
- Ensure CI passes
Apache 2.0 License - see LICENSE file
Format specifications from AdCP Media Buy Protocol