@adcp/client 4.23.0 → 4.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/bin/adcp.js +83 -18
- package/dist/lib/index.d.ts +2 -4
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +16 -12
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/server/index.d.ts +5 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +10 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/postgres-task-store.d.ts +105 -0
- package/dist/lib/server/postgres-task-store.d.ts.map +1 -0
- package/dist/lib/server/postgres-task-store.js +267 -0
- package/dist/lib/server/postgres-task-store.js.map +1 -0
- package/dist/lib/server/responses.d.ts +1 -0
- package/dist/lib/server/responses.d.ts.map +1 -1
- package/dist/lib/server/responses.js +1 -0
- package/dist/lib/server/responses.js.map +1 -1
- package/dist/lib/server/test-controller.d.ts +88 -0
- package/dist/lib/server/test-controller.d.ts.map +1 -0
- package/dist/lib/server/test-controller.js +227 -0
- package/dist/lib/server/test-controller.js.map +1 -0
- package/dist/lib/testing/agent-tester.d.ts +1 -1
- package/dist/lib/testing/agent-tester.d.ts.map +1 -1
- package/dist/lib/testing/agent-tester.js +13 -1
- package/dist/lib/testing/agent-tester.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +2 -0
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +76 -1
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/index.d.ts +1 -1
- package/dist/lib/testing/compliance/index.d.ts.map +1 -1
- package/dist/lib/testing/compliance/platform-storyboards.d.ts.map +1 -1
- package/dist/lib/testing/compliance/platform-storyboards.js +2 -0
- package/dist/lib/testing/compliance/platform-storyboards.js.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.d.ts.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.js +11 -2
- package/dist/lib/testing/compliance/storyboard-tracks.js.map +1 -1
- package/dist/lib/testing/compliance/types.d.ts +22 -1
- package/dist/lib/testing/compliance/types.d.ts.map +1 -1
- package/dist/lib/testing/orchestrator.d.ts.map +1 -1
- package/dist/lib/testing/orchestrator.js +5 -1
- package/dist/lib/testing/orchestrator.js.map +1 -1
- package/dist/lib/testing/scenarios/brand-rights.d.ts +19 -1
- package/dist/lib/testing/scenarios/brand-rights.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/brand-rights.js +138 -1
- package/dist/lib/testing/scenarios/brand-rights.js.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.js +7 -7
- package/dist/lib/testing/scenarios/deterministic.js.map +1 -1
- package/dist/lib/testing/scenarios/index.d.ts +1 -1
- package/dist/lib/testing/scenarios/index.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/index.js +4 -2
- package/dist/lib/testing/scenarios/index.js.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.js +4 -4
- package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
- package/dist/lib/testing/storyboard/loader.d.ts +1 -0
- package/dist/lib/testing/storyboard/loader.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/loader.js +14 -0
- package/dist/lib/testing/storyboard/loader.js.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.js +88 -11
- package/dist/lib/testing/storyboard/request-builder.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +83 -5
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/task-map.d.ts +2 -0
- package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/task-map.js +23 -9
- package/dist/lib/testing/storyboard/task-map.js.map +1 -1
- package/dist/lib/testing/storyboard/types.d.ts +6 -2
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +21 -4
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/testing/types.d.ts +1 -1
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/types/core.generated.d.ts +1 -1
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js +1 -1
- package/dist/lib/types/schemas.generated.d.ts +16 -13
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +7 -4
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/tools.generated.d.ts +14 -5
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/utils/capabilities.d.ts +2 -2
- package/dist/lib/utils/capabilities.d.ts.map +1 -1
- package/dist/lib/utils/capabilities.js +9 -3
- package/dist/lib/utils/capabilities.js.map +1 -1
- package/dist/lib/utils/response-schemas.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.js +9 -0
- package/dist/lib/utils/response-schemas.js.map +1 -1
- package/docs/llms.txt +10 -2
- package/package.json +8 -2
- package/skills/adcp/SKILL.md +118 -33
- package/skills/build-creative-agent/SKILL.md +221 -0
- package/skills/build-generative-seller-agent/SKILL.md +288 -0
- package/skills/build-retail-media-agent/SKILL.md +237 -0
- package/skills/build-seller-agent/SKILL.md +313 -0
- package/skills/build-signals-agent/SKILL.md +203 -0
- package/storyboards/campaign_governance_conditions.yaml +2 -2
- package/storyboards/campaign_governance_delivery.yaml +1 -1
- package/storyboards/campaign_governance_denied.yaml +1 -0
- package/storyboards/creative_generative.yaml +317 -0
- package/storyboards/creative_sales_agent.yaml +2 -2
- package/storyboards/creative_template.yaml +2 -1
- package/storyboards/deterministic_testing.yaml +271 -245
- package/storyboards/media_buy_catalog_creative.yaml +2 -1
- package/storyboards/media_buy_generative_seller.yaml +581 -0
- package/storyboards/media_buy_governance_escalation.yaml +1 -1
- package/storyboards/media_buy_guaranteed_approval.yaml +14 -14
- package/storyboards/media_buy_non_guaranteed.yaml +2 -2
- package/storyboards/media_buy_proposal_mode.yaml +5 -5
- package/storyboards/media_buy_seller.yaml +10 -10
- package/storyboards/media_buy_state_machine.yaml +4 -4
- package/storyboards/schema.yaml +2 -1
- package/storyboards/signal_marketplace.yaml +3 -0
- package/storyboards/signal_owned.yaml +1 -0
- package/storyboards/test-kits/acme-outdoor.yaml +118 -0
- package/storyboards/test-kits/nova-motors.yaml +134 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-seller-agent
|
|
3
|
+
description: Use when building an AdCP seller agent — a publisher, SSP, or retail media network that sells advertising inventory to buyer agents.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build a Seller Agent
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
A seller agent receives briefs from buyers, returns products with pricing, accepts media buys, manages creatives, and reports delivery. The business model — what you sell, how you price it, and whether humans approve deals — shapes every implementation decision. Determine that first.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- User wants to build an agent that sells ad inventory
|
|
15
|
+
- User mentions publisher, SSP, retail media, or media network in the context of AdCP
|
|
16
|
+
- User references `get_products`, `create_media_buy`, or the media buy protocol
|
|
17
|
+
|
|
18
|
+
**Not this skill:**
|
|
19
|
+
- Buying ad inventory → that's a buyer/DSP agent (see `docs/getting-started.md`)
|
|
20
|
+
- Serving audience segments → `skills/build-signals-agent/`
|
|
21
|
+
- Rendering creatives from briefs → that's a creative agent
|
|
22
|
+
|
|
23
|
+
## Before Writing Code
|
|
24
|
+
|
|
25
|
+
Determine these five things. Ask the user — don't guess.
|
|
26
|
+
|
|
27
|
+
### 1. What Kind of Seller?
|
|
28
|
+
|
|
29
|
+
- **Premium publisher** — guaranteed inventory, fixed pricing, IO approval (ESPN, NYT)
|
|
30
|
+
- **SSP / Exchange** — non-guaranteed, auction-based, instant activation
|
|
31
|
+
- **Retail media network** — both guaranteed and non-guaranteed, proposals, catalog-driven creative, conversion tracking
|
|
32
|
+
|
|
33
|
+
### 2. Guaranteed or Non-Guaranteed?
|
|
34
|
+
|
|
35
|
+
- **Guaranteed** — `delivery_type: "guaranteed"`, may require async approval (`submitted` → `pending_approval` → `confirmed`)
|
|
36
|
+
- **Non-guaranteed** — `delivery_type: "non_guaranteed"`, buyer sets `bid_price`, instant activation
|
|
37
|
+
|
|
38
|
+
Many sellers support both — different products can have different delivery types.
|
|
39
|
+
|
|
40
|
+
### 3. Products and Pricing
|
|
41
|
+
|
|
42
|
+
Get specific inventory. Each product needs:
|
|
43
|
+
- Name and description
|
|
44
|
+
- Channel: `display`, `olv`, `ctv`, `social`, `retail_media`, `dooh`, etc.
|
|
45
|
+
- Creative format requirements
|
|
46
|
+
- At least one pricing option
|
|
47
|
+
|
|
48
|
+
Pricing models:
|
|
49
|
+
- `cpm` — `{ pricing_model: "cpm", fixed_price: 12.00, currency: "USD" }`
|
|
50
|
+
- `cpc` — `{ pricing_model: "cpc", fixed_price: 1.50, currency: "USD" }`
|
|
51
|
+
- Auction — `{ pricing_model: "cpm", floor_price: 5.00, currency: "USD" }` (buyer bids above floor)
|
|
52
|
+
|
|
53
|
+
Each pricing option can set `min_spend_per_package` to enforce minimum budgets.
|
|
54
|
+
|
|
55
|
+
### 4. Approval Workflow
|
|
56
|
+
|
|
57
|
+
For guaranteed buys, choose one:
|
|
58
|
+
- **Instant confirmation** — `create_media_buy` returns completed with confirmed status. Simplest.
|
|
59
|
+
- **Async approval** — returns `submitted`, buyer polls `get_media_buys`. Use `registerAdcpTaskTool`.
|
|
60
|
+
- **Human-in-the-loop** — returns `input-required` with a setup URL for IO signing.
|
|
61
|
+
|
|
62
|
+
Non-guaranteed buys are always instant confirmation.
|
|
63
|
+
|
|
64
|
+
### 5. Creative Management
|
|
65
|
+
|
|
66
|
+
- **Standard** — `list_creative_formats` + `sync_creatives`. Buyer uploads assets, seller validates.
|
|
67
|
+
- **Catalog-driven** — buyer syncs product catalog via `sync_catalogs`. Common for retail media.
|
|
68
|
+
- **None** — creative handled out-of-band. Omit creative tools.
|
|
69
|
+
|
|
70
|
+
## Tools and Required Response Shapes
|
|
71
|
+
|
|
72
|
+
**`get_adcp_capabilities`** — register first, empty `{}` schema
|
|
73
|
+
```
|
|
74
|
+
capabilitiesResponse({
|
|
75
|
+
adcp: { major_versions: [3] },
|
|
76
|
+
supported_protocols: ['media_buy'],
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**`sync_accounts`** — `SyncAccountsRequestSchema.shape`
|
|
81
|
+
```
|
|
82
|
+
taskToolResponse({
|
|
83
|
+
accounts: [{
|
|
84
|
+
account_id: string, // required - your platform's ID
|
|
85
|
+
brand: { domain: string },// required - echo back from request
|
|
86
|
+
operator: string, // required - echo back from request
|
|
87
|
+
action: 'created' | 'updated', // required
|
|
88
|
+
status: 'active' | 'pending_approval', // required
|
|
89
|
+
}]
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**`sync_governance`** — `SyncGovernanceRequestSchema.shape`
|
|
94
|
+
```
|
|
95
|
+
taskToolResponse({
|
|
96
|
+
accounts: [{
|
|
97
|
+
account: { brand: {...}, operator: string }, // required - echo back
|
|
98
|
+
status: 'synced', // required
|
|
99
|
+
governance_agents: [{ url: string, categories?: string[] }], // required
|
|
100
|
+
}]
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**`get_products`** — `GetProductsRequestSchema.shape`
|
|
105
|
+
```
|
|
106
|
+
productsResponse({
|
|
107
|
+
products: Product[], // required - each needs product_id, delivery_type, pricing_options
|
|
108
|
+
sandbox: true, // for mock data
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**`create_media_buy`** — `CreateMediaBuyRequestSchema.shape`
|
|
113
|
+
```
|
|
114
|
+
mediaBuyResponse({
|
|
115
|
+
media_buy_id: string, // required
|
|
116
|
+
packages: [{ // required
|
|
117
|
+
package_id: string,
|
|
118
|
+
product_id: string,
|
|
119
|
+
pricing_option_id: string,
|
|
120
|
+
budget: number,
|
|
121
|
+
}],
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**`get_media_buys`** — `GetMediaBuysRequestSchema.shape`
|
|
126
|
+
```
|
|
127
|
+
taskToolResponse({
|
|
128
|
+
media_buys: [{
|
|
129
|
+
media_buy_id: string, // required
|
|
130
|
+
status: 'active' | 'pending_start' | ..., // required
|
|
131
|
+
currency: 'USD', // required
|
|
132
|
+
packages: [{
|
|
133
|
+
package_id: string, // required
|
|
134
|
+
}],
|
|
135
|
+
}]
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**`list_creative_formats`** — `ListCreativeFormatsRequestSchema.shape`
|
|
140
|
+
```
|
|
141
|
+
taskToolResponse({
|
|
142
|
+
formats: [{
|
|
143
|
+
format_id: { agent_url: string, id: string }, // required
|
|
144
|
+
name: string, // required
|
|
145
|
+
}]
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**`sync_creatives`** — `SyncCreativesRequestSchema.shape`
|
|
150
|
+
```
|
|
151
|
+
taskToolResponse({
|
|
152
|
+
creatives: [{
|
|
153
|
+
creative_id: string, // required - echo from request
|
|
154
|
+
action: 'created' | 'updated', // required
|
|
155
|
+
}]
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**`get_media_buy_delivery`** — `GetMediaBuyDeliveryRequestSchema.shape`
|
|
160
|
+
```
|
|
161
|
+
deliveryResponse({
|
|
162
|
+
reporting_period: { start: string, end: string }, // required - ISO timestamps
|
|
163
|
+
media_buy_deliveries: [{
|
|
164
|
+
media_buy_id: string, // required
|
|
165
|
+
status: 'active', // required
|
|
166
|
+
totals: { impressions: number, spend: number }, // required
|
|
167
|
+
by_package: [], // required (can be empty)
|
|
168
|
+
}]
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Compliance Testing (Optional)
|
|
173
|
+
|
|
174
|
+
Add `registerTestController` so the comply framework can deterministically test your state machines. Without it, compliance testing relies on observational storyboards that can't force state transitions.
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
import { registerTestController } from '@adcp/client';
|
|
178
|
+
import type { TestControllerStore } from '@adcp/client';
|
|
179
|
+
|
|
180
|
+
const store: TestControllerStore = {
|
|
181
|
+
async forceAccountStatus(accountId, status) {
|
|
182
|
+
const prev = accounts.get(accountId);
|
|
183
|
+
if (!prev) throw new TestControllerError('NOT_FOUND', `Account ${accountId} not found`);
|
|
184
|
+
accounts.set(accountId, status);
|
|
185
|
+
return { success: true, previous_state: prev, current_state: status };
|
|
186
|
+
},
|
|
187
|
+
async forceMediaBuyStatus(mediaBuyId, status) {
|
|
188
|
+
const prev = mediaBuys.get(mediaBuyId);
|
|
189
|
+
if (!prev) throw new TestControllerError('NOT_FOUND', `Media buy ${mediaBuyId} not found`);
|
|
190
|
+
const terminal = ['completed', 'rejected', 'canceled'];
|
|
191
|
+
if (terminal.includes(prev))
|
|
192
|
+
throw new TestControllerError('INVALID_TRANSITION', `Cannot transition from ${prev}`, prev);
|
|
193
|
+
mediaBuys.set(mediaBuyId, status);
|
|
194
|
+
return { success: true, previous_state: prev, current_state: status };
|
|
195
|
+
},
|
|
196
|
+
async forceCreativeStatus(creativeId, status, rejectionReason) {
|
|
197
|
+
const prev = creatives.get(creativeId);
|
|
198
|
+
if (!prev) throw new TestControllerError('NOT_FOUND', `Creative ${creativeId} not found`);
|
|
199
|
+
if (prev === 'archived')
|
|
200
|
+
throw new TestControllerError('INVALID_TRANSITION', `Cannot transition from archived`, prev);
|
|
201
|
+
creatives.set(creativeId, status);
|
|
202
|
+
return { success: true, previous_state: prev, current_state: status };
|
|
203
|
+
},
|
|
204
|
+
async simulateDelivery(mediaBuyId, params) {
|
|
205
|
+
// Accumulate delivery data and return simulated + cumulative totals
|
|
206
|
+
return { success: true, simulated: { ...params }, cumulative: { ...params } };
|
|
207
|
+
},
|
|
208
|
+
async simulateBudgetSpend(params) {
|
|
209
|
+
return { success: true, simulated: { spend_percentage: params.spend_percentage } };
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
registerTestController(server, store);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
When using this, declare `compliance_testing` in `supported_protocols`:
|
|
217
|
+
```
|
|
218
|
+
capabilitiesResponse({
|
|
219
|
+
adcp: { major_versions: [3] },
|
|
220
|
+
supported_protocols: ['media_buy', 'compliance_testing'],
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Only implement the store methods for scenarios your agent supports. Unimplemented methods are excluded from `list_scenarios` automatically.
|
|
225
|
+
|
|
226
|
+
The storyboard tests state machine correctness:
|
|
227
|
+
- `NOT_FOUND` when forcing transitions on unknown entities
|
|
228
|
+
- `INVALID_TRANSITION` when transitioning from terminal states (completed, rejected, canceled for media buys; archived for creatives)
|
|
229
|
+
- Successful transitions between valid states
|
|
230
|
+
|
|
231
|
+
Throw `TestControllerError` from store methods for typed errors. The SDK validates status enum values before calling your store.
|
|
232
|
+
|
|
233
|
+
Validate with: `adcp storyboard run <agent> deterministic_testing --json`
|
|
234
|
+
|
|
235
|
+
## SDK Quick Reference
|
|
236
|
+
|
|
237
|
+
| SDK piece | Usage |
|
|
238
|
+
|-----------|-------|
|
|
239
|
+
| `serve(createAgent)` | Start HTTP server on `:3001/mcp` |
|
|
240
|
+
| `createTaskCapableServer(name, version, { taskStore })` | Create MCP server with task support |
|
|
241
|
+
| `server.tool(name, Schema.shape, handler)` | Register tool — `.shape` unwraps Zod for MCP SDK |
|
|
242
|
+
| `capabilitiesResponse(data)` | Build `get_adcp_capabilities` response |
|
|
243
|
+
| `productsResponse(data)` | Build `get_products` response |
|
|
244
|
+
| `mediaBuyResponse(data)` | Build `create_media_buy` response |
|
|
245
|
+
| `deliveryResponse(data)` | Build `get_media_buy_delivery` response |
|
|
246
|
+
| `taskToolResponse(data, summary)` | Build generic tool response |
|
|
247
|
+
| `adcpError(code, { message })` | Structured error (e.g., `BUDGET_TOO_LOW`, `PRODUCT_NOT_FOUND`) |
|
|
248
|
+
| `registerTestController(server, store)` | Add `comply_test_controller` for deterministic testing |
|
|
249
|
+
| `TestControllerError(code, message)` | Typed error from store methods |
|
|
250
|
+
|
|
251
|
+
Import everything from `@adcp/client`. Types from `@adcp/client` with `import type`.
|
|
252
|
+
|
|
253
|
+
## Implementation
|
|
254
|
+
|
|
255
|
+
1. Single `.ts` file — all tools in one file
|
|
256
|
+
2. Always register `get_adcp_capabilities` as the **first** tool with empty `{}` schema
|
|
257
|
+
3. Use `Schema.shape` (not `Schema`) when registering tools
|
|
258
|
+
4. Use response builders — never return raw JSON
|
|
259
|
+
5. Set `sandbox: true` on all mock/demo responses
|
|
260
|
+
6. Use `ServeContext` pattern: `function createAgent({ taskStore }: ServeContext)` and pass `taskStore` to `createTaskCapableServer`
|
|
261
|
+
|
|
262
|
+
The skill contains everything you need. Do not read additional docs before writing code.
|
|
263
|
+
|
|
264
|
+
## Validation
|
|
265
|
+
|
|
266
|
+
**After writing the agent, validate it. Fix failures. Repeat.**
|
|
267
|
+
|
|
268
|
+
**Full validation** (if you can bind ports):
|
|
269
|
+
```bash
|
|
270
|
+
npx tsx agent.ts &
|
|
271
|
+
npx @adcp/client storyboard run http://localhost:3001/mcp media_buy_seller --json
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Sandbox validation** (if ports are blocked):
|
|
275
|
+
```bash
|
|
276
|
+
npx tsc --noEmit agent.ts
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
When storyboard output shows failures, fix each one:
|
|
280
|
+
- `response_schema` → response doesn't match Zod schema
|
|
281
|
+
- `field_present` → required field missing
|
|
282
|
+
- MCP error → check tool registration (schema, name)
|
|
283
|
+
|
|
284
|
+
**Keep iterating until all steps pass.**
|
|
285
|
+
|
|
286
|
+
## Storyboards
|
|
287
|
+
|
|
288
|
+
| Storyboard | Use case |
|
|
289
|
+
|-----------|----------|
|
|
290
|
+
| `media_buy_seller` | Full lifecycle — every seller should pass this |
|
|
291
|
+
| `media_buy_non_guaranteed` | Auction flow with bid adjustment |
|
|
292
|
+
| `media_buy_guaranteed_approval` | IO approval workflow |
|
|
293
|
+
| `media_buy_proposal_mode` | AI-generated proposals |
|
|
294
|
+
| `media_buy_catalog_creative` | Catalog sync + conversions |
|
|
295
|
+
|
|
296
|
+
## Common Mistakes
|
|
297
|
+
|
|
298
|
+
| Mistake | Fix |
|
|
299
|
+
|---------|-----|
|
|
300
|
+
| Pass `Schema` instead of `Schema.shape` | MCP SDK needs unwrapped Zod fields |
|
|
301
|
+
| Skip `get_adcp_capabilities` | Must be the first tool registered |
|
|
302
|
+
| Return raw JSON without response builders | LLM clients need the text content layer |
|
|
303
|
+
| Missing `brand`/`operator` in sync_accounts response | Echo them back from the request — they're required |
|
|
304
|
+
| sync_governance returns wrong shape | Must include `status: 'synced'` and `governance_agents` array |
|
|
305
|
+
| `sandbox: false` on mock data | Buyers may treat mock data as real |
|
|
306
|
+
|
|
307
|
+
## Reference
|
|
308
|
+
|
|
309
|
+
- `docs/guides/BUILD-AN-AGENT.md` — SDK patterns and async tools
|
|
310
|
+
- `docs/llms.txt` — full protocol reference
|
|
311
|
+
- `docs/TYPE-SUMMARY.md` — curated type signatures
|
|
312
|
+
- `storyboards/media_buy_seller.yaml` — full buyer interaction sequence
|
|
313
|
+
- `examples/error-compliant-server.ts` — seller with error handling
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-signals-agent
|
|
3
|
+
description: 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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build a Signals Agent
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
A signals agent serves audience segments to buyers for campaign targeting. Two tools: `get_signals` (discovery) and `activate_signal` (push to DSPs or sales agents). The business model — marketplace vs owned data — shapes every implementation decision. Determine that first.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- User wants to build an agent that serves audience/targeting data
|
|
15
|
+
- User mentions signals, segments, audiences, data provider, or CDP in the context of AdCP
|
|
16
|
+
- User references `get_signals`, `activate_signal`, or the signals protocol
|
|
17
|
+
|
|
18
|
+
**Not this skill:**
|
|
19
|
+
- Selling ad inventory (products, packages, media buys) → `skills/build-seller-agent/`
|
|
20
|
+
- Rendering creatives from briefs → that's a creative agent
|
|
21
|
+
- Building a client that *calls* a signals agent → see `docs/getting-started.md`
|
|
22
|
+
|
|
23
|
+
## Before Writing Code
|
|
24
|
+
|
|
25
|
+
Determine these four things. Ask the user — don't guess.
|
|
26
|
+
|
|
27
|
+
### 1. Marketplace or Owned?
|
|
28
|
+
|
|
29
|
+
These are fundamentally different businesses.
|
|
30
|
+
|
|
31
|
+
**Marketplace** — aggregates third-party data providers (LiveRamp, Oracle Data Cloud, Lotame). Each signal traces to a `data_provider_domain` that buyers can verify via `adagents.json`. `signal_type: "marketplace"`, `signal_id.source: "catalog"`.
|
|
32
|
+
|
|
33
|
+
**Owned** — first-party data (retailer CDP, publisher contextual, CRM). Buyers trust your agent directly. `signal_type: "owned"` or `"custom"`, `signal_id.source: "agent"`.
|
|
34
|
+
|
|
35
|
+
### 2. What Segments?
|
|
36
|
+
|
|
37
|
+
Get specifics: names, definitions, what each represents. Push for 3-5 segments with variety. Each needs:
|
|
38
|
+
- Clear behavioral/demographic definition
|
|
39
|
+
- Realistic `coverage_percentage` (typically 5-30%)
|
|
40
|
+
- Value type: `binary` (in/out), `categorical` (tier levels — define the categories), or `numeric` (score range — define min/max)
|
|
41
|
+
|
|
42
|
+
### 3. Pricing
|
|
43
|
+
|
|
44
|
+
At least one pricing option per signal:
|
|
45
|
+
- `cpm` — `{ pricing_option_id: "po_cpm", model: "cpm", cpm: 2.50, currency: "USD" }`
|
|
46
|
+
- `percent_of_media` — `{ pricing_option_id: "po_pom", model: "percent_of_media", percent: 15, currency: "USD" }`
|
|
47
|
+
- `flat_fee` — `{ pricing_option_id: "po_flat", model: "flat_fee", amount: 5000, period: "monthly", currency: "USD" }`
|
|
48
|
+
|
|
49
|
+
### 4. Activation Destinations
|
|
50
|
+
|
|
51
|
+
If implementing `activate_signal`:
|
|
52
|
+
- **Platform** (DSP): `type: "platform"`, returns `activation_key: { type: "segment_id", segment_id: "..." }`
|
|
53
|
+
- **Agent** (sales agent): `type: "agent"`, returns `activation_key: { type: "key_value", key: "...", value: "..." }`
|
|
54
|
+
|
|
55
|
+
## Tools and Required Response Shapes
|
|
56
|
+
|
|
57
|
+
**`get_adcp_capabilities`** — register first, empty `{}` schema
|
|
58
|
+
```
|
|
59
|
+
capabilitiesResponse({
|
|
60
|
+
adcp: { major_versions: [3] },
|
|
61
|
+
supported_protocols: ['signals'],
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**`get_signals`** — `GetSignalsRequestSchema.shape`
|
|
66
|
+
|
|
67
|
+
Two discovery modes — support both:
|
|
68
|
+
1. `signal_spec` — natural language. Match against segment names and descriptions.
|
|
69
|
+
2. `signal_ids` — exact lookup by `{ source, data_provider_domain, id }` or `{ source, agent_url, id }`.
|
|
70
|
+
|
|
71
|
+
Plus filtering via `filters.catalog_types`, `filters.max_cpm`, `filters.min_coverage_percentage`, and `max_results`.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
taskToolResponse({
|
|
75
|
+
signals: [{
|
|
76
|
+
signal_agent_segment_id: string, // required - key for activate_signal
|
|
77
|
+
name: string, // required
|
|
78
|
+
description: string, // required
|
|
79
|
+
signal_type: 'marketplace' | 'owned' | 'custom', // required
|
|
80
|
+
data_provider: string, // required - your company name
|
|
81
|
+
coverage_percentage: number, // required - 0 to 100
|
|
82
|
+
deployments: [], // required - empty array (not live until activated)
|
|
83
|
+
pricing_options: [{ // required - at least one
|
|
84
|
+
pricing_option_id: string, // required
|
|
85
|
+
model: 'cpm', // required - discriminator
|
|
86
|
+
cpm: number, // required for cpm model
|
|
87
|
+
currency: 'USD', // required
|
|
88
|
+
}],
|
|
89
|
+
// signal_id is critical — shape depends on marketplace vs owned:
|
|
90
|
+
signal_id: {
|
|
91
|
+
source: 'catalog', // marketplace
|
|
92
|
+
data_provider_domain: string, // marketplace — domain for provenance verification
|
|
93
|
+
id: string, // unique segment ID
|
|
94
|
+
},
|
|
95
|
+
// OR for owned:
|
|
96
|
+
signal_id: {
|
|
97
|
+
source: 'agent', // owned
|
|
98
|
+
agent_url: string, // your agent URL
|
|
99
|
+
id: string,
|
|
100
|
+
},
|
|
101
|
+
value_type: 'binary' | 'categorical' | 'numeric', // optional but recommended
|
|
102
|
+
}],
|
|
103
|
+
sandbox: true, // for mock data
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**`activate_signal`** — `ActivateSignalRequestSchema.shape`
|
|
108
|
+
|
|
109
|
+
Look up by `signal_agent_segment_id`. Validate `pricing_option_id`. Return deployments matching the requested destinations.
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
taskToolResponse({
|
|
113
|
+
deployments: [{
|
|
114
|
+
// Match the destination type from the request:
|
|
115
|
+
type: 'platform', // for platform destinations
|
|
116
|
+
platform: string, // echo from request destination
|
|
117
|
+
account: string | null, // echo from request
|
|
118
|
+
is_live: true, // signal is now active
|
|
119
|
+
activation_key: {
|
|
120
|
+
type: 'segment_id',
|
|
121
|
+
segment_id: string, // platform-specific segment ID
|
|
122
|
+
},
|
|
123
|
+
}],
|
|
124
|
+
// OR for agent destinations:
|
|
125
|
+
deployments: [{
|
|
126
|
+
type: 'agent',
|
|
127
|
+
agent_url: string,
|
|
128
|
+
is_live: true,
|
|
129
|
+
activation_key: {
|
|
130
|
+
type: 'key_value',
|
|
131
|
+
key: string,
|
|
132
|
+
value: string,
|
|
133
|
+
},
|
|
134
|
+
}],
|
|
135
|
+
sandbox: true,
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## SDK Quick Reference
|
|
140
|
+
|
|
141
|
+
| SDK piece | Usage |
|
|
142
|
+
|-----------|-------|
|
|
143
|
+
| `serve(createAgent)` | Start HTTP server on `:3001/mcp` |
|
|
144
|
+
| `createTaskCapableServer(name, version, { taskStore })` | Create MCP server with task support |
|
|
145
|
+
| `server.tool(name, Schema.shape, handler)` | Register tool — `.shape` unwraps Zod |
|
|
146
|
+
| `capabilitiesResponse(data)` | Build `get_adcp_capabilities` response |
|
|
147
|
+
| `taskToolResponse(data, summary)` | Build tool response |
|
|
148
|
+
| `adcpError(code, { message })` | Structured error (`SIGNAL_NOT_FOUND`, `INVALID_DESTINATION`) |
|
|
149
|
+
| `GetSignalsRequestSchema.shape` | Zod schema for get_signals input |
|
|
150
|
+
| `ActivateSignalRequestSchema.shape` | Zod schema for activate_signal input |
|
|
151
|
+
| `type Signal = GetSignalsResponse['signals'][number]` | Type for a single signal object |
|
|
152
|
+
|
|
153
|
+
Import everything from `@adcp/client`. Types from `@adcp/client` with `import type`.
|
|
154
|
+
|
|
155
|
+
## Implementation
|
|
156
|
+
|
|
157
|
+
1. Single `.ts` file — all tools in one file
|
|
158
|
+
2. Always register `get_adcp_capabilities` as the **first** tool with empty `{}` schema
|
|
159
|
+
3. Use `Schema.shape` (not `Schema`) when registering tools
|
|
160
|
+
4. Set `sandbox: true` for mock/demo data
|
|
161
|
+
5. Use `ServeContext` pattern: `function createAgent({ taskStore }: ServeContext)`
|
|
162
|
+
|
|
163
|
+
The skill contains everything you need. Do not read additional docs before writing code.
|
|
164
|
+
|
|
165
|
+
## Validation
|
|
166
|
+
|
|
167
|
+
**After writing the agent, validate it. Fix failures. Repeat.**
|
|
168
|
+
|
|
169
|
+
**Full validation** (if you can bind ports):
|
|
170
|
+
```bash
|
|
171
|
+
npx tsx agent.ts &
|
|
172
|
+
npx @adcp/client storyboard run http://localhost:3001/mcp signal_owned --json # for owned data
|
|
173
|
+
npx @adcp/client storyboard run http://localhost:3001/mcp signal_marketplace --json # for marketplace
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Sandbox validation** (if ports are blocked):
|
|
177
|
+
```bash
|
|
178
|
+
npx tsc --noEmit agent.ts
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Keep iterating until all steps pass.**
|
|
182
|
+
|
|
183
|
+
## Common Mistakes
|
|
184
|
+
|
|
185
|
+
| Mistake | Fix |
|
|
186
|
+
|---------|-----|
|
|
187
|
+
| Pass `Schema` instead of `Schema.shape` | MCP SDK needs unwrapped Zod fields |
|
|
188
|
+
| Skip `get_adcp_capabilities` | Must be the first tool registered |
|
|
189
|
+
| Missing `signal_agent_segment_id` on signals | Buyers can't activate without it |
|
|
190
|
+
| Wrong `signal_id` shape | Marketplace: `{ source: "catalog", data_provider_domain, id }`. Owned: `{ source: "agent", agent_url, id }` |
|
|
191
|
+
| Missing `data_provider` field | Required on every signal — your company/brand name |
|
|
192
|
+
| Empty `pricing_options` array | Must have at least one pricing option per signal |
|
|
193
|
+
| `is_live: true` in get_signals deployments | Signals aren't live until `activate_signal` — use empty `deployments: []` |
|
|
194
|
+
| Activation doesn't match destination type | If request has `type: "platform"`, deployment must be `type: "platform"` |
|
|
195
|
+
| `sandbox: false` on mock data | Buyers may treat mock data as real |
|
|
196
|
+
|
|
197
|
+
## Reference
|
|
198
|
+
|
|
199
|
+
- `examples/signals-agent.ts` — complete runnable example
|
|
200
|
+
- `storyboards/signal_marketplace.yaml` — buyer call sequences for marketplace agent
|
|
201
|
+
- `storyboards/signal_owned.yaml` — call sequences for owned data agent
|
|
202
|
+
- `docs/guides/BUILD-AN-AGENT.md` — SDK patterns
|
|
203
|
+
- `docs/llms.txt` — full protocol reference
|
|
@@ -160,9 +160,9 @@ phases:
|
|
|
160
160
|
expected: |
|
|
161
161
|
Confirm the media buy with governance approval:
|
|
162
162
|
- media_buy_id: your platform's identifier
|
|
163
|
-
- status:
|
|
163
|
+
- status: active
|
|
164
164
|
- governance_context: echoed back confirming governance was validated
|
|
165
|
-
- packages:
|
|
165
|
+
- packages: line items
|
|
166
166
|
|
|
167
167
|
sample_request:
|
|
168
168
|
account:
|
|
@@ -164,7 +164,7 @@ phases:
|
|
|
164
164
|
- check: response_schema
|
|
165
165
|
description: "Response matches get-media-buy-delivery-response.json schema"
|
|
166
166
|
- check: field_present
|
|
167
|
-
path: "
|
|
167
|
+
path: "media_buy_deliveries"
|
|
168
168
|
description: "Response contains media buy delivery data"
|
|
169
169
|
|
|
170
170
|
- id: drift_recheck
|