This guide helps AI assistants understand the AdCP project structure and maintain consistency when working on the codebase.
The Advertising Context Protocol (AdCP) is an open standard for AI-powered advertising workflows. It provides a unified interface for media buying across diverse advertising platforms.
When working on documentation, it's crucial to maintain separation between:
-
Protocol Specification (what goes in docs/)
- Abstract interface definitions
- Tool signatures and parameters
- Data models and schemas
- Workflow descriptions
- Platform-agnostic concepts
-
Implementation Details (what doesn't belong in the spec)
- Database choices (PostgreSQL, MongoDB, etc.)
- Deployment methods (Docker, Kubernetes, etc.)
- Infrastructure details (multi-tenant architecture, etc.)
- Specific technology stacks
- Performance optimizations
- Security implementation details
Implementation details can be mentioned as:
- Recommendations in a separate implementation guide
- Examples clearly marked as non-normative
- Reference implementations in the code
- Best practices documentation separate from the spec
- Use "AdCP" not "ADCP"
- Focus on capabilities, not implementation
- Write for an audience implementing the protocol, not using a specific implementation
- Keep examples generic and illustrative
GOLDEN RULE: Documentation and JSON schemas MUST always be synchronized.
CRITICAL PRINCIPLE: Task response schemas should contain ONLY domain-specific data. Protocol-level concerns are handled by the transport layer (MCP, A2A, REST).
Protocol-level fields (DO NOT include in task responses):
message- Human-readable summaries (handled by MCP content field, A2A assistant messages)context_id- Session/conversation tracking (handled by protocol)task_id- Async operation tracking (handled by protocol)status- Task state management (handled by protocol)webhook_url/webhook_secret- Callback configuration (handled by protocol)
Task response fields (DO include):
- Domain-specific results (signals, creatives, products, delivery metrics)
- Operation-specific metadata (dry_run flag, estimated durations)
- Task-specific errors and warnings
- Resource identifiers (decisioning_platform_segment_id, platform_id, etc.)
Example - What a task response should look like:
{
"signals": [...], // ✅ Domain data
"errors": [...] // ✅ Task-specific errors
}NOT like this:
{
"message": "Found 3 signals", // ❌ Protocol concern
"context_id": "ctx_123", // ❌ Protocol concern
"task_id": "task_456", // ❌ Protocol concern
"status": "completed", // ❌ Protocol concern
"signals": [...] // ✅ Domain data
}This separation ensures AdCP tasks work identically across different protocol implementations (MCP, A2A, REST, future protocols).
Protocol Envelope Schema: The standard wrapper structure is documented in /schemas/v1/core/protocol-envelope.json. This schema shows how protocol layers wrap task response payloads with protocol-level fields.
Update JSON schemas whenever you:
- Add, remove, or rename any fields in task requests/responses
- Change field types, constraints, or validation rules
- Modify enum values (like status types, delivery types, etc.)
- Add new data models or modify existing core objects
- Change required vs optional field specifications
When making documentation changes:
- ✅ Identify affected schemas in
static/schemas/v1/ - ✅ Update request schemas (if changing task parameters)
- ✅ Update response schemas (if changing response structure)
- ✅ Update core data models (if changing object definitions)
- ✅ Update enum schemas (if changing allowed values)
- ✅ Verify cross-references (
$reflinks) are still valid - ✅ Test schema validation with example data
- ✅ Update schema descriptions to match documentation
- Task Requests:
static/schemas/v1/media-buy/orstatic/schemas/v1/signals/ - Core Objects:
static/schemas/v1/core/ - Enums:
static/schemas/v1/enums/ - Registry:
static/schemas/v1/index.json
Always validate schemas work correctly:
# Use online JSON schema validators or
# Node.js with ajv library to test schemas
# Schemas are accessible locally at http://localhost:3000/schemas/v1/ when running npm run startWhen running npm run start, all JSON schemas are accessible at:
- Schema registry:
http://localhost:3000/schemas/v1/index.json - Core schemas:
http://localhost:3000/schemas/v1/core/{name}.json - Task schemas:
http://localhost:3000/schemas/v1/media-buy/{task}-{request|response}.json - Signal schemas:
http://localhost:3000/schemas/v1/signals/{task}-{request|response}.json - Enum schemas:
http://localhost:3000/schemas/v1/enums/{name}.json
IMPORTANT: AdCP uses path-based versioning. The schema URL path indicates the version, not individual fields in schemas.
- Version is in the path:
/schemas/v1/vs/schemas/v2/ - No
adcp_versionfield in request or response schemas - Single source of truth:
static/schemas/v1/index.jsoncontains the current version
Rationale:
- Eliminates redundant version fields in every schema
- Reduces maintenance burden (no need to update version in 30+ files)
- Clearer semantics (the schema you reference IS the version you use)
- Follows REST/HTTP conventions (version in path, not payload)
AdCP uses semantic versioning for schemas. Increment the version when:
PATCH (1.0.0 → 1.0.1): Schema fixes that don't change behavior
- Fix typos in descriptions
- Correct validation patterns
- Clarify existing field meanings
- Fix broken
$reflinks
MINOR (1.0.0 → 1.1.0): Backward-compatible additions
- Add new optional fields to requests/responses
- Add new enum values (append-only)
- Add new optional core object properties
- Add new tasks (new request/response pairs)
MAJOR (1.0.0 → 2.0.0): Breaking changes
- Remove or rename existing fields
- Change field types or constraints
- Make optional fields required
- Remove enum values
- Change existing field meanings
When making ANY schema change:
-
✅ Determine Version Impact
- Review changes against patch/minor/major criteria above
- If breaking change, consider if really necessary
- Document the rationale for the change
-
✅ Update Schema Registry
- Update
adcp_versioninstatic/schemas/v1/index.json - Update
lastUpdatedfield in schema registry - DO NOT add
adcp_versionto individual schemas
- Update
-
✅ Update All Related Schemas
- If changing core objects, update all schemas that reference them
- If adding enum values, ensure all using schemas are compatible
- Verify
$reflinks still resolve correctly
-
✅ Test Schema Changes
- Validate all modified schemas with JSON Schema validator
- Test with real request/response examples
- Ensure existing examples still validate
-
✅ Update Documentation
- Update all affected task documentation in
docs/ - Update API examples to reference correct schema path
- If major version, create migration guide in
docs/reference/versioning.md
- Update all affected task documentation in
When adding a new optional field to create-media-buy-request.json:
// In create-media-buy-request.json
{
"$id": "/schemas/v1/media-buy/create-media-buy-request.json",
"properties": {
"new_optional_field": {
"type": "string",
"description": "New feature description"
}
}
}Then update schema registry:
// In static/schemas/v1/index.json
{
"adcp_version": "1.1.0", // ← Update version
"lastUpdated": "2025-10-13" // ← Update date
}For major version changes:
- Create new version directory:
static/schemas/v2/ - Implement breaking changes in v2 schemas
- Update schema registry to include v2 paths
- Create migration documentation with:
- What changed and why
- Step-by-step migration guide
- Code examples for before/after
- Maintain v1 support during transition period
- Deprecation timeline for removing v1 support
- Use TypeScript for all new code
- Follow existing patterns in the codebase
- Implement proper error handling
- Add types for all parameters and return values
- Check for existing test patterns before writing new tests
- Run tests with
npm testbefore committing - Ensure new features have corresponding tests
CRITICAL: Always use consistent naming for format-related fields to avoid developer confusion.
Established Convention:
"formats"= Array of format objects (with full details like name, type, requirements, assets_required, etc.)"format_ids"= Array of format ID strings (references to format objects)"format_types"= Array of high-level type strings (video, display, audio, native, etc.)
Examples:
// ✅ CORRECT - list_creative_formats response (format objects)
{
"formats": [
{
"format_id": "video_standard_30s",
"name": "Standard Video - 30 seconds",
"type": "video",
"requirements": {...}
}
]
}
// ✅ CORRECT - Product response (format ID strings)
{
"product_id": "ctv_premium",
"format_ids": ["video_standard_30s", "video_standard_15s"]
}
// ✅ CORRECT - get_products filter (high-level types)
{
"filters": {
"format_types": ["video", "display"]
}
}When adding new fields:
- Use
format_idswhen referencing existing formats by ID - Use
formatsonly when returning full format objects - Use
format_typesfor broad categorical filtering - Never use
formatsfor arrays of strings - always useformat_ids
Schema Validation: All schemas must follow this convention. Tests will fail if format fields don't match the expected naming pattern.
CRITICAL: Format IDs are ALWAYS structured objects, never strings, to avoid parsing ambiguity and handle namespace collisions.
Structured Format ID (REQUIRED EVERYWHERE):
{
"agent_url": "https://creatives.adcontextprotocol.org",
"id": "display_300x250"
}Where structured format IDs are used (everywhere):
- All request parameters accepting format_id (sync_creatives, build_creative, preview_creative, etc.)
- All response fields containing format_id (list_creatives, get_products, list_creative_formats, etc.)
- Creative asset objects specifying which format they conform to
- Product responses listing supported formats
- Filter parameters in list operations (format_ids plural = array of objects)
- Creative manifests specifying the format
Why structured objects everywhere?
- No parsing ambiguity - components are explicit
- Handles format ID collisions between different creative agents
- Simpler mental model - one pattern, no exceptions
- Future-proof for versioning and extensions
Schema reference:
{
"format_id": {
"$ref": "/schemas/v1/core/format-id.json"
}
}Validation rule: All AdCP agents MUST reject string format_ids in ALL contexts with clear error messages. No exceptions.
Legacy handling: If supporting legacy clients sending strings, you MAY auto-upgrade during a deprecation period (max 6 months), but MUST log warnings and fail on unknown format strings. Recommended approach is strict rejection from day one.
IMPORTANT: Visual formats (display, dooh, native) use structured dimensions within the renders array instead of string-based dimensions.
Schema structure (formats have renders array):
{
"format_id": {...},
"type": "display",
"renders": [
{
"role": "primary",
"dimensions": {
"width": 300,
"height": 250,
"responsive": {
"width": false,
"height": false
},
"unit": "px"
}
}
]
}Why the renders structure:
- Supports single and multi-render formats uniformly (companion ads, adaptive formats)
- Eliminates string parsing ("300x250" → structured object)
- Schema-validated with proper typing
- Supports responsive dimensions (min/max width/height)
- Supports aspect ratio constraints
- Enables physical units for DOOH (inches, cm)
- Proper preview rendering without custom parsing
- Clear semantic roles for each rendered piece (primary, companion, mobile_variant, etc.)
Migration from string dimensions:
- Old:
"dimensions": "300x250"(string in requirements) - New:
"renders": [{"role": "primary", "dimensions": {width: 300, height: 250, responsive: {width: false, height: false}, unit: "px"}}]
IMPORTANT: Preview responses now support multiple rendered pieces per variant (companion ads, multi-placement formats).
Schema structure (preview-creative-response.json):
{
"previews": [{
"preview_id": "variant_1",
"renders": [{
"render_id": "primary_video",
"preview_url": "https://...",
"role": "primary",
"dimensions": {"width": 1920, "height": 1080}
}, {
"render_id": "companion_banner",
"preview_url": "https://...",
"role": "companion",
"dimensions": {"width": 300, "height": 250}
}]
}]
}Why multi-render previews:
- Companion ads (video + display banner)
- Adaptive formats (desktop/mobile/tablet variants)
- Multi-placement formats (multiple sizes from one creative)
- DOOH installations (multiple screens)
Key insight: All renders are from the SAME format (e.g., video_with_companion_300x250). The format specification defines multiple rendered pieces. Each render includes dimensions for iframe sizing.
Terminology: Use "renders" not "outputs" to avoid confusion with output_format_ids in generative creative formats.
IMPORTANT: The following fields have been removed from the format schema and should NOT be used:
- Why removed: Redundant with HTML and JavaScript asset types
- Migration: Filter formats by
asset_types: ["html"]orasset_types: ["javascript"]to find formats that accept third-party tags - Rationale: Third-party tags ARE HTML/JavaScript assets. If a format accepts these asset types, it implicitly supports third-party tags.
- Why removed: Information is redundant with source location
- Migration:
- Standard formats are defined in
/schemas/v1/standard-formats/directory - Custom formats have an
agent_urlpointing to a non-standard creative agent - Format location/source already indicates whether it's standard or custom
- Standard formats are defined in
- Rationale: These fields carried information already expressed by the format's authoritative source. Adding explicit fields was redundant and increased maintenance burden.
- Check
git statusto understand current state - Read relevant existing documentation
- Search for similar patterns in the codebase
- Consider impact on other parts of the system
- Keep protocol spec abstract and implementation-agnostic
- Update all related documentation when making changes
- Ensure examples are consistent across docs
- Remove version numbers while in v1 development
- CRITICAL: JSON Schema Synchronization
- When changing any task parameters, data models, or field definitions in docs, ALWAYS update the corresponding JSON schemas in
static/schemas/v1/ - When updating JSON schemas, ALWAYS verify the documentation matches the schema definitions
- Check both request/response schemas AND core data model schemas for affected changes
- Update the schema registry (
static/schemas/v1/index.json) if adding/removing schemas
- When changing any task parameters, data models, or field definitions in docs, ALWAYS update the corresponding JSON schemas in
- Follow existing patterns
- Update tests
- Run linting:
npm run lint - Run type checking:
npm run typecheck - Run tests:
npm test
- MCP-Based: Built on Model Context Protocol
- Asynchronous: Operations may take time
- Human-in-the-Loop: Optional manual approval
- Platform Agnostic: Works across ad platforms
- AI-Optimized: Designed for AI agents
- ✅
get_products- discovers inventory - ❌
discover_products- doesn't exist - ❌
get_avails- removed in favor of direct purchase - ✅
create_media_buy- creates campaigns - ✅
list_creative_formats- shows supported formats
- Products don't include
targeting_templateorimplementation_configin responses - Focus on what's visible to API consumers, not internal implementation
# Development
npm run dev # Start development server
npm run build # Build the project
npm test # Run tests
npm run lint # Check code style
npm run typecheck # Check TypeScript types
# Documentation
npm run docs:dev # Start docs dev server
npm run docs:build # Build documentation
# Git
git status # Check current changes
git add -A # Stage all changes
git commit -m "..." # Commit with message
git push # Push to remote- Check existing code for patterns
- Keep the specification abstract
- Focus on protocol capabilities, not implementation
- Ask for clarification on design decisions
- Refer to the API Reference for tool signatures
Through the standard formats implementation, we've learned key principles for schema design:
-
Remove Platform-Specific Complexity
- Formats should be platform-agnostic
- No
platformorplacement_typefields in format definitions - Publishers adapt formats through placement, not specification changes
-
Simplify Selection Logic
- Removed complex
format-selection.jsonschema - No placement types or format preferences in products
- Buyers directly specify formats they want to provide
- Removed complex
-
Clear Asset Identification
- Added
asset_rolefield to identify asset purposes (e.g., 'hero_image', 'logo') - Assets are self-describing with clear roles
- Enables better creative assembly and validation
- Added
-
Better Field Naming
accepts_3p_tagsinstead ofis_3p_served(indicates optionality)format_idsinstead of ambiguous or verbose alternatives (clear and consistent)- Field names should indicate purpose, not state
-
Schema Registry Tests
- Not all schemas need to be in the registry
- Registry only needs to reference core and enum schemas
- Standard format schemas are discovered through directory structure
- Test should validate registry references exist, not that all schemas are registered
-
Schema Validation Patterns
- Include
index.jsonfiles in schema discovery - Validate examples match schema structure
- Ensure all
$reflinks resolve correctly - Test both request and response schemas
- Include
When addressing code review feedback:
-
Use Todo Lists
- Create a todo for each review comment
- Track progress systematically
- Mark items complete as you address them
-
Batch Related Changes
- Group similar schema updates together
- Use MultiEdit for multiple changes to same file
- Test after each batch of changes
-
Documentation Sync
- Update documentation when changing schemas
- Keep examples consistent with schema changes
- Update both spec docs and CLAUDE.md as needed
The simplified standard formats structure:
static/schemas/v1/standard-formats/
├── index.json # Registry of all standard formats
├── asset-types/ # Reusable asset type definitions
│ ├── image.json
│ ├── video.json
│ └── text.json
├── display/ # Display format definitions
│ ├── display_300x250.json
│ └── mobile_interstitial_320x480.json
├── video/ # Video format definitions
│ ├── video_skippable_15s.json
│ └── video_story_vertical.json
└── native/ # Native format definitions
└── native_responsive.json
Key principles:
- Each format is self-contained with all requirements
- No cross-references to placement or selection schemas
- Assets are defined inline with clear specifications
- Format categories match industry standards (display, video, native, etc.)
Based on extensive reorganization of the AdCP documentation, these principles have emerged as critical for effective technical documentation:
- Structure docs around user workflows, not internal system architecture
- Group related concepts together even if they map to different technical components
- Prioritize what users need to accomplish over how the system is built internally
- One source of truth for each concept - never duplicate content across multiple files
- Use cross-references rather than copying content
- When consolidating, choose the most logical location based on user workflow
- Use "Overview" for index pages rather than repeating the section name
- Examples:
capability-discovery/index.mdtitled "Overview", not "Capability Discovery" - Avoid navigation hierarchies where the same name appears twice
- Never assume a specific protocol (MCP, A2A, REST) in core documentation
- Focus on tasks and capabilities, not transport mechanisms
- Reference protocol-specific details in dedicated protocol sections
- Organize by user journey stages: Discovery → Planning → Execution → Optimization
- Group related activities together (e.g., Policy Compliance belongs with Media Buys, not as standalone)
- Separate conceptual understanding from technical implementation
Main Section (e.g., "Media Buy")
├── Overview (introduces the domain)
├── Foundation Concepts (e.g., Capability Discovery)
├── Planning Activities (e.g., Product Discovery)
├── Execution Workflows (e.g., Media Buys, Creatives)
└── Advanced Topics (technical deep-dives)
When to Consolidate:
- Content serves the same user workflow
- Information is closely related conceptually
- Separate documents create cognitive overhead
- Users would naturally expect to find information together
When to Keep Separate:
- Content serves different audiences (conceptual vs. technical)
- Documents are reference material (e.g., API docs)
- Content has different lifecycles (e.g., stable concepts vs. evolving examples)
-
Role-Based Getting Started
- Provide multiple entry points based on user role
- "For AI Agent Developers", "For Campaign Managers", "For Publishers"
- Each path references the same underlying documentation but suggests different reading order
-
Clear Section Purposes
- Capability Discovery: Foundation concepts and requirements
- Product Discovery: Natural language inventory search
- Media Buys: Campaign lifecycle and execution
- Creatives: Asset management workflows
- Advanced Topics: Technical implementation details
-
Avoid Deep Nesting
- Maximum 3 levels of navigation hierarchy
- Use in-page sections rather than additional file levels
- Prefer longer comprehensive pages over many small pages
❌ BAD: Organized by tech stack
├── MCP Integration
├── A2A Integration
├── Database Schemas
└── API Endpoints
✅ GOOD: Organized by user workflow
├── Discovery & Planning
├── Campaign Execution
├── Performance Optimization
└── Technical Implementation
❌ BAD: Navigation shows duplicate names
Product Discovery
├── Product Discovery (redundant!)
├── Brief Expectations
└── Example Briefs
✅ GOOD: Clear hierarchy
Product Discovery
├── Overview
├── Brief Expectations
└── Example Briefs
❌ BAD: MCP-centric language
"Use MCP tool calls to discover products..."
✅ GOOD: Protocol-agnostic language
"Use the get_products task to discover products..."
❌ BAD: Related concepts separated
├── Media Buys (lifecycle)
├── Policy Compliance (standalone)
└── Optimization (separate section)
✅ GOOD: Workflow-grouped content
└── Media Buys
├── Lifecycle
├── Policy Compliance
└── Optimization & Reporting
- Documentation should emphasize natural language approach over technical targeting APIs
- Focus on publisher expertise and inclusive pricing concepts
- Technical overlays are exception cases, not the primary workflow
- The three-role model (Publisher, Principal, Orchestrator) is central to AdCP
- This model should be explained in Media Buy context, not as general protocol theory
- Each role has different documentation needs and entry points
- Emphasize "timely but not real-time" nature throughout documentation
- Response time expectations should be clearly communicated early
- Human-in-the-loop workflows are normal, not edge cases
- AdCP tasks are protocol-agnostic - the same 8 tasks work across MCP, A2A, and future protocols
- Protocol choice is an integration decision, not a capability limitation
- Documentation should guide protocol selection based on use case, not assume one
-
Regular User Journey Reviews
- Periodically trace through documentation as different user types
- Identify friction points and missing connections
- Consolidate or separate content based on actual usage patterns
-
Cross-Reference Validation
- Ensure all internal links remain valid after reorganization
- Use relative paths that survive structural changes
- Regularly test build process to catch broken references
-
Content Lifecycle Management
- Mark outdated concepts for removal rather than updating indefinitely
- Archive rather than delete content that may become relevant again
- Keep CLAUDE.md updated with structural decisions and rationale
These principles emerged from reorganizing 50+ documentation files and should guide future structural decisions. The goal is always user success - helping people accomplish their goals with minimal cognitive overhead.