@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.
Files changed (120) hide show
  1. package/README.md +23 -9
  2. package/bin/adcp.js +83 -18
  3. package/dist/lib/index.d.ts +2 -4
  4. package/dist/lib/index.d.ts.map +1 -1
  5. package/dist/lib/index.js +16 -12
  6. package/dist/lib/index.js.map +1 -1
  7. package/dist/lib/server/index.d.ts +5 -1
  8. package/dist/lib/server/index.d.ts.map +1 -1
  9. package/dist/lib/server/index.js +10 -1
  10. package/dist/lib/server/index.js.map +1 -1
  11. package/dist/lib/server/postgres-task-store.d.ts +105 -0
  12. package/dist/lib/server/postgres-task-store.d.ts.map +1 -0
  13. package/dist/lib/server/postgres-task-store.js +267 -0
  14. package/dist/lib/server/postgres-task-store.js.map +1 -0
  15. package/dist/lib/server/responses.d.ts +1 -0
  16. package/dist/lib/server/responses.d.ts.map +1 -1
  17. package/dist/lib/server/responses.js +1 -0
  18. package/dist/lib/server/responses.js.map +1 -1
  19. package/dist/lib/server/test-controller.d.ts +88 -0
  20. package/dist/lib/server/test-controller.d.ts.map +1 -0
  21. package/dist/lib/server/test-controller.js +227 -0
  22. package/dist/lib/server/test-controller.js.map +1 -0
  23. package/dist/lib/testing/agent-tester.d.ts +1 -1
  24. package/dist/lib/testing/agent-tester.d.ts.map +1 -1
  25. package/dist/lib/testing/agent-tester.js +13 -1
  26. package/dist/lib/testing/agent-tester.js.map +1 -1
  27. package/dist/lib/testing/compliance/comply.d.ts +2 -0
  28. package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
  29. package/dist/lib/testing/compliance/comply.js +76 -1
  30. package/dist/lib/testing/compliance/comply.js.map +1 -1
  31. package/dist/lib/testing/compliance/index.d.ts +1 -1
  32. package/dist/lib/testing/compliance/index.d.ts.map +1 -1
  33. package/dist/lib/testing/compliance/platform-storyboards.d.ts.map +1 -1
  34. package/dist/lib/testing/compliance/platform-storyboards.js +2 -0
  35. package/dist/lib/testing/compliance/platform-storyboards.js.map +1 -1
  36. package/dist/lib/testing/compliance/storyboard-tracks.d.ts.map +1 -1
  37. package/dist/lib/testing/compliance/storyboard-tracks.js +11 -2
  38. package/dist/lib/testing/compliance/storyboard-tracks.js.map +1 -1
  39. package/dist/lib/testing/compliance/types.d.ts +22 -1
  40. package/dist/lib/testing/compliance/types.d.ts.map +1 -1
  41. package/dist/lib/testing/orchestrator.d.ts.map +1 -1
  42. package/dist/lib/testing/orchestrator.js +5 -1
  43. package/dist/lib/testing/orchestrator.js.map +1 -1
  44. package/dist/lib/testing/scenarios/brand-rights.d.ts +19 -1
  45. package/dist/lib/testing/scenarios/brand-rights.d.ts.map +1 -1
  46. package/dist/lib/testing/scenarios/brand-rights.js +138 -1
  47. package/dist/lib/testing/scenarios/brand-rights.js.map +1 -1
  48. package/dist/lib/testing/scenarios/deterministic.js +7 -7
  49. package/dist/lib/testing/scenarios/deterministic.js.map +1 -1
  50. package/dist/lib/testing/scenarios/index.d.ts +1 -1
  51. package/dist/lib/testing/scenarios/index.d.ts.map +1 -1
  52. package/dist/lib/testing/scenarios/index.js +4 -2
  53. package/dist/lib/testing/scenarios/index.js.map +1 -1
  54. package/dist/lib/testing/scenarios/media-buy.js +4 -4
  55. package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
  56. package/dist/lib/testing/storyboard/loader.d.ts +1 -0
  57. package/dist/lib/testing/storyboard/loader.d.ts.map +1 -1
  58. package/dist/lib/testing/storyboard/loader.js +14 -0
  59. package/dist/lib/testing/storyboard/loader.js.map +1 -1
  60. package/dist/lib/testing/storyboard/request-builder.d.ts.map +1 -1
  61. package/dist/lib/testing/storyboard/request-builder.js +88 -11
  62. package/dist/lib/testing/storyboard/request-builder.js.map +1 -1
  63. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  64. package/dist/lib/testing/storyboard/runner.js +83 -5
  65. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  66. package/dist/lib/testing/storyboard/task-map.d.ts +2 -0
  67. package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
  68. package/dist/lib/testing/storyboard/task-map.js +23 -9
  69. package/dist/lib/testing/storyboard/task-map.js.map +1 -1
  70. package/dist/lib/testing/storyboard/types.d.ts +6 -2
  71. package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
  72. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  73. package/dist/lib/testing/storyboard/validations.js +21 -4
  74. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  75. package/dist/lib/testing/types.d.ts +1 -1
  76. package/dist/lib/testing/types.d.ts.map +1 -1
  77. package/dist/lib/types/core.generated.d.ts +1 -1
  78. package/dist/lib/types/core.generated.d.ts.map +1 -1
  79. package/dist/lib/types/core.generated.js +1 -1
  80. package/dist/lib/types/schemas.generated.d.ts +16 -13
  81. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  82. package/dist/lib/types/schemas.generated.js +7 -4
  83. package/dist/lib/types/schemas.generated.js.map +1 -1
  84. package/dist/lib/types/tools.generated.d.ts +14 -5
  85. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  86. package/dist/lib/utils/capabilities.d.ts +2 -2
  87. package/dist/lib/utils/capabilities.d.ts.map +1 -1
  88. package/dist/lib/utils/capabilities.js +9 -3
  89. package/dist/lib/utils/capabilities.js.map +1 -1
  90. package/dist/lib/utils/response-schemas.d.ts.map +1 -1
  91. package/dist/lib/utils/response-schemas.js +9 -0
  92. package/dist/lib/utils/response-schemas.js.map +1 -1
  93. package/docs/llms.txt +10 -2
  94. package/package.json +8 -2
  95. package/skills/adcp/SKILL.md +118 -33
  96. package/skills/build-creative-agent/SKILL.md +221 -0
  97. package/skills/build-generative-seller-agent/SKILL.md +288 -0
  98. package/skills/build-retail-media-agent/SKILL.md +237 -0
  99. package/skills/build-seller-agent/SKILL.md +313 -0
  100. package/skills/build-signals-agent/SKILL.md +203 -0
  101. package/storyboards/campaign_governance_conditions.yaml +2 -2
  102. package/storyboards/campaign_governance_delivery.yaml +1 -1
  103. package/storyboards/campaign_governance_denied.yaml +1 -0
  104. package/storyboards/creative_generative.yaml +317 -0
  105. package/storyboards/creative_sales_agent.yaml +2 -2
  106. package/storyboards/creative_template.yaml +2 -1
  107. package/storyboards/deterministic_testing.yaml +271 -245
  108. package/storyboards/media_buy_catalog_creative.yaml +2 -1
  109. package/storyboards/media_buy_generative_seller.yaml +581 -0
  110. package/storyboards/media_buy_governance_escalation.yaml +1 -1
  111. package/storyboards/media_buy_guaranteed_approval.yaml +14 -14
  112. package/storyboards/media_buy_non_guaranteed.yaml +2 -2
  113. package/storyboards/media_buy_proposal_mode.yaml +5 -5
  114. package/storyboards/media_buy_seller.yaml +10 -10
  115. package/storyboards/media_buy_state_machine.yaml +4 -4
  116. package/storyboards/schema.yaml +2 -1
  117. package/storyboards/signal_marketplace.yaml +3 -0
  118. package/storyboards/signal_owned.yaml +1 -0
  119. package/storyboards/test-kits/acme-outdoor.yaml +118 -0
  120. 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: confirmed or active
163
+ - status: active
164
164
  - governance_context: echoed back confirming governance was validated
165
- - packages: confirmed line items
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: "media_buys"
167
+ path: "media_buy_deliveries"
168
168
  description: "Response contains media buy delivery data"
169
169
 
170
170
  - id: drift_recheck
@@ -129,6 +129,7 @@ phases:
129
129
  description: "Response contains a governance decision"
130
130
  - check: field_value
131
131
  path: "decision"
132
+ value: "denied"
132
133
  description: "Decision is denied"
133
134
  - check: field_present
134
135
  path: "findings"