- Dappier Ask AI inventory across our publisher network
- Open internet advertising inventory through existing programmatic buying platforms / DSPs
Watch the Video
What is MCP?
If you’ve never seen MCP before, here’s the mental model:- The server advertises tools (name + JSON schema + description).
- The client fetches that list and lets the LLM call any tool by name with arguments that match the schema.
- The server validates, executes, and returns a result the LLM can use to continue the conversation.
What the Sales Agent MCP Server Does
Dappier is a sales agent on the AdCPmedia_buy protocol, selling one product: Sponsored Conversations.
A sponsored prompt — a clickable suggested question — appears inside publisher AI chat widgets across the Dappier network. When a user clicks it, they enter a full Brand Agent conversation: an AI chat experience that answers as the advertiser’s brand, grounded in the brand’s own content, with follow-up prompts and a call-to-action button.
What you get as a developer:
- An AI-callable surface for Dappier Sponsored Conversations — the branded prompt suggestions that appear inside publisher AI chat widgets across the Dappier network.
- One call to launch.
create_media_buybuilds the brand agent, the campaign, and the branded chat widget together. There is no separate creative-upload step. - A shareable preview (
demo_url) and two ready-to-traffic activation snippets returned on that same call. - Delivery reporting and a feedback channel back to Dappier.
- It does not quote prices. Sponsored Conversations are flat-rate; the figure is agreed offline with Dappier.
- It does not activate campaigns. Every campaign is registered
paused, withconfirmed_at: null, pending Dappier trafficking and reviewer activation. - It does not honor standard AdCP targeting (geo, device, language, audience). Contextual keywords are the only targeting signal — every campaign runs across the full Dappier network.
- It has no creative library. Creatives are inline and package-scoped, not reusable by
creative_id. - It cannot cancel a buy over the API, and it cannot change flight dates after creation.
Getting Started
Base URLs and Endpoints
The server is hosted by Dappier. Connect using the standard MCP streamable-HTTP transport.
Health / discovery endpoints (open, no auth):
Authentication
Every MCP request (except/ and /.well-known/* discovery endpoints) requires a Dappier API key.
Query parameter
HTTP header
401 with a plain-text body:
Getting an API key
Create Dappier API keys at platform.dappier.com/profile/api-keys. Keys start withak_. The key is used:
- As a Bearer token on every outbound call the server makes to
api.dappier.com. - To authorize the MCP session itself at the edge.
Connecting from Common MCP Clients
Claude Desktop (via mcp-remote)
Claude Desktop speaks MCP over stdio. To reach a remote HTTPS MCP server, proxy through mcp-remote.
Edit your Claude Desktop config (Settings → Developer → Edit Config):
Claude.ai (Connectors / Remote MCP)
On claude.ai, add a custom connector / remote MCP server pointing athttps://sales-agent.dappier.com/mcp. Supply the API key via header (dappier-api-key) where the UI allows custom headers, or via ?apiKey=... in the URL otherwise.
Cursor
Add a remote MCP server in Cursor’s MCP settings pointing at:Cloudflare AI Playground
Go to playground.ai.cloudflare.com and enter this as the server URL:Custom Node.js Client
Any client built on@modelcontextprotocol/sdk can connect over streamable HTTP:
TypeScript
Anthropic SDK (Native MCP Connector)
The Claude Messages API can call a remote MCP server directly as a tool-use source — no separate MCP client SDK required:Python
Both halves are required —
mcp_servers alone is rejected as a validation error. The mcp_server_name in tools must match the name in mcp_servers.Server Discovery
GET /.well-known/mcp.json (and /.well-known/server.json) returns:
Only
media_buy is declared. list_creative_formats is a Creative Protocol task, but the spec lists it in the media-buy sales agent’s required set — implementing it does not oblige a creative claim, and none of the creative-lifecycle tasks are implemented.Conventions
Response envelope
Every tool returns an MCPCallToolResult with two fields:
content[0]— atextblock containing pretty-printed JSON (for LLMs reading the text).structuredContent— the same payload as a machine-readable object (for programmatic clients).
status is "completed" on success and "failed" on error. Reads and writes alike complete synchronously — this agent never returns a pending task.
Idempotency
idempotency_key is required on the three tools with side effects — create_media_buy, update_media_buy, and provide_performance_feedback — and accepted-but-ignored on the read tools.
- Format: 16–255 characters of letters, digits, and
_ . : -only. Use a UUID v4. - Retrying with the same key replays the original response, and the response carries
replayed: true. - A fresh key always creates a new campaign and a new brand agent. This is the most common way duplicate buys get made.
- Replay window: 24 hours (
replay_ttl_seconds: 86400).
Optimistic concurrency (revision)
update_media_buy requires the revision you last observed via get_media_buys. It is checked atomically; a stale value returns CONFLICT and changes nothing. Re-read the buy and retry with the current revision and a fresh idempotency_key.
The account object
account is a strict one-of. Send either { account_id } or { brand, operator } — never fields from both, which is rejected rather than reconciled.
{ brand, operator }; no prior setup call is needed. operator is the entity placing the buy on the brand’s behalf — an agency trading desk, or the brand’s own domain when buying direct.
account is required on create_media_buy and update_media_buy, and optional on the reads.
Brand identity is a domain
AdCP identifies advertisers by domain, not by company name. Dappier resolves the display name and description from the domain’s/.well-known/brand.json where available, falling back to your brief and then to a name derived from the domain.
ID prefixes
Context passthrough
create_media_buy, update_media_buy, and the read tools accept a context: Record<string, unknown> field, echoed back unchanged on the response — useful for correlating tool calls with your own session state. Packages carry their own context too, so you can map Dappier’s package_id onto your own line item.
Error shape
Errors follow AdCP 3.1:recovery hint — the field that tells a buying agent whether to retry:
Error code reference
Field paths in errors are translated into your request’s vocabulary — you get
packages[0].creatives[0].assets.cta_url, not the backend’s internal campaign.cta_link.Typical campaign lifecycle
Tools Reference
get_adcp_capabilities
Purpose: The first call a buyer should make. Tells you which AdCP versions Dappier speaks, which protocols it implements, the auth and billing model, idempotency semantics, and per-protocol feature flags.
Network behavior: In-memory lookup. No outbound call. Returns instantly.
Inputs (all optional)
Success response
inline_creative_management: true is what makes packages[].creatives a legal input. has_creative_library: false is what makes it the only way to supply a creative — there is no sync_creatives step and no reuse by creative_id.get_products
Purpose: Discover what Dappier sells. Call this before create_media_buy — it returns the product_id and pricing_option_id a media buy requires.
Network behavior: In-memory. No API call.
Two buying modes
refine mode is not supported — this agent publishes a single product and issues no proposals.
Inputs
Success response
brief_relevance is present only in brief mode. available_metrics is a binding contract — spend is deliberately absent because Sponsored Conversations are flat-rate with no per-impression rate to meter.list_creative_formats
Purpose: Get the full asset specification for Dappier’s creative format. Call this before create_media_buy — it tells you exactly which assets a brand agent needs.
Network behavior: In-memory. Instant.
Dappier publishes one format, dappier_brand_agent. It is not a banner or a video: it is a conversational brand agent, so its assets are a persona, knowledge sources, promoted questions, a call-to-action, and widget branding.
Inputs (all optional)
The dappier_brand_agent asset spec
Individual assets — supplied by asset_id:
Repeatable groups — flattened to zero-indexed keys of the form
{group}_{index}_{asset}, not nested arrays:
Asset values are always objects, never bare strings:
create_media_buy
Purpose: Create a Sponsored Conversations campaign. One call creates the brand agent, the campaign, and the branded chat widget together. There is no separate creative-upload step.
Top-level inputs
start_time and end_time are independent — you can set an end without a start, or the reverse. Never invent a flight window to fill a field; the response message states the actual schedule, so repeat that back.Package inputs
Include the category, use cases, and adjacent topics in
keyword_targets — not just brand names. Someone searching the brand by name is already converted.Success response
demo_url— a working, shareable page rendering the brand agent exactly as an end user will see it, with this buy’s placement selected. It works immediately even though the campaign is paused. Never construct or guess this URL.distribution— the two activation snippets. See Distributing the Brand Agent.platform_links— deep links into the advertiser’s own agent on the Dappier platform, with the click path for each, so they can upload or replace the logo, banner, and thumbnail themselves. These are environment-specific; use the URLs from the response rather than hand-assembling them.
On an idempotent replay the response carries
replayed: true. Omission means it was freshly executed.Not supported
Sending any of these returns an explicit error rather than being silently ignored:proposal_id, total_budget, invoice_recipient, reporting_webhook, plan_id, and at the package level pacing, bid_price, impressions, optimization_goals, creative_assignments, catalogs, measurement_terms, performance_standards, committed_metrics, and all geo / device / language / audience targeting.
Example
get_media_buys
Purpose: Read the current state of campaigns — status, budget, the SmartSync pixel, and the revision token you need before any update.
Use this for “what’s the current state of my campaigns?”. For performance, use get_media_buy_delivery.
Inputs (all optional)
Success response
Status lives on a different field here. This tool returns lifecycle state on
media_buys[].status; create_media_buy and update_media_buy return the same value on media_buy_status. Same vocabulary, two field names.Valid actions by status
cancel and update_dates are deliberately absent: Dappier does not cancel over the API, and a Sponsored Conversations campaign is not date-scheduled.
Not supported
include_snapshot, include_history, include_webhook_activity.
update_media_buy
Purpose: Change an existing campaign — pause or resume it, adjust the budget, replace its contextual keywords, or revise the prompts and image assets.
PATCH semantics. Only the fields you send change; anything omitted is left exactly as it was.
Inputs
Pausing, resuming, and activation
paused: false is how a campaign created paused gets activated — once the SmartSync pixel is trafficked in the advertiser’s DSP, which is what makes the buy deliver. The first activation is when Dappier commits to the buy, so confirmed_at is stamped then.
Resuming a campaign Dappier has not yet trafficked returns INVALID_STATE.
Revisable assets
Send the same asset keys you used on create, underpackages[0].creatives[0].assets:
Edits reach the live widget in the same call, and a prompt whose text is unchanged keeps its id, so its reporting history is continuous.
What cannot be changed
Success response
Examples
Activate a campaign:get_media_buy_delivery
Purpose: Retrieve delivery performance — impressions, clicks, click-through rate, and call-to-action engagements, reported per package.
Use this for “how are my campaigns performing?”. For current state, use get_media_buys.
Inputs (all optional)
Lifetime only
What the metrics mean
No spend figure. Sponsored Conversations are sold flat-rate with no per-impression rate, so spend-to-date is not something Dappier can compute and is not reported.
currency denominates the campaign budget, not spend. Every row carries is_final: false — these numbers are for pacing and reporting, never for invoicing.
Success response
Reading zeros
impressions: 0 means no delivery yet, not missing data — an absent row means no such campaign. Check as_of and by_package[0].delivery_status before concluding a campaign is underdelivering. A campaign live for less than the reporting lag reports no delivery_status at all rather than guessing.
provide_performance_feedback
Purpose: Tell Dappier how a campaign actually performed for you, so delivery can be tuned over time.
This is the reverse of get_media_buy_delivery. That tool reports what Dappier served; this one reports what that traffic was worth, measured by your own attribution. Dappier cannot see whether a prompt click became a subscription — only you can.
Inputs
What Dappier does with it today: the signal is stored, not acted on. It will not change how the campaign runs right now — it is captured so it can inform tuning once optimization exists. Do not report to an advertiser that delivery has been adjusted.
idempotency_key per period. Re-scoring an earlier period after re-running attribution is fine and expected: send it as new feedback with its own key, and both readings are retained.
Example
Distributing the Brand Agent
create_media_buy returns a distribution object: one Brand Agent, two independent distribution channels. They do not depend on each other, and both can run at once. Present them to the advertiser as two separate options — never merge them, and never report one as the only way to activate.
1. Activate across Dappier AskAI
Sponsored Prompt → user engages → Brand Agent opens inside AskAIDistributes the Brand Agent through Sponsored Prompts inside Dappier AskAI publisher inventory. Activation method: the SmartSync Pixel.
The SmartSync pixel activates this Brand Agent across Dappier AskAI inventory only. It is not what runs the agent on the open web.
smart_pixel object is returned at the top level of create_media_buy and on every get_media_buys row.
2. Run as a Standalone Brand Agent
300x600 programmatic impression on the open web → the same Brand AgentThe same Brand Agent, trafficked as a standalone interactive ad across open-web inventory through a DSP, ad server, or compatible programmatic platform. Activation method: a JavaScript Creative.
widget-id there is not redundant with the element’s widgetId.
Macros
creativeId, lineitemId, publisherId, and clickUrl are DSP / ad-server macros, substituted by the serving platform at impression time. Map each one to the equivalent macro your platform supports — any macro left unmapped is delivered verbatim as a literal %macro_...% string.
If
distribution.omitted is present, the standalone option could not be built for that buy (no placement id was returned). Report it as unavailable rather than assembling a tag by hand.End-to-End Recipes
Launch a campaign (minimum viable flow)
What’s happening with my campaigns right now?
How is a paused campaign performing?
The active-only default applies to delivery even when you name ids, so pass the status filter too:Add an image the advertiser supplied after launch
platform_links entry — they can upload it themselves without another API call.
Broaden the keywords
Keywords replace, they don’t merge. Send the full set:Stop a campaign
There is no cancel.paused: true is how delivery ends:
Guardrails the Server Enforces
These are quiet-but-strict rules callers often trip over.On
create_media_buy, never infer cta_url or cta_button_text from the brand — ask the advertiser. The same goes for flight dates and budget: omit them rather than inventing values, and report back what the response message actually says.FAQ
Where did build_creative and list_creatives go?
Where did build_creative and list_creatives go?
They were removed. Creative creation is folded into
create_media_buy — one call creates the brand agent, the campaign, and the widget together — and creative revision into update_media_buy. This agent declares inline_creative_management: true and has_creative_library: false, so creatives are package-scoped and never reusable by creative_id.Do I need to call get_adcp_capabilities on every request?
Do I need to call get_adcp_capabilities on every request?
No. Call it once per session to discover the surface, then cache the result.
Why is my new campaign still paused?
Why is my new campaign still paused?
Every campaign is registered paused with
confirmed_at: null, pending Dappier trafficking and reviewer activation. Once the SmartSync pixel is trafficked in your DSP, update_media_buy { paused: false } activates it — and that first activation is when confirmed_at is stamped. Resuming before Dappier has trafficked it returns INVALID_STATE.I retried a failed create and now I have two campaigns.
I retried a failed create and now I have two campaigns.
That happens when a retry mints a fresh
idempotency_key. Reuse the same key — the response then carries replayed: true and nothing is created twice. If you get IDEMPOTENCY_EXPIRED, check whether the original call succeeded (call get_media_buys and match on the context you sent) before minting a new key.Can I set a budget?
Can I set a budget?
Yes —
packages[0].budget, as a plain number. It is optional: Sponsored Conversations are flat-rate, agreed offline with Dappier, so omit it rather than guessing a figure. The buy is registered either way.Can I target by geo / device / audience?
Can I target by geo / device / audience?
No. Contextual keywords are the only targeting signal. Every campaign runs across the full Dappier network. Sending geo, device, language, or audience targeting returns an explicit error rather than being silently dropped.
How do I set an end date?
How do I set an end date?
Set
end_time at creation if the advertiser named one. After creation, flight dates are immutable — paused: true is the end date.Why is there no spend in the delivery report?
Why is there no spend in the delivery report?
Sponsored Conversations are sold flat-rate with no per-impression rate stored, so spend-to-date is not a number Dappier can compute.
spend is deliberately absent from the product’s available_metrics rather than reported as a fabricated value. currency denominates the campaign budget only, and every row carries is_final: false.What happens if I pass extra fields the server doesn't understand?
What happens if I pass extra fields the server doesn't understand?
Fields AdCP defines but Dappier doesn’t honor —
pacing, bid_price, optimization_goals, reporting_webhook, include_snapshot, reporting_dimensions, and so on — are rejected with UNSUPPORTED_FEATURE and the exact field path, not silently ignored. An advertiser who thinks they bought US-only inventory needs to be told they did not.How do I correlate a tool call with my own request id?
How do I correlate a tool call with my own request id?
Pass
context: { your_request_id: "..." } on the buy, and/or packages[0].context on the package — both are echoed back unchanged, and the package one comes back on get_media_buys too.Is the pagination cursor format stable?
Is the pagination cursor format stable?
Treat it as opaque — round-trip the
pagination.cursor value verbatim and follow it while pagination.has_more is true. Don’t parse or construct it yourself.Can I attach more than one Brand Agent to a campaign?
Can I attach more than one Brand Agent to a campaign?
No. One media buy = one campaign = one package = one Brand Agent. Create separate media buys for separate campaigns.
Does provide_performance_feedback change how my campaign runs?
Does provide_performance_feedback change how my campaign runs?
Not today. The signal is stored so it can inform tuning once optimization exists. Don’t report to an advertiser that delivery has been adjusted.
Glossary
Conclusion
The Dappier Sales Agent MCP gives AI agents a complete, AI-callable surface over the Dappier Sponsored Conversations network — a brand agent, campaign, and branded widget in a single call, two ready-to-traffic distribution channels, delivery reporting, and a feedback loop — while keeping pricing and activation with Dappier. 🔗 Explore further:- Dappier Developers
- Dappier Platform — create your API key
- AdCP Specification
- Model Context Protocol

