build-signals-agent · git:20260409.69a6dde · 2026-04-09 · sha256 dcb33490fdbd63ff
build-signals-agent git:20260409.69a6ddeA
Immutable. This exact content is served forever at /api/v1/blob/dcb33490fdbd63ff.
---
name: build-signals-agent
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.
---
# Build a Signals Agent
## Overview
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.
## When to Use
- User wants to build an agent that serves audience/targeting data
- User mentions signals, segments, audiences, data provider, or CDP in the context of AdCP
- User references `get_signals`, `activate_signal`, or the signals protocol
**Not this skill:**
- Selling ad inventory (products, packages, media buys) → `skills/build-seller-agent/`
- Rendering creatives from briefs → that's a creative agent
- Building a client that *calls* a signals agent → see `docs/getting-started.md`
## Before Writing Code
Determine these four things. Ask the user — don't guess.
### 1. Marketplace or Owned?
These are fundamentally different businesses.
**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"`.
**Owned** — first-party data (retailer CDP, publisher contextual, CRM). Buyers trust your agent directly. `signal_type: "owned"` or `"custom"`, `signal_id.source: "agent"`.
### 2. What Segments?
Get specifics: names, definitions, what each represents. Push for 3-5 segments with variety. Each needs:
- Clear behavioral/demographic definition
- Realistic `coverage_percentage` (typically 5-30%)
- Value type: `binary` (in/out), `categorical` (tier levels — define the categories), or `numeric` (score range — define min/max)
### 3. Pricing
At least one pricing option per signal:
- `cpm` — `{ pricing_option_id: "po_cpm", model: "cpm", cpm: 2.50, currency: "USD" }`
- `percent_of_media` — `{ pricing_option_id: "po_pom", model: "percent_of_media", percent: 15, currency: "USD" }`
- `flat_fee` — `{ pricing_option_id: "po_flat", model: "flat_fee", amount: 5000, period: "monthly", currency: "USD" }`
### 4. Activation Destinations
If implementing `activate_signal`:
- **Platform** (DSP): `type: "platform"`, returns `activation_key: { type: "segment_id", segment_id: "..." }`
- **Agent** (sales agent): `type: "agent"`, returns `activation_key: { type: "key_value", key: "...", value: "..." }`
## Tools and Required Response Shapes
**`get_adcp_capabilities`** — register first, empty `{}` schema
```
capabilitiesResponse({
adcp: { major_versions: [3] },
supported_protocols: ['signals'],
})
```
**`get_signals`** — `GetSignalsRequestSchema.shape`
Two discovery modes — support both:
1. `signal_spec` — natural language. Match against segment names and descriptions.
2. `signal_ids` — exact lookup by `{ source, data_provider_domain, id }` or `{ source, agent_url, id }`.
Plus filtering via `filters.catalog_types`, `filters.max_cpm`, `filters.min_coverage_percentage`, and `max_results`.
```
taskToolResponse({
signals: [{
signal_agent_segment_id: string, // required - key for activate_signal
name: string, // required
description: string, // required
signal_type: 'marketplace' | 'owned' | 'custom', // required
data_provider: string, // required - your company name
coverage_percentage: number, // required - 0 to 100
deployments: [], // required - empty array (not live until activated)
pricing_options: [{ // required - at least one
pricing_option_id: string, // required
model: 'cpm', // required - discriminator
cpm: number, // required for cpm model
currency: 'USD', // required
}],
// signal_id is critical — shape depends on marketplace vs owned:
signal_id: {
source: 'catalog', // marketplace
data_provider_domain: string, // marketplace — domain for provenance verification
id: string, // unique segment ID
},
// OR for owned:
signal_id: {
source: 'agent', // owned
agent_url: string, // your agent URL
id: string,
},
value_type: 'binary' | 'categorical' | 'numeric', // optional but recommended
}],
sandbox: true, // for mock data
})
```
**`activate_signal`** — `ActivateSignalRequestSchema.shape`
Look up by `signal_agent_segment_id`. Validate `pricing_option_id`. Return deployments matching the requested destinations.
```
taskToolResponse({
deployments: [{
// Match the destination type from the request:
type: 'platform', // for platform destinations
platform: string, // echo from request destination
account: string | null, // echo from request
is_live: true, // signal is now active
activation_key: {
type: 'segment_id',
segment_id: string, // platform-specific segment ID
},
}],
// OR for agent destinations:
deployments: [{
type: 'agent',
agent_url: string,
is_live: true,
activation_key: {
type: 'key_value',
key: string,
value: string,
},
}],
sandbox: true,
})
```
## SDK Quick Reference
| SDK piece | Usage |
|-----------|-------|
| `serve(createAgent)` | Start HTTP server on `:3001/mcp` |
| `createTaskCapableServer(name, version, { taskStore })` | Create MCP server with task support |
| `server.tool(name, Schema.shape, handler)` | Register tool — `.shape` unwraps Zod |
| `capabilitiesResponse(data)` | Build `get_adcp_capabilities` response |
| `taskToolResponse(data, summary)` | Build tool response |
| `adcpError(code, { message })` | Structured error (`SIGNAL_NOT_FOUND`, `INVALID_DESTINATION`) |
| `GetSignalsRequestSchema.shape` | Zod schema for get_signals input |
| `ActivateSignalRequestSchema.shape` | Zod schema for activate_signal input |
| `type Signal = GetSignalsResponse['signals'][number]` | Type for a single signal object |
Import everything from `@adcp/client`. Types from `@adcp/client` with `import type`.
## Implementation
1. Single `.ts` file — all tools in one file
2. Always register `get_adcp_capabilities` as the **first** tool with empty `{}` schema
3. Use `Schema.shape` (not `Schema`) when registering tools
4. Set `sandbox: true` for mock/demo data
5. Use `ServeContext` pattern: `function createAgent({ taskStore }: ServeContext)`
The skill contains everything you need. Do not read additional docs before writing code.
## Validation
**After writing the agent, validate it. Fix failures. Repeat.**
**Full validation** (if you can bind ports):
```bash
npx tsx agent.ts &
npx @adcp/client storyboard run http://localhost:3001/mcp signal_owned --json # for owned data
npx @adcp/client storyboard run http://localhost:3001/mcp signal_marketplace --json # for marketplace
```
**Sandbox validation** (if ports are blocked):
```bash
npx tsc --noEmit agent.ts
```
**Keep iterating until all steps pass.**
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Pass `Schema` instead of `Schema.shape` | MCP SDK needs unwrapped Zod fields |
| Skip `get_adcp_capabilities` | Must be the first tool registered |
| Missing `signal_agent_segment_id` on signals | Buyers can't activate without it |
| Wrong `signal_id` shape | Marketplace: `{ source: "catalog", data_provider_domain, id }`. Owned: `{ source: "agent", agent_url, id }` |
| Missing `data_provider` field | Required on every signal — your company/brand name |
| Empty `pricing_options` array | Must have at least one pricing option per signal |
| `is_live: true` in get_signals deployments | Signals aren't live until `activate_signal` — use empty `deployments: []` |
| Activation doesn't match destination type | If request has `type: "platform"`, deployment must be `type: "platform"` |
| `sandbox: false` on mock data | Buyers may treat mock data as real |
## Reference
- `examples/signals-agent.ts` — complete runnable example
- `storyboards/signal_marketplace.yaml` — buyer call sequences for marketplace agent
- `storyboards/signal_owned.yaml` — call sequences for owned data agent
- `docs/guides/BUILD-AN-AGENT.md` — SDK patterns
- `docs/llms.txt` — full protocol reference