build-creative-agent · git:20260409.69a6dde · 2026-04-09 · sha256 951409a048b2d1e3
build-creative-agent git:20260409.69a6ddeA
Immutable. This exact content is served forever at /api/v1/blob/951409a048b2d1e3.
---
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.
---
# Build a Creative Agent
## Overview
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 — it manages creatives but doesn't sell media.
## When to Use
- User wants to build an ad server, creative management platform, or creative rendering service
- User mentions `build_creative`, `preview_creative`, `sync_creatives`, or `list_creatives`
- User references creative formats, VAST tags, serving tags, or creative libraries
**Not this skill:**
- Selling inventory + generating creatives → `skills/build-generative-seller-agent/`
- Selling inventory (no creative management) → `skills/build-seller-agent/`
- Serving audience segments → `skills/build-signals-agent/`
## Before Writing Code
Determine these things. Ask the user — don't guess.
### 1. What kind of creative platform?
- **Ad server** (Innovid, Flashtalking, CM360) — stateful library, builds serving tags (VAST, display tags), tracks delivery
- **Creative management platform** (Celtra) — format transformation, template rendering, asset management
- **Publisher creative service** — accepts buyer assets, validates against publisher specs, renders previews
### 2. What formats?
Get specific formats the platform supports. Common ones:
- **Display**: `display_300x250`, `display_728x90`, `display_160x600`
- **Video**: `video_30s`, `vast_30s`, `video_15s`
- **Native**: `native_content` (image + headline + description)
- **Rich media**: `html5_300x250` (interactive HTML)
Each format needs: dimensions, accepted asset types (image, video, html, text), mime types.
### 3. What operations?
- **Sync** — accept and store creatives from buyers (always needed)
- **List** — query the creative library with filtering (recommended)
- **Preview** — render a visual preview of a creative (recommended)
- **Build** — produce serving tags (VAST, display tags, etc.) from stored creatives (recommended)
### 4. Review pipeline?
What happens when a creative is synced:
- **Instant accept** — creative passes validation, immediately available
- **Pending review** — human or automated review before going live
- **Rejection** — creative fails validation (wrong dimensions, prohibited content)
## Tools and Required Response Shapes
**`get_adcp_capabilities`** — register first, empty `{}` schema
```
capabilitiesResponse({
adcp: { major_versions: [3] },
supported_protocols: ['creative'],
})
```
**`list_creative_formats`** — `ListCreativeFormatsRequestSchema.shape`
```
taskToolResponse({
formats: [{
format_id: { agent_url: string, id: string }, // required
name: string, // required
description: string,
renders: [{ width: number, height: number }], // output dimensions
assets: [{ // what the format accepts
item_type: 'individual',
asset_id: string,
asset_type: 'image' | 'video' | 'html' | 'text',
required: boolean,
accepted_media_types: string[], // e.g., ['image/png', 'image/jpeg']
}],
}],
})
```
**`sync_creatives`** — `SyncCreativesRequestSchema.shape`
Store creatives in the library. Echo back creative_id and action.
```
taskToolResponse({
creatives: [{
creative_id: string, // required — echo from request
action: 'created' | 'updated', // required
status: 'accepted' | 'pending_review' | 'rejected',
}],
})
```
**`list_creatives`** — `ListCreativesRequestSchema.shape`
Return creatives from the library. Support filtering by format_id.
```
taskToolResponse({
creatives: [{
creative_id: string,
name: string,
format_id: { agent_url: string, id: string },
status: 'accepted' | 'pending_review' | 'rejected',
}],
})
```
The handler should check `args.filters?.format_ids` — if present, return only creatives matching those formats.
**`preview_creative`** — `PreviewCreativeRequestSchema.shape`
Render a preview of a stored creative. Each preview has a `renders` array with output_format discriminator.
```
taskToolResponse({
response_type: 'single',
previews: [{
preview_id: string,
input: { format_id: { agent_url: string, id: string }, name: string, assets: {} },
renders: [{
render_id: string,
output_format: 'url', // discriminator: 'url' or 'html'
preview_url: string, // URL to rendered preview (for output_format: 'url')
role: 'primary',
dimensions: { width: number, height: number },
}],
}],
expires_at: string, // ISO timestamp
})
```
**`build_creative`** — `BuildCreativeRequestSchema.shape`
Produce a serving tag from a stored creative.
```
taskToolResponse({
creative_manifest: {
format_id: { agent_url: string, id: string },
name: string,
assets: {}, // built output assets
},
sandbox: true,
})
```
## SDK Quick Reference
| SDK piece | Usage |
|-----------|-------|
| `serve(createAgent)` | Start HTTP server on `:3001/mcp` |
| `createTaskCapableServer(name, version, { taskStore })` | Create MCP server with task support |
| `server.tool(name, Schema.shape, handler)` | Register tool — `.shape` unwraps Zod |
| `capabilitiesResponse(data)` | Build `get_adcp_capabilities` response |
| `taskToolResponse(data, summary)` | Build generic tool response |
| `adcpError(code, { message })` | Structured error |
Schemas: `ListCreativeFormatsRequestSchema`, `SyncCreativesRequestSchema`, `ListCreativesRequestSchema`, `PreviewCreativeRequestSchema`, `BuildCreativeRequestSchema`.
Import everything from `@adcp/client`. Types from `@adcp/client` with `import type`.
## Implementation
1. Single `.ts` file — all tools in one file
2. Always register `get_adcp_capabilities` as the **first** tool with empty `{}` schema
3. Use `Schema.shape` (not `Schema`) when registering tools
4. Use an in-memory Map to store synced creatives (the creative library)
5. Set `sandbox: true` on all mock/demo responses
6. Use `ServeContext` pattern: `function createAgent({ taskStore }: ServeContext)`
The skill contains everything you need. Do not read additional docs before writing code.
### Key implementation detail: creative library
Use a `Map<string, Creative>` to store synced creatives. The `sync_creatives` handler adds/updates entries. The `list_creatives` handler queries the map. The `preview_creative` and `build_creative` handlers look up by `creative_id`.
## Validation
**After writing the agent, validate it. Fix failures. Repeat.**
**Full validation** (if you can bind ports):
```bash
npx tsx agent.ts &
npx @adcp/client storyboard run http://localhost:3001/mcp creative_lifecycle --json
```
**Sandbox validation** (if ports are blocked):
```bash
npx tsc --noEmit agent.ts
```
**Keep iterating until all steps pass.**
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Skip `get_adcp_capabilities` | Must be the first tool registered |
| Pass `Schema` instead of `Schema.shape` | MCP SDK needs unwrapped Zod fields |
| `list_creatives` ignores format filter | Check `args.filters?.format_ids` and filter results |
| `preview_creative` returns wrong response_type | Must be `'single'` for single creative previews |
| `build_creative` missing creative_manifest | Required field — contains the built output |
| No in-memory store for synced creatives | `list_creatives` and `preview_creative` need to find previously synced creatives |
## Storyboards
| Storyboard | Tests |
|-----------|-------|
| `creative_lifecycle` | Full lifecycle: format discovery → sync → list → preview → build |
| `creative_template` | Stateless template rendering (build + preview only) |
| `creative_sales_agent` | Sales agent that accepts pushed assets |
| `creative_ad_server` | Ad server with pre-loaded library |
## Reference
- `storyboards/creative_lifecycle.yaml` — full creative lifecycle storyboard
- `docs/guides/BUILD-AN-AGENT.md` — SDK patterns
- `docs/llms.txt` — full protocol reference