mit einem Klick
build-creative-agent
// Use when building an AdCP creative agent — an ad server, creative management platform, or any system that accepts, stores, transforms, and serves ad creatives.
// Use when building an AdCP creative agent — an ad server, creative management platform, or any system that accepts, stores, transforms, and serves ad creatives.
| name | build-creative-agent |
| description | Use when building an AdCP creative agent — an ad server, creative management platform, or any system that accepts, stores, transforms, and serves ad creatives. |
A creative agent manages the creative lifecycle: accepts assets from buyers, stores them in a library, builds serving tags, and renders previews. Unlike a generative seller (which also sells inventory), a creative agent is a standalone creative platform.
build_creative, preview_creative, sync_creatives, or list_creativesNot this skill:
skills/build-generative-seller-agent/skills/build-seller-agent/skills/build-signals-agent/Determine these things. Ask the user — don't guess.
Get specific formats. Each format needs: dimensions, accepted asset types, mime types.
display_300x250, display_728x90video_30s, vast_30snative_content (image + headline + description)One file. Subclass ADCPHandler, override the tools you support, call serve(). Use an in-memory dict to store synced creatives.
from adcp.server import ADCPHandler, serve
from adcp.server.helpers import adcp_error
from adcp.server.responses import (
capabilities_response, creative_formats_response, sync_creatives_response,
list_creatives_response, preview_creative_response, build_creative_response,
)
creatives: dict[str, dict] = {} # in-memory creative library
class MyCreativeAgent(ADCPHandler):
async def get_adcp_capabilities(self, params, context=None):
return capabilities_response(["creative"])
# ... implement tools
serve(MyCreativeAgent(), name="my-creative-agent")
Every tool uses a response builder from adcp.server.responses.
get_adcp_capabilities
from adcp.server.responses import capabilities_response
async def get_adcp_capabilities(self, params, context=None):
return capabilities_response(["creative"])
list_creative_formats
import os
from adcp.server.responses import creative_formats_response
ADCP_PORT = int(os.environ.get("ADCP_PORT", 3001))
AGENT_URL = f"http://localhost:{ADCP_PORT}/mcp"
async def list_creative_formats(self, params, context=None):
return creative_formats_response([
{
"format_id": {"agent_url": AGENT_URL, "id": "display_300x250"},
"name": "Display 300x250",
"description": "Standard IAB medium rectangle",
"renders": [{"width": 300, "height": 250}],
"assets": [{
"item_type": "individual",
"asset_id": "image",
"asset_type": "image",
"required": True,
"accepted_media_types": ["image/png", "image/jpeg"],
}],
},
{
"format_id": {"agent_url": AGENT_URL, "id": "video_30s"},
"name": "Video 30s Pre-Roll",
"renders": [{"width": 1920, "height": 1080}],
"assets": [{
"item_type": "individual",
"asset_id": "video",
"asset_type": "video",
"required": True,
"accepted_media_types": ["video/mp4"],
}],
},
])
sync_creatives — store creatives in the library. Status must be a valid CreativeStatus: processing, pending_review, approved, rejected, archived.
from adcp.server.responses import sync_creatives_response
async def sync_creatives(self, params, context=None):
results = []
for c in params.get("creatives", []):
creative_id = c.get("creative_id", f"c-{uuid.uuid4().hex[:8]}")
creatives[creative_id] = {**c, "creative_id": creative_id, "status": "approved"}
results.append({
"creative_id": creative_id,
"action": "created",
"status": "approved",
})
return sync_creatives_response(results)
list_creatives — query the library. Must include pagination and query_summary fields. Status must be a valid CreativeStatus.
from datetime import datetime, timezone
from adcp.server.responses import list_creatives_response
async def list_creatives(self, params, context=None):
results = list(creatives.values())
# Filter by format_ids if provided
filters = params.get("filters") or {}
if format_ids := filters.get("format_ids"):
format_id_set = {f.get("id", "") if isinstance(f, dict) else str(f) for f in format_ids}
results = [c for c in results if c.get("format_id", {}).get("id") in format_id_set]
now = datetime.now(timezone.utc).isoformat()
serialized = [
{
"creative_id": c["creative_id"],
"name": c.get("name", ""),
"format_id": c.get("format_id"),
"status": c.get("status", "approved"),
"created_date": now,
"updated_date": now,
}
for c in results
]
return list_creatives_response(serialized)
preview_creative — render a preview of a stored creative
from adcp.server.responses import preview_creative_response
async def preview_creative(self, params, context=None):
creative_id = params.get("creative_id")
creative = creatives.get(creative_id) if creative_id else None
format_id = params.get("format_id") or (creative or {}).get("format_id", {})
return preview_creative_response([{
"preview_id": f"prev-{uuid.uuid4().hex[:8]}",
"input": {
"format_id": format_id,
"name": (creative or {}).get("name", "Preview"),
"assets": (creative or {}).get("assets", {}),
},
"renders": [{
"render_id": f"render-{uuid.uuid4().hex[:8]}",
"output_format": "url",
"preview_url": f"https://example.com/preview/{creative_id or 'unknown'}.png",
"role": "primary",
"dimensions": {"width": 300, "height": 250},
}],
}])
build_creative — produce serving tags. The storyboard sends target_format_id (not output_format). Look up the creative by ID, by format match, or fall back to the first available.
from adcp.server.responses import build_creative_response
async def build_creative(self, params, context=None):
creative_id = params.get("creative_id")
creative = creatives.get(creative_id) if creative_id else None
# Resolve target format
# target_format_id is canonical per spec; other names are legacy aliases accepted for compatibility.
target_format = params.get("target_format_id") or params.get("output_format") or params.get("format_id")
# Find creative by format if not found by ID
if not creative and target_format:
target_id = target_format.get("id", "") if isinstance(target_format, dict) else str(target_format)
for c in creatives.values():
if c.get("format_id", {}).get("id") == target_id:
creative = c
break
if not creative:
format_label = target_format.get("id", "<none>") if isinstance(target_format, dict) else (target_format or "<none>")
return adcp_error("CREATIVE_NOT_FOUND", f"No creative found for format {format_label}", field="target_format_id")
format_id = target_format or (creative or {}).get("format_id", {})
# Build assets dict from stored creative
stored_assets = (creative or {}).get("assets", [])
built_assets = {}
if isinstance(stored_assets, list):
for asset in stored_assets:
built_assets[asset.get("asset_id", "unknown")] = asset
elif isinstance(stored_assets, dict):
built_assets = stored_assets
return build_creative_response({
"format_id": format_id,
"name": (creative or {}).get("name", "Built Creative"),
"assets": built_assets,
})
| Function | Usage |
|---|---|
serve(handler, transport="a2a"|"streamable-http", port=3001) | Start MCP or A2A server. Context passthrough is automatic. |
capabilities_response(protocols) | get_adcp_capabilities response |
creative_formats_response(formats) | list_creative_formats response |
sync_creatives_response(creatives) | sync_creatives response |
list_creatives_response(creatives) | list_creatives response (adds pagination, query_summary) |
preview_creative_response(previews) | preview_creative response (adds response_type, expires_at) |
build_creative_response(manifest) | build_creative response |
Import handlers from adcp.server. Import response builders from adcp.server.responses.
python agent.py &
npx -y -p @adcp/client adcp storyboard run http://localhost:3001/mcp creative_lifecycle --json
Keep iterating until all steps pass.
| Mistake | Fix |
|---|---|
Skip get_adcp_capabilities | Must be implemented |
| Return raw dicts without builders | Use response builders for every tool |
| Wrong creative status | Must be approved, not accepted. Valid: processing, pending_review, approved, rejected, archived |
list_creatives ignores format filter | Check filters.format_ids and filter results |
list_creatives missing pagination/query_summary | Use list_creatives_response() which adds them automatically |
build_creative can't find creative | Check target_format_id param (not output_format), fall back to first available |
| No in-memory store for synced creatives | list_creatives, preview_creative, build_creative need previously synced creatives |
This skill contains everything needed to build a 6/6 passing creative agent. The code blocks above are taken from a validated implementation.
Use when building an AdCP seller agent — a publisher, SSP, or retail media network that sells advertising inventory to buyer agents.
Use when building an AdCP signals agent, creating an audience data server, or standing up a data provider agent that serves targeting segments to buyers.
Use when building an AdCP generative seller — an AI ad network, generative DSP, or platform that sells inventory AND generates creatives from briefs.
Use when building an AdCP retail media network agent — a platform that sells on-site placements, supports product catalogs, tracks conversions, and reports performance.