with one click
build-retail-media-agent
// Use when building an AdCP retail media network agent in Go — sells on-site placements, supports product catalogs, tracks conversions.
// Use when building an AdCP retail media network agent in Go — sells on-site placements, supports product catalogs, tracks conversions.
Use when building an AdCP generative seller in Go — an AI ad network or platform that sells inventory AND generates creatives from briefs.
Use when building an AdCP seller agent in Go — a publisher, SSP, or retail media network that sells advertising inventory to buyer agents.
Use when building an AdCP creative agent in Go — an ad server, creative management platform, or rendering service.
Use when building an AdCP signals agent in Go — a CDP, data provider, or audience data server that serves targeting segments to buyers.
Use when building an AdCP collection agent in Go — a governance provider, data company, or agency that manages curated lists of content collections (TV shows, podcasts, publications) for targeting and brand safety.
Use when a Go agent needs to send async notifications (task status changes, list changes, artifact batches, revocations) as AdCP-compliant signed webhooks. Covers RFC 9421 webhook-signing, idempotency_key generation, retry, and push_notification_config decoding via the adcp/webhook package.
| name | build-retail-media-agent |
| description | Use when building an AdCP retail media network agent in Go — sells on-site placements, supports product catalogs, tracks conversions. |
Status: Validated against storyboard runner. If validation fails, check the common mistakes table first, then file an issue.
A retail media agent sells advertising on a retailer's properties. It extends the standard seller with catalog sync, event tracking, and performance feedback.
push_notification_config — see skills/build-webhook-publisher/ for the emission pattern.Use adcp.AddTool for all 13 tools.
get_adcp_capabilities — MUST include conversion_trackingadcp.AddTool(server, "get_adcp_capabilities", "Agent capabilities",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.EmptyInput) (*mcp.CallToolResult, any, error) {
return adcp.CapabilitiesResponse(&adcp.CapabilitiesData{
ADCP: &adcp.ADCPVersion{MajorVersions: []int{3}},
SupportedProtocols: []string{"media_buy", "conversion_tracking", "compliance_testing"},
})
})
sync_accountsadcp.AddTool(server, "sync_accounts", "Register accounts",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.SyncAccountsRequest) (*mcp.CallToolResult, any, error) {
var results []adcp.AccountResult
for i, acct := range input.Accounts {
id := fmt.Sprintf("acct-%s-%d", acct.Brand.Domain, i+1)
results = append(results, adcp.AccountResult{
AccountID: id, Brand: acct.Brand, Operator: acct.Operator,
Action: "created", Status: "active",
})
}
return adcp.SyncAccountsResponse(results, true)
})
sync_governanceadcp.AddTool(server, "sync_governance", "Register governance agents",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.SyncGovernanceRequest) (*mcp.CallToolResult, any, error) {
var results []adcp.GovernanceResult
for _, acct := range input.Accounts {
govAcct := acct.Account
if govAcct == nil {
govAcct = &adcp.GovernanceAccount{Brand: acct.Brand, Operator: acct.Operator}
}
results = append(results, adcp.GovernanceResult{
Account: govAcct, Status: "synced", GovernanceAgents: acct.GovernanceAgents,
})
}
return adcp.GovernanceResponse(results)
})
get_productsvar products = []adcp.Product{
{
ProductID: "sponsored-product", Name: "Sponsored Product",
Description: "Promoted product listings in search results",
Channels: []string{"retail_media"}, DeliveryType: "non_guaranteed",
PublisherProperties: []adcp.PublisherPropertySelector{
{PublisherDomain: "shop.example", SelectionType: "all"},
},
PricingOptions: []adcp.PricingOption{
{PricingOptionID: "sp-cpc", PricingModel: "cpc", FixedPrice: adcp.Ptr(0.50), Currency: "USD"},
},
FormatIDs: []adcp.FormatRef{{AgentURL: agentURL, ID: "product-card"}},
ReportingCapabilities: adcp.ReportingCapabilities{
AvailableMetrics: []string{"impressions", "spend", "clicks", "conversions"},
AvailableReportingFrequencies: []string{"daily"},
ExpectedDelayMinutes: 60,
Timezone: "UTC",
SupportsWebhooks: false,
DateRangeSupport: "date_range",
},
},
}
adcp.AddTool(server, "get_products", "Available products",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.GetProductsRequest) (*mcp.CallToolResult, any, error) {
return adcp.ProductsResponse(&adcp.ProductsData{Products: products, Sandbox: true})
})
create_media_buyadcp.AddTool(server, "create_media_buy", "Create media buy",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.CreateMediaBuyRequest) (*mcp.CallToolResult, any, error) {
id := fmt.Sprintf("mb-%d", counter)
var pkgs []adcp.Package
for i, p := range input.Packages {
pkgs = append(pkgs, adcp.Package{
PackageID: fmt.Sprintf("%s-pkg-%d", id, i+1),
ProductID: p.ProductID, PricingOptionID: p.PricingOptionID, Budget: p.Budget,
CreativeAssignments: p.CreativeAssignments,
})
}
return adcp.MediaBuyResponse(&adcp.CreateMediaBuySuccess{
MediaBuyID: id, Status: "active", Packages: pkgs,
ValidActions: []string{"pause", "cancel", "sync_creatives", "update_packages"},
})
})
get_media_buysadcp.AddTool(server, "get_media_buys", "List media buys",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.GetMediaBuysRequest) (*mcp.CallToolResult, any, error) {
buys := []adcp.MediaBuyData{{
MediaBuyID: "mb-1", Status: "active", Currency: "USD", TotalBudget: 1000,
ValidActions: []string{"pause", "cancel", "sync_creatives", "update_packages"},
Packages: []adcp.PackageStatus{{Package: adcp.Package{PackageID: "mb-1-pkg-1", ProductID: "sponsored-products", Budget: 1000}}},
}}
return adcp.MediaBuysResponse(buys, true)
})
list_creative_formatsadcp.AddTool(server, "list_creative_formats", "Available formats",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.ListCreativeFormatsRequest) (*mcp.CallToolResult, any, error) {
return adcp.CreativeFormatsResponse(creativeFormats, true)
})
sync_creativesadcp.AddTool(server, "sync_creatives", "Submit creatives",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.SyncCreativesRequest) (*mcp.CallToolResult, any, error) {
var results []adcp.CreativeResult
for _, c := range input.Creatives {
results = append(results, adcp.CreativeResult{
CreativeID: c.CreativeID, Action: "created", Status: "approved",
})
}
return adcp.SyncCreativesResponse(results, true)
})
get_media_buy_deliveryadcp.AddTool(server, "get_media_buy_delivery", "Delivery metrics",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.GetMediaBuyDeliveryRequest) (*mcp.CallToolResult, any, error) {
deliveries := make([]adcp.MediaBuyDelivery, 0)
// populate from store
return adcp.DeliveryResponse(&adcp.DeliveryData{
ReportingPeriod: adcp.ReportingPeriod{Start: start, End: end},
MediaBuyDeliveries: deliveries,
})
})
sync_catalogsadcp.AddTool(server, "sync_catalogs", "Accept product catalog feeds",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.SyncCatalogsRequest) (*mcp.CallToolResult, any, error) {
var results []adcp.CatalogResult
for _, c := range input.Catalogs {
count := len(c.Items)
if count == 0 { count = 10 }
results = append(results, adcp.CatalogResult{
CatalogID: c.CatalogID, Action: "created", ItemCount: count, ItemsApproved: count,
})
}
return adcp.SyncCatalogsResponse(results, true)
})
sync_event_sourcesadcp.AddTool(server, "sync_event_sources", "Register event tracking",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.SyncEventSourcesRequest) (*mcp.CallToolResult, any, error) {
var results []adcp.EventSourceResult
for _, es := range input.EventSources {
results = append(results, adcp.EventSourceResult{
EventSourceID: es.EventSourceID, Action: "created",
Setup: &adcp.EventSourceSetup{
Snippet: fmt.Sprintf("<script src=\"https://track.example.com/%s.js\"></script>", es.EventSourceID),
Description: "Add this snippet to your checkout page",
},
})
}
return adcp.SyncEventSourcesResponse(results, true)
})
log_eventadcp.AddTool(server, "log_event", "Accept conversion events",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.LogEventRequest) (*mcp.CallToolResult, any, error) {
return adcp.LogEventResponse(len(input.Events), len(input.Events), 0.85, true)
})
provide_performance_feedbackadcp.AddTool(server, "provide_performance_feedback", "Accept performance metrics",
func(ctx context.Context, req *mcp.CallToolRequest, input adcp.ProvidePerformanceFeedbackRequest) (*mcp.CallToolResult, any, error) {
return adcp.PerformanceFeedbackResponse(true)
})
Same pattern as seller — adcp.RegisterTestController(server, store) with ForceAccountStatus, ForceMediaBuyStatus, ForceCreativeStatus, SimulateDelivery, SimulateBudgetSpend.
package main
import (
"context"
"fmt"
"log"
"sync"
"time"
"github.com/adcontextprotocol/adcp-go/adcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
const agentURL = "http://localhost:3001/mcp"
type store struct {
mu sync.RWMutex
accounts map[string]*adcp.AccountResult
mediaBuys map[string]*adcp.MediaBuyData
creatives map[string]string
delivery map[string]*deliveryState
catalogs map[string]int
eventSources map[string]bool
}
type deliveryState struct { Impressions, Clicks int; Spend float64 }
var products = []adcp.Product{ /* with Description, PublisherProperties, FormatIDs */ }
var creativeFormats = []adcp.CreativeFormat{ /* with item_type: "individual" */ }
func createServer(s *store) *mcp.Server {
server := mcp.NewServer(&mcp.Implementation{Name: "my-retail", Version: "1.0.0"}, nil)
// Register all 13 tools + test controller
return server
}
func main() {
s := &store{accounts: make(map[string]*adcp.AccountResult), mediaBuys: make(map[string]*adcp.MediaBuyData), creatives: make(map[string]string), delivery: make(map[string]*deliveryState), catalogs: make(map[string]int), eventSources: make(map[string]bool)}
log.Fatal(adcp.Serve(func() *mcp.Server { return createServer(s) }))
}
module your-retail-media
go 1.25
require (
github.com/adcontextprotocol/adcp-go/adcp v0.0.0
github.com/modelcontextprotocol/go-sdk v1.5.0
)
Then go mod tidy.
go run main.go &
npx @adcp/client storyboard run http://localhost:3001/mcp media_buy_catalog_creative --json
| Mistake | Fix |
|---|---|
Using mcp.AddTool directly | Use adcp.AddTool |
Missing conversion_tracking in supported_protocols | Storyboard rejects catalog/event tools without it |
Products missing description | Required field |
Missing publisher_properties, format_ids, or reporting_capabilities | Required fields |
sync_catalogs missing item_count | Required field |
log_event missing events_received | Required counter |
log_event missing match_quality | Include match quality score (0.0-1.0) via adcp.LogEventResponse |
sync_event_sources missing setup.snippet | Include Setup with integration snippet on each event source |
sync_governance response key results | Must be accounts |
get_delivery returns null for empty arrays | Use make([]T, 0) |
get_delivery returns null for empty deliveries | Use adcp.DeliveryResponse |
| Uppercase pricing model | Use "cpm", "cpc" not "CPM" |
| No mutex on maps | Use sync.RWMutex |
| Function | Usage |
|---|---|
adcp.AddTool(server, name, desc, handler) | Register tool |
adcp.Serve(createAgent) | HTTP server |
adcp.RegisterTestController(server, store) | Test controller |
adcp.CapabilitiesResponse(data) | Capabilities |
adcp.ProductsResponse(data) | Products |
adcp.MediaBuyResponse(*CreateMediaBuySuccess|*CreateMediaBuyError|*CreateMediaBuySubmitted) | Create media buy |
adcp.CreateMediaBuySuccessResponse(data) | Sync create media buy |
adcp.CreateMediaBuyErrorResponse(data) | Create media buy error branch |
adcp.CreateMediaBuySubmittedResponse(taskID, message) | Async create media buy |
adcp.MediaBuysResponse(buys, sandbox) | List media buys |
adcp.DeliveryResponse(data) | Delivery metrics |
adcp.SyncAccountsResponse(accounts, sandbox) | Sync accounts |
adcp.GovernanceResponse(accounts) | Sync governance |
adcp.CreativeFormatsResponse(formats, sandbox) | Creative formats |
adcp.SyncCreativesResponse(creatives, sandbox) | Sync creatives |
adcp.SyncCatalogsResponse(catalogs, sandbox) | Sync catalogs |
adcp.SyncEventSourcesResponse(sources, sandbox) | Event sources |
adcp.LogEventResponse(received, processed, matchQuality, sandbox) | Log events |
adcp.PerformanceFeedbackResponse(sandbox) | Performance feedback |
adcp.Result(data, summary) | Generic response |
adcp.Errorf(code, opts) | Error response |
Input types: adcp.EmptyInput, adcp.SyncAccountsRequest, adcp.SyncGovernanceRequest, adcp.GetProductsRequest, adcp.CreateMediaBuyRequest, adcp.GetMediaBuysRequest, adcp.ListCreativeFormatsRequest, adcp.SyncCreativesRequest, adcp.GetMediaBuyDeliveryRequest, adcp.SyncCatalogsRequest, adcp.SyncEventSourcesRequest, adcp.LogEventRequest, adcp.ProvidePerformanceFeedbackRequest
The skill contains everything you need.