@adcp/sdk 6.7.0 → 6.8.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 (166) hide show
  1. package/bin/adcp.js +15 -3
  2. package/dist/lib/adapters/derived-account-store.d.ts +152 -0
  3. package/dist/lib/adapters/derived-account-store.d.ts.map +1 -0
  4. package/dist/lib/adapters/derived-account-store.js +135 -0
  5. package/dist/lib/adapters/derived-account-store.js.map +1 -0
  6. package/dist/lib/adapters/implicit-account-store.d.ts +3 -1
  7. package/dist/lib/adapters/implicit-account-store.d.ts.map +1 -1
  8. package/dist/lib/adapters/implicit-account-store.js +3 -1
  9. package/dist/lib/adapters/implicit-account-store.js.map +1 -1
  10. package/dist/lib/adapters/index.d.ts +1 -0
  11. package/dist/lib/adapters/index.d.ts.map +1 -1
  12. package/dist/lib/adapters/index.js +7 -1
  13. package/dist/lib/adapters/index.js.map +1 -1
  14. package/dist/lib/adapters/oauth-passthrough-resolver.d.ts +3 -1
  15. package/dist/lib/adapters/oauth-passthrough-resolver.d.ts.map +1 -1
  16. package/dist/lib/adapters/oauth-passthrough-resolver.js +3 -1
  17. package/dist/lib/adapters/oauth-passthrough-resolver.js.map +1 -1
  18. package/dist/lib/adapters/roster-account-store.d.ts +85 -24
  19. package/dist/lib/adapters/roster-account-store.d.ts.map +1 -1
  20. package/dist/lib/adapters/roster-account-store.js +52 -30
  21. package/dist/lib/adapters/roster-account-store.js.map +1 -1
  22. package/dist/lib/index.d.ts +3 -3
  23. package/dist/lib/index.d.ts.map +1 -1
  24. package/dist/lib/index.js +13 -8
  25. package/dist/lib/index.js.map +1 -1
  26. package/dist/lib/mock-server/creative-ad-server/seed-data.d.ts +81 -0
  27. package/dist/lib/mock-server/creative-ad-server/seed-data.d.ts.map +1 -0
  28. package/dist/lib/mock-server/creative-ad-server/seed-data.js +200 -0
  29. package/dist/lib/mock-server/creative-ad-server/seed-data.js.map +1 -0
  30. package/dist/lib/mock-server/creative-ad-server/server.d.ts +39 -0
  31. package/dist/lib/mock-server/creative-ad-server/server.d.ts.map +1 -0
  32. package/dist/lib/mock-server/creative-ad-server/server.js +618 -0
  33. package/dist/lib/mock-server/creative-ad-server/server.js.map +1 -0
  34. package/dist/lib/mock-server/index.d.ts.map +1 -1
  35. package/dist/lib/mock-server/index.js +180 -24
  36. package/dist/lib/mock-server/index.js.map +1 -1
  37. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.d.ts +66 -0
  38. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.d.ts.map +1 -0
  39. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.js +193 -0
  40. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.js.map +1 -0
  41. package/dist/lib/mock-server/sales-non-guaranteed/server.d.ts +33 -0
  42. package/dist/lib/mock-server/sales-non-guaranteed/server.d.ts.map +1 -0
  43. package/dist/lib/mock-server/sales-non-guaranteed/server.js +782 -0
  44. package/dist/lib/mock-server/sales-non-guaranteed/server.js.map +1 -0
  45. package/dist/lib/mock-server/sponsored-intelligence/seed-data.d.ts +50 -0
  46. package/dist/lib/mock-server/sponsored-intelligence/seed-data.d.ts.map +1 -0
  47. package/dist/lib/mock-server/sponsored-intelligence/seed-data.js +133 -0
  48. package/dist/lib/mock-server/sponsored-intelligence/seed-data.js.map +1 -0
  49. package/dist/lib/mock-server/sponsored-intelligence/server.d.ts +13 -0
  50. package/dist/lib/mock-server/sponsored-intelligence/server.d.ts.map +1 -0
  51. package/dist/lib/mock-server/sponsored-intelligence/server.js +609 -0
  52. package/dist/lib/mock-server/sponsored-intelligence/server.js.map +1 -0
  53. package/dist/lib/protocols/mcp.d.ts.map +1 -1
  54. package/dist/lib/protocols/mcp.js +1 -41
  55. package/dist/lib/protocols/mcp.js.map +1 -1
  56. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  57. package/dist/lib/server/account-mode.d.ts +113 -0
  58. package/dist/lib/server/account-mode.d.ts.map +1 -0
  59. package/dist/lib/server/account-mode.js +125 -0
  60. package/dist/lib/server/account-mode.js.map +1 -0
  61. package/dist/lib/server/adcp-server.js +41 -0
  62. package/dist/lib/server/adcp-server.js.map +1 -1
  63. package/dist/lib/server/auth.d.ts +35 -0
  64. package/dist/lib/server/auth.d.ts.map +1 -1
  65. package/dist/lib/server/auth.js.map +1 -1
  66. package/dist/lib/server/create-adcp-server.d.ts +26 -9
  67. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  68. package/dist/lib/server/create-adcp-server.js +46 -20
  69. package/dist/lib/server/create-adcp-server.js.map +1 -1
  70. package/dist/lib/server/ctx-metadata/store.d.ts +1 -1
  71. package/dist/lib/server/ctx-metadata/store.d.ts.map +1 -1
  72. package/dist/lib/server/ctx-metadata/store.js +1 -0
  73. package/dist/lib/server/ctx-metadata/store.js.map +1 -1
  74. package/dist/lib/server/decisioning/account.d.ts +5 -0
  75. package/dist/lib/server/decisioning/account.d.ts.map +1 -1
  76. package/dist/lib/server/decisioning/account.js.map +1 -1
  77. package/dist/lib/server/decisioning/buyer-agent.d.ts +37 -4
  78. package/dist/lib/server/decisioning/buyer-agent.d.ts.map +1 -1
  79. package/dist/lib/server/decisioning/buyer-agent.js +12 -2
  80. package/dist/lib/server/decisioning/buyer-agent.js.map +1 -1
  81. package/dist/lib/server/decisioning/compose.d.ts +33 -2
  82. package/dist/lib/server/decisioning/compose.d.ts.map +1 -1
  83. package/dist/lib/server/decisioning/compose.js +13 -46
  84. package/dist/lib/server/decisioning/compose.js.map +1 -1
  85. package/dist/lib/server/decisioning/index.d.ts +2 -1
  86. package/dist/lib/server/decisioning/index.d.ts.map +1 -1
  87. package/dist/lib/server/decisioning/index.js +2 -1
  88. package/dist/lib/server/decisioning/index.js.map +1 -1
  89. package/dist/lib/server/decisioning/platform-helpers.d.ts +18 -0
  90. package/dist/lib/server/decisioning/platform-helpers.d.ts.map +1 -1
  91. package/dist/lib/server/decisioning/platform-helpers.js +20 -0
  92. package/dist/lib/server/decisioning/platform-helpers.js.map +1 -1
  93. package/dist/lib/server/decisioning/platform.d.ts +19 -21
  94. package/dist/lib/server/decisioning/platform.d.ts.map +1 -1
  95. package/dist/lib/server/decisioning/platform.js.map +1 -1
  96. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  97. package/dist/lib/server/decisioning/runtime/from-platform.js +334 -44
  98. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  99. package/dist/lib/server/decisioning/runtime/observed-modes.d.ts +40 -0
  100. package/dist/lib/server/decisioning/runtime/observed-modes.d.ts.map +1 -0
  101. package/dist/lib/server/decisioning/runtime/observed-modes.js +82 -0
  102. package/dist/lib/server/decisioning/runtime/observed-modes.js.map +1 -0
  103. package/dist/lib/server/decisioning/runtime/protocol-for-tool.js +2 -2
  104. package/dist/lib/server/decisioning/runtime/protocol-for-tool.js.map +1 -1
  105. package/dist/lib/server/decisioning/runtime/validate-platform.d.ts.map +1 -1
  106. package/dist/lib/server/decisioning/runtime/validate-platform.js +9 -1
  107. package/dist/lib/server/decisioning/runtime/validate-platform.js.map +1 -1
  108. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.d.ts +125 -0
  109. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.d.ts.map +1 -0
  110. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.js +52 -0
  111. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.js.map +1 -0
  112. package/dist/lib/server/decisioning/tenant-registry.d.ts +16 -0
  113. package/dist/lib/server/decisioning/tenant-registry.d.ts.map +1 -1
  114. package/dist/lib/server/decisioning/tenant-registry.js.map +1 -1
  115. package/dist/lib/server/index.d.ts +4 -1
  116. package/dist/lib/server/index.d.ts.map +1 -1
  117. package/dist/lib/server/index.js +9 -3
  118. package/dist/lib/server/index.js.map +1 -1
  119. package/dist/lib/server/serve.js +20 -2
  120. package/dist/lib/server/serve.js.map +1 -1
  121. package/dist/lib/server/test-controller.d.ts +3 -0
  122. package/dist/lib/server/test-controller.d.ts.map +1 -1
  123. package/dist/lib/server/test-controller.js +23 -20
  124. package/dist/lib/server/test-controller.js.map +1 -1
  125. package/dist/lib/testing/comply-controller.d.ts +23 -2
  126. package/dist/lib/testing/comply-controller.d.ts.map +1 -1
  127. package/dist/lib/testing/comply-controller.js +19 -2
  128. package/dist/lib/testing/comply-controller.js.map +1 -1
  129. package/dist/lib/testing/index.d.ts +1 -1
  130. package/dist/lib/testing/index.d.ts.map +1 -1
  131. package/dist/lib/testing/index.js.map +1 -1
  132. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  133. package/dist/lib/testing/storyboard/validations.js +36 -54
  134. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  135. package/dist/lib/testing/test-controller.d.ts +10 -4
  136. package/dist/lib/testing/test-controller.d.ts.map +1 -1
  137. package/dist/lib/testing/test-controller.js +9 -3
  138. package/dist/lib/testing/test-controller.js.map +1 -1
  139. package/dist/lib/types/index.d.ts +3 -2
  140. package/dist/lib/types/index.d.ts.map +1 -1
  141. package/dist/lib/types/index.js +3 -0
  142. package/dist/lib/types/index.js.map +1 -1
  143. package/dist/lib/utils/glob.d.ts +4 -2
  144. package/dist/lib/utils/glob.d.ts.map +1 -1
  145. package/dist/lib/utils/glob.js +4 -2
  146. package/dist/lib/utils/glob.js.map +1 -1
  147. package/dist/lib/version.d.ts +3 -3
  148. package/dist/lib/version.js +3 -3
  149. package/docs/llms.txt +2 -2
  150. package/examples/README.md +29 -13
  151. package/examples/hello-cluster.ts +62 -23
  152. package/examples/hello_creative_adapter_ad_server.ts +790 -0
  153. package/examples/hello_seller_adapter_guaranteed.ts +80 -22
  154. package/examples/hello_seller_adapter_non_guaranteed.ts +1020 -0
  155. package/examples/hello_si_adapter_brand.ts +572 -0
  156. package/package.json +3 -2
  157. package/skills/build-creative-agent/SKILL.md +103 -183
  158. package/skills/build-generative-seller-agent/SKILL.md +15 -9
  159. package/skills/build-governance-agent/SKILL.md +20 -11
  160. package/skills/build-retail-media-agent/SKILL.md +10 -8
  161. package/skills/build-seller-agent/SKILL.md +15 -13
  162. package/skills/build-seller-agent/specialisms/sales-non-guaranteed.md +9 -31
  163. package/skills/build-si-agent/SKILL.md +251 -196
  164. package/skills/build-signals-agent/SKILL.md +2 -0
  165. package/skills/call-adcp-agent/SKILL.md +7 -1
  166. package/skills/call-adcp-agent.previous/SKILL.md +0 -261
@@ -23,6 +23,8 @@ A creative agent manages the creative lifecycle: accepts assets from buyers, sto
23
23
  - Selling inventory (no creative management) → `skills/build-seller-agent/`
24
24
  - Serving audience segments → `skills/build-signals-agent/`
25
25
 
26
+ **Often claimed alongside:** [`creative-generative`](../build-generative-seller-agent/SKILL.md) (hybrid creative platform — template-driven + AI-generated ads share the same library), [`sales-catalog-driven`](../build-retail-media-agent/SKILL.md) (retail-media dynamic-creative rendering). See [Common multi-specialism bundles](../../examples/README.md#common-multi-specialism-bundles).
27
+
26
28
  ## Specialisms This Skill Covers
27
29
 
28
30
  Creative specialisms are three distinct archetypes with materially different tool contracts. Pick the one that matches your platform — do not try to make one handler cover all three.
@@ -115,6 +117,7 @@ What happens when a creative is synced:
115
117
  > accepted_media_types: ['image/jpeg', 'image/png'],
116
118
  > }] }
117
119
  > ```
120
+ >
118
121
  > - `build_creative` response is `{ creative_manifest: { format_id, assets } }` (single) or `{ creative_manifests: [...] }` (multi). Platform-native fields at the top level (`tag_url`, `creative_id`, `media_type`) are **invalid** — use `buildCreativeResponse({ creative_manifest })` / `buildCreativeMultiResponse({ creative_manifests })` from `@adcp/sdk/server` to lock the shape at compile time.
119
122
  > - Each asset in `creative_manifest.assets` requires an `asset_type` discriminator. Use the typed factories (`imageAsset`, `videoAsset`, `audioAsset`, `htmlAsset`, `urlAsset`, `textAsset`) so the discriminator is injected for you; a plain `{ serving_tag: { content: '<vast>...' } }` fails validation.
120
123
  > - `preview_creative` renders have the same pattern — each `renders[]` entry is a oneOf on `output_format`. Use `urlRender({...})`, `htmlRender({...})`, or `bothRender({...})` to inject the discriminator and require the matching `preview_url` / `preview_html` field automatically.
@@ -124,14 +127,14 @@ What happens when a creative is synced:
124
127
 
125
128
  **Handler bindings — read the Contract column entry before writing each return:**
126
129
 
127
- | Tool | Handler | Contract (field list) | Gotchas |
128
- | ----------------------- | ------------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
129
- | `get_adcp_capabilities` | auto-generated | n/a | Do not register manually. |
130
+ | Tool | Handler | Contract (field list) | Gotchas |
131
+ | ----------------------- | ------------------------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
+ | `get_adcp_capabilities` | auto-generated | n/a | Do not register manually. |
130
133
  | `list_creative_formats` | `creative.listCreativeFormats` | [`#list_creative_formats`](../../docs/llms.txt#list_creative_formats) | Each `renders[]` entry MUST have `role` + exactly one of `dimensions` (object) OR `parameters_from_format_id: true` — `{width, height}` shorthand fails. Each `assets[]` entry MUST have `item_type: 'individual' as const` + `asset_id` + `asset_type` (image/video/audio/text/click_url/html) + `required: boolean`. See the strict-shape callout above the table. |
131
- | `sync_creatives` | `creative.syncCreatives` | [`#sync_creatives`](../../docs/llms.txt#sync_creatives) | Echo `creative_id`; `action` ∈ `created \| updated \| unchanged \| failed \| deleted`. |
132
- | `list_creatives` | `creative.listCreatives` | [`#list_creatives`](../../docs/llms.txt#list_creatives) | Honor `args.filters?.format_ids` when present. `created_date` + `updated_date` on each row are required ISO timestamps. |
133
- | `preview_creative` | `creative.previewCreative` | [`#preview_creative`](../../docs/llms.txt#preview_creative) | `renders[].output_format` is a discriminator — use `urlRender({...})`, `htmlRender({...})`, or `bothRender({...})` so the discriminator is injected and the required `preview_url`/`preview_html` field is enforced at compile time. |
134
- | `build_creative` | `creative.buildCreative` | [`#build_creative`](../../docs/llms.txt#build_creative) | Check `args.target_format_id` → library lookup; fall back to `args.creative_id`. Response requires `creative_manifest.format_id` + `creative_manifest.assets`. |
134
+ | `sync_creatives` | `creative.syncCreatives` | [`#sync_creatives`](../../docs/llms.txt#sync_creatives) | Echo `creative_id`; `action` ∈ `created \| updated \| unchanged \| failed \| deleted`. |
135
+ | `list_creatives` | `creative.listCreatives` | [`#list_creatives`](../../docs/llms.txt#list_creatives) | Honor `args.filters?.format_ids` when present. `created_date` + `updated_date` on each row are required ISO timestamps. |
136
+ | `preview_creative` | `creative.previewCreative` | [`#preview_creative`](../../docs/llms.txt#preview_creative) | `renders[].output_format` is a discriminator — use `urlRender({...})`, `htmlRender({...})`, or `bothRender({...})` so the discriminator is injected and the required `preview_url`/`preview_html` field is enforced at compile time. |
137
+ | `build_creative` | `creative.buildCreative` | [`#build_creative`](../../docs/llms.txt#build_creative) | Check `args.target_format_id` → library lookup; fall back to `args.creative_id`. Response requires `creative_manifest.format_id` + `creative_manifest.assets`. |
135
138
 
136
139
  Asset values use type-specific shapes, not a generic `asset_type` discriminator:
137
140
 
@@ -151,21 +154,21 @@ Some schemas also define an `ext` field for vendor-namespaced extensions. If you
151
154
 
152
155
  ## SDK Quick Reference
153
156
 
154
- | SDK piece | Usage |
155
- | ------------------------------------------------------- | ---------------------------------------------------------------- |
156
- | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, auto-capabilities |
157
- | `createAdcpServer(config)` *(legacy)* | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
158
- | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
159
- | `creative: { listCreativeFormats, syncCreatives, ... }` | Domain group — register handlers by name |
160
- | `ctx.store.put(collection, id, data)` | Persist state (creative library) across requests |
161
- | `ctx.store.get(collection, id)` | Retrieve persisted state |
162
- | `ctx.store.list(collection)` | List all items in a collection (for `list_creatives`) |
163
- | `listCreativeFormatsResponse(data)` | Auto-applied response builder (don't call manually) |
164
- | `syncCreativesResponse(data)` | Auto-applied response builder (don't call manually) |
165
- | `listCreativesResponse(data)` | Auto-applied response builder (don't call manually) |
166
- | `buildCreativeResponse(data)` | Auto-applied response builder (don't call manually) |
167
- | `previewCreativeResponse(data)` | Auto-applied response builder (don't call manually) |
168
- | `adcpError(code, { message })` | Structured error |
157
+ | SDK piece | Usage |
158
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
159
+ | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, auto-capabilities |
160
+ | `createAdcpServer(config)` _(legacy)_ | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
161
+ | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
162
+ | `creative: { listCreativeFormats, syncCreatives, ... }` | Domain group — register handlers by name |
163
+ | `ctx.store.put(collection, id, data)` | Persist state (creative library) across requests |
164
+ | `ctx.store.get(collection, id)` | Retrieve persisted state |
165
+ | `ctx.store.list(collection)` | List all items in a collection (for `list_creatives`) |
166
+ | `listCreativeFormatsResponse(data)` | Auto-applied response builder (don't call manually) |
167
+ | `syncCreativesResponse(data)` | Auto-applied response builder (don't call manually) |
168
+ | `listCreativesResponse(data)` | Auto-applied response builder (don't call manually) |
169
+ | `buildCreativeResponse(data)` | Auto-applied response builder (don't call manually) |
170
+ | `previewCreativeResponse(data)` | Auto-applied response builder (don't call manually) |
171
+ | `adcpError(code, { message })` | Structured error |
169
172
 
170
173
  Import: `import { createAdcpServerFromPlatform, serve, adcpError } from '@adcp/sdk/server';`
171
174
 
@@ -268,63 +271,63 @@ class MyCreative implements DecisioningPlatform {
268
271
 
269
272
  creative: CreativeAdServerPlatform = {
270
273
  listCreativeFormats: async (params, ctx) => {
271
- return { formats };
272
- },
274
+ return { formats };
275
+ },
273
276
 
274
- syncCreatives: async (params, ctx) => {
275
- const results = [];
276
- for (const creative of params.creatives) {
277
- const now = new Date().toISOString();
278
- const existing = await ctx.store.get('creatives', creative.creative_id);
279
- await ctx.store.put('creatives', creative.creative_id, {
280
- ...creative,
281
- status: 'approved',
282
- created_date: existing?.created_date ?? now,
283
- updated_date: now,
284
- });
285
- results.push({
286
- creative_id: creative.creative_id,
287
- action: existing ? ('updated' as const) : ('created' as const),
288
- });
289
- }
290
- return { creatives: results };
291
- },
277
+ syncCreatives: async (params, ctx) => {
278
+ const results = [];
279
+ for (const creative of params.creatives) {
280
+ const now = new Date().toISOString();
281
+ const existing = await ctx.store.get('creatives', creative.creative_id);
282
+ await ctx.store.put('creatives', creative.creative_id, {
283
+ ...creative,
284
+ status: 'approved',
285
+ created_date: existing?.created_date ?? now,
286
+ updated_date: now,
287
+ });
288
+ results.push({
289
+ creative_id: creative.creative_id,
290
+ action: existing ? ('updated' as const) : ('created' as const),
291
+ });
292
+ }
293
+ return { creatives: results };
294
+ },
292
295
 
293
- listCreatives: async (params, ctx) => {
294
- // `ctx.store.list` returns `{ items, nextCursor? }` — destructure.
295
- let { items: creatives } = await ctx.store.list('creatives');
296
- if (params.filters?.format_ids) {
297
- creatives = creatives.filter(c => params.filters!.format_ids!.some(fid => fid.id === c.format_id?.id));
298
- }
299
- return {
300
- query_summary: { total_matching: creatives.length, returned: creatives.length, filters_applied: [] },
301
- creatives,
302
- pagination: { has_more: false },
303
- };
304
- },
296
+ listCreatives: async (params, ctx) => {
297
+ // `ctx.store.list` returns `{ items, nextCursor? }` — destructure.
298
+ let { items: creatives } = await ctx.store.list('creatives');
299
+ if (params.filters?.format_ids) {
300
+ creatives = creatives.filter(c => params.filters!.format_ids!.some(fid => fid.id === c.format_id?.id));
301
+ }
302
+ return {
303
+ query_summary: { total_matching: creatives.length, returned: creatives.length, filters_applied: [] },
304
+ creatives,
305
+ pagination: { has_more: false },
306
+ };
307
+ },
305
308
 
306
- buildCreative: async (params, ctx) => {
307
- // `ctx.store.list` returns `{ items, nextCursor? }` — destructure.
308
- // Calling `.find`/`.map` on the raw result throws `TypeError` and
309
- // the dispatcher wraps it as `SERVICE_UNAVAILABLE`.
310
- const { items: creatives } = await ctx.store.list('creatives');
311
- const match = params.target_format_id
312
- ? creatives.find(c => c.format_id?.id === params.target_format_id!.id)
313
- : params.creative_id
314
- ? await ctx.store.get('creatives', params.creative_id)
315
- : null;
316
- // Return structured errors — don't throw. The dispatcher now unwraps
317
- // thrown envelopes, but throwing still releases the idempotency claim
318
- // before the throw is caught. If your handler mutated state before
319
- // throwing, a retry with the same key re-executes the write. Returning
320
- // an error envelope has the same claim-release semantics, so the rule
321
- // holds for both paths: mutate last, or don't mutate at all on error.
322
- if (!match) return adcpError('CREATIVE_NOT_FOUND', { message: 'No matching creative' });
323
- return {
324
- creative_manifest: { format_id: match.format_id, assets: match.assets ?? {} },
325
- sandbox: true,
326
- };
327
- },
309
+ buildCreative: async (params, ctx) => {
310
+ // `ctx.store.list` returns `{ items, nextCursor? }` — destructure.
311
+ // Calling `.find`/`.map` on the raw result throws `TypeError` and
312
+ // the dispatcher wraps it as `SERVICE_UNAVAILABLE`.
313
+ const { items: creatives } = await ctx.store.list('creatives');
314
+ const match = params.target_format_id
315
+ ? creatives.find(c => c.format_id?.id === params.target_format_id!.id)
316
+ : params.creative_id
317
+ ? await ctx.store.get('creatives', params.creative_id)
318
+ : null;
319
+ // Return structured errors — don't throw. The dispatcher now unwraps
320
+ // thrown envelopes, but throwing still releases the idempotency claim
321
+ // before the throw is caught. If your handler mutated state before
322
+ // throwing, a retry with the same key re-executes the write. Returning
323
+ // an error envelope has the same claim-release semantics, so the rule
324
+ // holds for both paths: mutate last, or don't mutate at all on error.
325
+ if (!match) return adcpError('CREATIVE_NOT_FOUND', { message: 'No matching creative' });
326
+ return {
327
+ creative_manifest: { format_id: match.format_id, assets: match.assets ?? {} },
328
+ sandbox: true,
329
+ };
330
+ },
328
331
 
329
332
  previewCreative: async params => {
330
333
  return {
@@ -560,22 +563,22 @@ Common failure decoder:
560
563
 
561
564
  ## Common Mistakes
562
565
 
563
- | Mistake | Fix |
564
- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
565
- | Using `createTaskCapableServer` + `server.tool()` | Use `createAdcpServerFromPlatform` with a typed `creative: CreativeAdServerPlatform` (or `CreativeBuilderPlatform`) field |
566
+ | Mistake | Fix |
567
+ | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
568
+ | Using `createTaskCapableServer` + `server.tool()` | Use `createAdcpServerFromPlatform` with a typed `creative: CreativeAdServerPlatform` (or `CreativeBuilderPlatform`) field |
566
569
  | Calling `createAdcpServer` directly in new code | Reach for `createAdcpServerFromPlatform`; `createAdcpServer` lives at `@adcp/sdk/server/legacy/v5` for mid-migration / escape-hatch only |
567
- | Manually registering `get_adcp_capabilities` | Auto-generated by `createAdcpServerFromPlatform` from your typed `DecisioningPlatform` — do not register it |
568
- | Calling `server.registerTool('preview_creative', ...)` | `AdcpServer` does not expose `registerTool` — put `previewCreative` in the `creative:` domain group like the other handlers |
569
- | Using module-level Maps for state | Use `ctx.store.put/get/list` — framework provides `InMemoryStateStore` by default |
570
- | Calling response builders manually in domain handlers | Handlers return raw data — response builders are auto-applied across the `creative:` group, including `preview_creative` |
571
- | `list_creatives` ignores format filter | Check `args.filters?.format_ids` and filter results |
572
- | `preview_creative` returns wrong response_type | Must be `'single'` for single creative previews |
573
- | `preview_creative` looks up by creative_id | Preview the `creative_manifest` from the request — no library lookup needed |
574
- | `build_creative` looks up by `args.creative_id` only | Storyboard sends `target_format_id` — find a synced creative matching that format |
575
- | `build_creative` missing creative_manifest | Required field — contains the built output |
576
- | `creative_manifest` includes `name` field | `CreativeManifest` has no `name` — only `format_id` and `assets` |
577
- | HTML asset uses `{ html: '...' }` | Use `{ content: '...' }` — the schema field is `content`, not `html` |
578
- | format_ids in list_creative_formats don't match what sellers reference | Sellers include your format_ids in their products — if the buyer can't look them up via list_creative_formats, creative sync breaks |
570
+ | Manually registering `get_adcp_capabilities` | Auto-generated by `createAdcpServerFromPlatform` from your typed `DecisioningPlatform` — do not register it |
571
+ | Calling `server.registerTool('preview_creative', ...)` | `AdcpServer` does not expose `registerTool` — put `previewCreative` in the `creative:` domain group like the other handlers |
572
+ | Using module-level Maps for state | Use `ctx.store.put/get/list` — framework provides `InMemoryStateStore` by default |
573
+ | Calling response builders manually in domain handlers | Handlers return raw data — response builders are auto-applied across the `creative:` group, including `preview_creative` |
574
+ | `list_creatives` ignores format filter | Check `args.filters?.format_ids` and filter results |
575
+ | `preview_creative` returns wrong response_type | Must be `'single'` for single creative previews |
576
+ | `preview_creative` looks up by creative_id | Preview the `creative_manifest` from the request — no library lookup needed |
577
+ | `build_creative` looks up by `args.creative_id` only | Storyboard sends `target_format_id` — find a synced creative matching that format |
578
+ | `build_creative` missing creative_manifest | Required field — contains the built output |
579
+ | `creative_manifest` includes `name` field | `CreativeManifest` has no `name` — only `format_id` and `assets` |
580
+ | HTML asset uses `{ html: '...' }` | Use `{ content: '...' }` — the schema field is `content`, not `html` |
581
+ | format_ids in list_creative_formats don't match what sellers reference | Sellers include your format_ids in their products — if the buyer can't look them up via list_creative_formats, creative sync breaks |
579
582
 
580
583
  ## Storyboards
581
584
 
@@ -592,101 +595,18 @@ Common failure decoder:
592
595
 
593
596
  Storyboard: `creative_ad_server`. Stateful — the library is pre-loaded; buyers do **not** push assets via `sync_creatives` at runtime. Pricing and billing round-trips are first-class.
594
597
 
595
- **`list_creatives`** with `include_pricing=true` returns per-creative `pricing_options`:
596
-
597
- ```typescript
598
- listCreatives: async (params, ctx) => {
599
- const { items } = await ctx.store.list('creatives');
600
- const creatives = items.map((c) => ({
601
- creative_id: c.creative_id,
602
- name: c.name,
603
- format_id: c.format_id,
604
- status: 'approved' as const,
605
- created_date: c.created_date,
606
- updated_date: c.updated_date,
607
- ...(params.include_pricing && {
608
- pricing_options: [{
609
- pricing_option_id: 'standard_cpm',
610
- model: 'cpm' as const,
611
- cpm: 2.5,
612
- currency: 'USD',
613
- }],
614
- }),
615
- }));
616
- return {
617
- query_summary: { total_matching: creatives.length, returned: creatives.length, filters_applied: [] },
618
- creatives,
619
- pagination: { has_more: false },
620
- };
621
- },
622
- ```
598
+ **Fork target**: [`examples/hello_creative_adapter_ad_server.ts`](../../examples/hello_creative_adapter_ad_server.ts) is the worked, passing reference adapter for this specialism. CI gates strict tsc + storyboard pass + upstream-traffic façade — all 7 storyboard steps pass on the worked build.
623
599
 
624
- **`build_creative`** receives `media_buy_id`, `package_id`, `target_format_id`, and `pricing_option_id`. Return a VAST tag with macro placeholders, plus `vendor_cost` at CPM = 0 (billing happens via `report_usage`):
600
+ The adapter demonstrates the stateful-library deltas vs `creative-template`:
625
601
 
626
- ```typescript
627
- buildCreative: async (params, ctx) => {
628
- const creative = await lookupCreativeForFormat(params.target_format_id, ctx);
629
- const vast = `<?xml version="1.0"?>
630
- <VAST version="4.2">
631
- <Ad id="${creative.creative_id}"><InLine>
632
- <Impression><![CDATA[https://adserver.example/imp?cb=[CACHEBUSTER]&mb=${params.media_buy_id}]]></Impression>
633
- <Creatives><Creative>
634
- <Linear><Duration>00:00:30</Duration>
635
- <MediaFiles><MediaFile type="video/mp4"><![CDATA[${creative.video_url}]]></MediaFile></MediaFiles>
636
- <VideoClicks><ClickThrough><![CDATA[[CLICK_URL]https://landing.example]]></ClickThrough></VideoClicks>
637
- </Linear>
638
- </Creative></Creatives>
639
- </InLine></Ad>
640
- </VAST>`;
641
-
642
- return {
643
- creative_manifest: {
644
- format_id: params.target_format_id,
645
- assets: { serving_tag: { content: vast } }, // HTML asset shape: { content: string }
646
- },
647
- pricing_option_id: params.pricing_option_id, // echo
648
- vendor_cost: { amount: 0, currency: 'USD' }, // CPM at build time — billing is separate
649
- sandbox: true,
650
- };
651
- },
652
- ```
653
-
654
- **`report_usage`** is the billing reconciliation tool. Validate `idempotency_key` (return the same response for the same key) and echo `pricing_option_id` + `reporting_period`:
655
-
656
- ```typescript
657
- reportUsage: async (params, ctx) => {
658
- const existing = await ctx.store.get('usage_reports', params.idempotency_key);
659
- if (existing) return existing; // idempotent
660
- const report = {
661
- idempotency_key: params.idempotency_key,
662
- creative_id: params.creative_id,
663
- pricing_option_id: params.pricing_option_id,
664
- reporting_period: params.reporting_period,
665
- billable_amount: { amount: params.impressions * 2.5 / 1000, currency: 'USD' },
666
- status: 'accepted' as const,
667
- };
668
- await ctx.store.put('usage_reports', params.idempotency_key, report);
669
- return report;
670
- },
671
- ```
672
-
673
- **`get_creative_delivery`** returns impressions/spend, optionally broken down by variant and filtered by `media_buy_ids`:
674
-
675
- ```typescript
676
- getCreativeDelivery: async (params, ctx) => ({
677
- reporting_period: params.reporting_period,
678
- currency: 'USD', // required top-level per get-creative-delivery-response.json
679
- creatives: (params.creative_ids ?? []).map((id) => ({
680
- creative_id: id,
681
- impressions: 12500,
682
- spend: { amount: 31.25, currency: 'USD' },
683
- by_variant: [],
684
- })),
685
- sandbox: true,
686
- }),
687
- ```
602
+ - **Library shape** — `POST /v1/creatives` writes, `GET /v1/creatives` reads (cursor pagination + multi-id pass-through), `PATCH` mutates. Maps directly onto `CreativeAdServerPlatform.{syncCreatives, listCreatives}`. Library state is owned by the adopter's UI / API ingestion; the comply controller's `seed.creative` is the only test path that writes via the adapter.
603
+ - **Tag generation** — `buildCreative` looks up the creative by id, calls upstream `POST /v1/creatives/{id}/render` for macro substitution against a stored snippet template (`{click_url}`, `{impression_pixel}`, `{cb}`, `{advertiser_id}`, `{creative_id}`, `{asset_url}` …), and returns `BuildCreativeSuccess` with `creative_manifest.assets` carrying the rendered tag (HTML / VAST / native). The mock's `tag_url` points at a real iframe-embeddable `/serve/{id}` endpoint adopters can iframe in storyboards.
604
+ - **`previewCreative`** `NoAccountCtx<TCtxMeta>` no-account tool; returns the same iframe URL as a `preview_url`. The resolver synthesizes a default-listing network so `ctx.account` resolves cleanly inside the no-account handler. See migration recipe #11.
605
+ - **`listCreativeFormats`** also `NoAccountCtx`. Project upstream catalog to closed-shape `Format.renders[]` (display fixed dims, video/CTV at 1080p baseline, parameterized with `accepts_parameters[]`) per the typed builders from `@adcp/sdk` (#1325).
606
+ - **Per-creative pricing** — when the adopter bills through AdCP, `listCreatives` with `include_pricing=true` surfaces `pricing_options`, `buildCreative` echoes the applied `pricing_option_id`, and `reportUsage` closes the loop. Adopters that bill out of band (CM360-style flat license / SaaS contract) omit these fields and surface `report_usage` records as `not_accepted` rather than fake-accepting.
607
+ - **`getCreativeDelivery`** — multi-id pass-through per #1342 + #1410; required top-level `currency` + `reporting_period` per the response schema.
688
608
 
689
- Output formats returned by `list_creative_formats` for ad servers are **serving-tag formats** (VAST 4.2, display tag HTML, native JSON payload), not input visual formats.
609
+ Output formats returned by `list_creative_formats` for ad servers are **serving-tag formats** (VAST 4.2, display tag HTML, native JSON payload), not input visual formats. Replace the `// SWAP:` markers with calls to your real backend.
690
610
 
691
611
  ### <a name="specialism-creative-template"></a>creative-template
692
612
 
@@ -23,6 +23,8 @@ A generative seller that sells programmatic inventory MUST also accept standard
23
23
  - Standalone creative agent (renders but doesn't sell) → creative agent
24
24
  - Signals/audience data → `skills/build-signals-agent/`
25
25
 
26
+ **Often claimed alongside:** [`creative-template`](../build-creative-agent/SKILL.md) — hybrid creative platforms (Celtra, Bannerflow) claim both; template-driven and AI-driven generation share the same creative library. See [Common multi-specialism bundles](../../examples/README.md#common-multi-specialism-bundles).
27
+
26
28
  ## Specialisms This Skill Covers
27
29
 
28
30
  A generative seller inherits every sales specialism it supports (usually `sales-non-guaranteed`, optionally `sales-catalog-driven`) **plus** `creative-generative`. Declare all three in your `get_adcp_capabilities` response so buyers can filter correctly.
@@ -349,14 +351,14 @@ Validate with: `adcp storyboard run <agent> deterministic_testing --json`
349
351
 
350
352
  ## SDK Quick Reference
351
353
 
352
- | SDK piece | Usage |
353
- | --------------------------------------- | ----------------------------------------------------------------------- |
354
- | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, auto-generated capabilities, ctx_metadata round-trip |
355
- | `createAdcpServer(config)` *(legacy)* | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
356
- | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
357
- | `ctx.store` | State persistence — `get/put/patch/delete/list` domain objects |
358
- | `adcpError(code, { message })` | Structured error |
359
- | `registerTestController(server, store)` | Add `comply_test_controller` for deterministic testing |
354
+ | SDK piece | Usage |
355
+ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
356
+ | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, auto-generated capabilities, ctx_metadata round-trip |
357
+ | `createAdcpServer(config)` _(legacy)_ | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
358
+ | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
359
+ | `ctx.store` | State persistence — `get/put/patch/delete/list` domain objects |
360
+ | `adcpError(code, { message })` | Structured error |
361
+ | `registerTestController(server, store)` | Add `comply_test_controller` for deterministic testing |
360
362
 
361
363
  Response builders (`productsResponse`, `mediaBuyResponse`, `syncCreativesResponse`, etc.) are auto-applied by the framework. Handlers return raw data objects — the framework wraps them.
362
364
 
@@ -477,7 +479,11 @@ class MyGenerativeSeller implements DecisioningPlatform {
477
479
  buildCreative: async (req, ctx) => {
478
480
  // Generative path — read brief, call your model, return assets.
479
481
  // Framework auto-stores the build for refine/lookup via ctx_metadata.
480
- return { creative_manifest: { /* generated assets */ } };
482
+ return {
483
+ creative_manifest: {
484
+ /* generated assets */
485
+ },
486
+ };
481
487
  },
482
488
  previewCreative: async (req, ctx) => {
483
489
  return { preview_url: '...' };
@@ -21,6 +21,8 @@ A governance agent sits between buyers and sellers, evaluating proposed media bu
21
21
  - Serving audience segments → `skills/build-signals-agent/`
22
22
  - Managing brand identity and licensing → `skills/build-brand-rights-agent/`
23
23
 
24
+ **Often claimed alongside:** `measurement-verification` _(preview)_ + `content-standards` — governance vendors (IAS, DV) typically claim all three together. See [Common multi-specialism bundles](../../examples/README.md#common-multi-specialism-bundles).
25
+
24
26
  ## Specialisms This Skill Covers
25
27
 
26
28
  Your compliance obligations come from the specialisms you claim in `get_adcp_capabilities`. Each maps to a storyboard at `compliance/cache/latest/specialisms/<id>/`:
@@ -460,13 +462,13 @@ Some schemas also define an `ext` field for vendor-namespaced extensions. If you
460
462
 
461
463
  ## SDK Quick Reference
462
464
 
463
- | SDK piece | Usage |
464
- | ---------------------------------------- | -------------------------------------------------------------------------- |
465
- | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, auto-generated capabilities, ctx_metadata round-trip |
466
- | `createAdcpServer(config)` *(legacy)* | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
467
- | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
468
- | `ctx.store` | State persistence — `get/put/patch/delete/list` domain objects |
469
- | `adcpError(code, { message })` | Structured error |
465
+ | SDK piece | Usage |
466
+ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
467
+ | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, auto-generated capabilities, ctx_metadata round-trip |
468
+ | `createAdcpServer(config)` _(legacy)_ | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
469
+ | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
470
+ | `ctx.store` | State persistence — `get/put/patch/delete/list` domain objects |
471
+ | `adcpError(code, { message })` | Structured error |
470
472
 
471
473
  Handlers return raw data objects. The framework auto-wraps responses and auto-generates `get_adcp_capabilities` from registered handlers.
472
474
 
@@ -568,9 +570,15 @@ class MyGovernance implements DecisioningPlatform {
568
570
 
569
571
  propertyLists: PropertyListsPlatform = {
570
572
  listPropertyLists: async (req, ctx) => ({ property_lists: [] }),
571
- createPropertyList: async (req, ctx) => ({ /* ... */ }),
572
- updatePropertyList: async (req, ctx) => ({ /* ... */ }),
573
- deletePropertyList: async (req, ctx) => ({ /* ... */ }),
573
+ createPropertyList: async (req, ctx) => ({
574
+ /* ... */
575
+ }),
576
+ updatePropertyList: async (req, ctx) => ({
577
+ /* ... */
578
+ }),
579
+ deletePropertyList: async (req, ctx) => ({
580
+ /* ... */
581
+ }),
574
582
  };
575
583
  }
576
584
 
@@ -611,7 +619,8 @@ const idempotency = createIdempotencyStore({
611
619
  });
612
620
 
613
621
  const server = createAdcpServerFromPlatform(platform, {
614
- name: '...', version: '...',
622
+ name: '...',
623
+ version: '...',
615
624
  idempotency,
616
625
  // MUST never return undefined — or every mutating request rejects as
617
626
  // SERVICE_UNAVAILABLE. A constant works for a demo; production uses
@@ -21,6 +21,8 @@ A retail media agent sells advertising on a retailer's properties (sponsored pro
21
21
  - Generative seller (AI creative from briefs) → `skills/build-generative-seller-agent/`
22
22
  - Signals/audience data → `skills/build-signals-agent/`
23
23
 
24
+ **Often claimed alongside:** [`audience-sync`](../build-seller-agent/SKILL.md) (first-party audience push), [`creative-template`](../build-creative-agent/SKILL.md) (dynamic-creative rendering from catalog). Together these form the canonical retail-media bundle — see [Common multi-specialism bundles](../../examples/README.md#common-multi-specialism-bundles).
25
+
24
26
  Despite the name, **this skill also covers non-retail catalog-driven sales** — restaurants, travel, local commerce, any platform where the ad unit is rendered from a feed of products/listings/menu items. The compliance storyboard `media_buy_catalog_creative` uses a steakhouse protagonist, not a retailer.
25
27
 
26
28
  ## Specialisms This Skill Covers
@@ -254,14 +256,14 @@ Validate with: `adcp storyboard run <agent> deterministic_testing --json`
254
256
 
255
257
  ## SDK Quick Reference
256
258
 
257
- | SDK piece | Usage |
258
- | --------------------------------------- | ----------------------------------------------------------------------- |
259
- | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, ctx_metadata round-trip, auto-generated capabilities |
260
- | `createAdcpServer(config)` *(legacy)* | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
261
- | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
262
- | `ctx.store` | State persistence — `get/put/patch/delete/list` domain objects |
263
- | `adcpError(code, { message })` | Structured error |
264
- | `registerTestController(server, store)` | Add `comply_test_controller` for deterministic testing |
259
+ | SDK piece | Usage |
260
+ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
261
+ | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time specialism enforcement, ctx_metadata round-trip, auto-generated capabilities |
262
+ | `createAdcpServer(config)` _(legacy)_ | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
263
+ | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
264
+ | `ctx.store` | State persistence — `get/put/patch/delete/list` domain objects |
265
+ | `adcpError(code, { message })` | Structured error |
266
+ | `registerTestController(server, store)` | Add `comply_test_controller` for deterministic testing |
265
267
 
266
268
  Response builders (`productsResponse`, `mediaBuyResponse`, `deliveryResponse`, etc.) are auto-applied by the framework. Handlers return raw data objects — the framework wraps them.
267
269
 
@@ -23,25 +23,27 @@ A seller agent receives briefs from buyers, returns products with pricing, accep
23
23
  - Serving audience segments → `skills/build-signals-agent/`
24
24
  - Rendering creatives from briefs → that's a creative agent
25
25
 
26
+ **Often claimed alongside:** [`audience-sync`](../build-seller-agent/SKILL.md) (walled-garden social + identity provider patterns), [`signal-marketplace`](../build-signals-agent/SKILL.md) (DSP-side data surface), [`sales-catalog-driven`](../build-retail-media-agent/SKILL.md) (retail-media catalog + dynamic-creative). See [Common multi-specialism bundles](../../examples/README.md#common-multi-specialism-bundles).
27
+
26
28
  ## <a name="the-baseline-what-every-sales--agent-must-implement"></a>The baseline: what every sales-\* agent MUST implement
27
29
 
28
30
  Every sales-_ specialism (including `sales-social`, `sales-broadcast-tv`, `sales-retail-media`, `sales-catalog-driven`, etc.) is **additive on top of this baseline**. If you claim any `sales-_` specialism, you implement these tools regardless of the specialism-specific deltas below.
29
31
 
30
32
  **Required tools** (tested by the `media_buy_seller` storyboard bundle at `compliance/cache/3.0.0/protocols/media-buy/`):
31
33
 
32
- | Tool | Purpose | `SalesPlatform` method |
33
- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
34
- | `get_adcp_capabilities` | Declare protocols + specialisms + features | auto (framework) |
35
- | `sync_accounts` | Advertiser onboarding, per-tenant account creation | `accounts.upsert` |
36
- | `list_accounts` | Account lookup by brand/operator; buyers listing their accounts on your platform | `accounts.list` |
37
- | `get_products` | Product catalog discovery from a brief; returns `{ products: [...] }`. **Surface `Product.forecast: DeliveryForecast`** when you have per-targeting estimates — see [`sales-guaranteed`](specialisms/sales-guaranteed.md#forecast-productforecast) and [`sales-social`](specialisms/sales-social.md#planning-surface) for projection patterns. | `sales.getProducts` |
38
- | `list_creative_formats` | Formats your agent accepts | `sales.listCreativeFormats` |
39
- | `create_media_buy` | Accept a campaign with packages, budget, flight dates | `sales.createMediaBuy` |
40
- | `update_media_buy` | Bid, budget, status, package mutations over the campaign lifecycle | `sales.updateMediaBuy` |
41
- | `get_media_buys` | Read campaigns back with full state (status, budget, packages, targeting overlays) | `sales.getMediaBuys` |
42
- | `sync_creatives` | Accept creative assets and return per-asset status | `sales.syncCreatives` |
43
- | `list_creatives` | Read the creative library back with pagination | `sales.listCreatives` |
44
- | `get_media_buy_delivery` | Delivery + spend reporting with `reporting_period`, per-package billing rows | `sales.getMediaBuyDelivery` |
34
+ | Tool | Purpose | `SalesPlatform` method |
35
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
36
+ | `get_adcp_capabilities` | Declare protocols + specialisms + features | auto (framework) |
37
+ | `sync_accounts` | Advertiser onboarding, per-tenant account creation | `accounts.upsert` |
38
+ | `list_accounts` | Account lookup by brand/operator; buyers listing their accounts on your platform | `accounts.list` |
39
+ | `get_products` | Product catalog discovery from a brief; returns `{ products: [...] }`. **Surface `Product.forecast: DeliveryForecast`** when you have per-targeting estimates — see [`sales-guaranteed`](specialisms/sales-guaranteed.md#forecast-productforecast) and [`sales-social`](specialisms/sales-social.md#planning-surface) for projection patterns. | `sales.getProducts` |
40
+ | `list_creative_formats` | Formats your agent accepts | `sales.listCreativeFormats` |
41
+ | `create_media_buy` | Accept a campaign with packages, budget, flight dates | `sales.createMediaBuy` |
42
+ | `update_media_buy` | Bid, budget, status, package mutations over the campaign lifecycle | `sales.updateMediaBuy` |
43
+ | `get_media_buys` | Read campaigns back with full state (status, budget, packages, targeting overlays) | `sales.getMediaBuys` |
44
+ | `sync_creatives` | Accept creative assets and return per-asset status | `sales.syncCreatives` |
45
+ | `list_creatives` | Read the creative library back with pagination | `sales.listCreatives` |
46
+ | `get_media_buy_delivery` | Delivery + spend reporting with `reporting_period`, per-package billing rows | `sales.getMediaBuyDelivery` |
45
47
 
46
48
  > **`sales-guaranteed` minimum tool surface** — register ALL of these or storyboard scenarios will cascade-skip with `skip_reason: missing_tool`:
47
49
  > `get_adcp_capabilities`, `sync_accounts`, `list_accounts`, `get_products`, `list_creative_formats`, `create_media_buy`, `update_media_buy`, `get_media_buys`, `sync_creatives`, `get_media_buy_delivery`