Skip to main content
The Dappier Sales Agent MCP is a remote Model Context Protocol (MCP) server that lets any AI agent or LLM client discover Dappier advertising inventory, create Sponsored Conversations campaigns, manage Brand Agent creatives, and pull delivery metrics — all without writing HTTP wrappers around the Dappier REST API. It implements the Advertising Context Protocol (AdCP) sales-agent surface for Dappier and exposes 9 AI-callable tools across two protocols: media_buy and creative. The server is hosted at sales-agent.dappier.com.

Watch the Video

If you prefer a visual walkthrough, check out the accompanying video below:

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

Two AdCP protocols are supported:
ProtocolWhat it covers
media_buyDiscover products, create/update/cancel campaigns (“media buys”), pull delivery data.
creativeList creative formats, create/update Dappier Brand Agent creatives, list existing creatives.
What you get as a developer:
  1. An AI-callable surface for Dappier Sponsored Conversations — the branded prompt suggestions that appear inside publisher AI chat widgets across the Dappier network.
  2. Campaign lifecycle in a single tool call: create a paused campaign with advertiser details, CTA, targeting, and optional sponsored prompts.
  3. Creative lifecycle: attach a configurable Dappier Brand Agent (conversational AI creative) to a campaign via build_creative.
  4. Delivery reporting over a date range.
What the server is not:
  • It does not quote prices or sell impressions directly — pricing is handled offline by Dappier sales ([email protected]).
  • It does not activate campaigns. Every campaign is born paused; a Dappier reviewer must approve and a sales rep must complete Google Ad Manager (GAM) line-item setup before it serves.
  • It does not honor standard AdCP targeting (geo, device, language, audience). Dappier targeting is dappier_network / my_network / individual_agents.

Getting Started

Base URLs and Endpoints

The server is hosted by Dappier. Connect using the standard MCP streamable-HTTP transport.
TransportPathWhen to use
Streamable HTTPPOST /mcpAll MCP clients (Claude.ai, Cursor, custom apps, mcp-remote proxy).
Health / discovery endpoints (open, no auth):
PathReturns
GET /HTML landing page confirming the worker is up.
GET /.well-known/mcp.jsonThe AdCP server card (name, version, protocols, tool names).
GET /.well-known/server.jsonSame server card (alias).
Use the deployed URL Dappier publishes — for example https://sales-agent.dappier.com/mcp. Confirm the current host with your Dappier contact.

Authentication

Every MCP request (except / and /.well-known/* discovery endpoints) requires a Dappier API key.

Query parameter

https://<host>/mcp?apiKey=YOUR_DAPPIER_API_KEY

HTTP header

dappier-api-key: YOUR_DAPPIER_API_KEY
If the key is missing you’ll get an HTTP 401 with a plain-text body:
Authentication required

A Dappier API key is required to access this endpoint.

You can provide it in one of two ways:
  1. Query parameter:  ?apiKey=YOUR_DAPPIER_API_KEY
  2. Request header:   dappier-api-key: YOUR_DAPPIER_API_KEY

Don't have a key yet? Create one at:
  https://platform.dappier.com/profile/api-keys

Getting an API key

Create Dappier API keys at platform.dappier.com/profile/api-keys. 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.
Keep the key server-side or in a secret manager. Never expose it in browser code.

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):
{
  "mcpServers": {
    "dappier-sales-agent": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://sales-agent.dappier.com/mcp?apiKey=YOUR_DAPPIER_API_KEY"
      ]
    }
  }
}
Restart Claude Desktop — the 9 Dappier tools will appear in the tool picker.

Claude.ai (Connectors / Remote MCP)

On claude.ai, add a custom connector / remote MCP server pointing at https://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:
https://sales-agent.dappier.com/mcp?apiKey=YOUR_DAPPIER_API_KEY
Cursor supports the streamable HTTP transport directly.

Cloudflare AI Playground

Go to playground.ai.cloudflare.com and enter this as the server URL:
https://sales-agent.dappier.com/mcp?apiKey=YOUR_DAPPIER_API_KEY

Custom Node.js Client

Any client built on @modelcontextprotocol/sdk can connect over streamable HTTP:
TypeScript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://sales-agent.dappier.com/mcp"),
  {
    requestInit: {
      headers: { "dappier-api-key": process.env.DAPPIER_API_KEY! }
    }
  }
);

const client = new Client(
  { name: "my-app", version: "1.0.0" },
  { capabilities: {} }
);
await client.connect(transport);

const tools = await client.listTools();
console.log(tools);

const products = await client.callTool({
  name: "get_products",
  arguments: {
    buying_mode: "brief",
    brief: "Launch a new coffee brand in Q3"
  }
});
console.log(products);

Anthropic SDK (Native MCP Connector)

The Anthropic Messages API can call a remote MCP server directly as a tool-use source — no separate MCP client SDK required:
Python
client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    mcp_servers=[{
        "type": "url",
        "url": "https://sales-agent.dappier.com/mcp",
        "name": "dappier-sales-agent",
        "authorization_token": "YOUR_DAPPIER_API_KEY"  # sent as dappier-api-key
    }],
    messages=[{
        "role": "user",
        "content": "List my active Dappier campaigns."
    }]
)

Server Discovery

GET /.well-known/mcp.json (and /.well-known/server.json) returns:
{
  "name": "com.dappier/sales-agent",
  "version": "1.0.0",
  "title": "Dappier Sales Agent",
  "description": "AdCP sales agent for Dappier's Sponsored Conversations network — discover inventory, create and manage branded-prompt campaigns across Dappier's publisher AI chat widgets.",
  "tools": [
    { "name": "get_adcp_capabilities" },
    { "name": "list_creative_formats" },
    { "name": "build_creative" },
    { "name": "list_creatives" },
    { "name": "get_products" },
    { "name": "create_media_buy" },
    { "name": "update_media_buy" },
    { "name": "get_media_buys" },
    { "name": "get_media_buy_delivery" }
  ],
  "_meta": {
    "adcontextprotocol.org": {
      "protocols_supported": ["media_buy", "creative"]
    }
  }
}

Conventions

ID prefixes

PrefixMeaningExample
cp_Dappier campaign / media buy idcp_01HW9ZAB...
am_Dappier Brand Agent / creative idam_01HW9CD...
pm_Sponsored prompt idpm_01ABC...
Schemas reject values that don’t match the expected prefix — the server won’t silently accept a mismatched id.

Response envelope

Every tool returns an MCP CallToolResult with two fields:
  • content[0] — a text block containing pretty-printed JSON (for LLMs reading the text).
  • structuredContent — the same payload as a machine-readable object (for programmatic clients).
Both contain the same data — use whichever matches your client.

Error shape

Errors follow AdCP conventions:
{
  "errors": [
    { "code": "VALIDATION_ERROR", "message": "...", "field": "cta_link" }
  ]
}
For async-style tools, a status: "failed" wrapper is used:
{
  "status": "failed",
  "errors": [{ "code": "INVALID_REQUEST", "message": "..." }]
}

Error code reference

CodeMeaning
INVALID_REQUESTGeneric 400-level rejection on create.
VALIDATION_ERRORUpdate with no fields, or 400-level rejection on update.
INVALID_DATE_RANGEMissing, malformed, or unsorted start_date / end_date.
POLICY_VIOLATION401/403 (bad key, insufficient scope).
AUTH_REQUIRED401 on read tools.
MEDIA_BUY_NOT_FOUNDUnknown cp_xxxx.
CREATIVE_NOT_FOUNDUnknown am_xxxx.
INVALID_STATECampaign in a state that forbids the requested action.
CREATIVE_ID_EXISTS409 conflict (duplicate creative attached).
CONTEXT_REQUIREDDefault-scope list returned zero; supply media_buy_ids or status_filter.
VERSION_UNSUPPORTEDadcp_major_version not supported.
INTERNAL_ERRORFallback.

Context passthrough

Most tools accept a context: Record<string, unknown> field. Whatever you send is echoed back unchanged on the response — useful for correlating tool calls with your own session state.

Typical campaign lifecycle

1. get_adcp_capabilities              — discover what the server supports
2. get_products                       — confirm Sponsored Conversations is the product
3. create_media_buy                   — submit the campaign (returns cp_xxxx, starts paused)
4. list_creative_formats              — find the dappier_brand_agent format id
5. build_creative (create mode)       — attach a Brand Agent to the cp_xxxx
6. [offline] Dappier sales sets up GAM, reviewer approves
7. update_media_buy { paused: false } — resume (may be rejected until sales approves)
8. get_media_buys                     — check operational state
9. get_media_buy_delivery             — pull impressions/clicks over a date range

Tools Reference

get_adcp_capabilities

Purpose: The first call a buyer should make. Tells you which AdCP protocols Dappier implements, which features inside those protocols are honored vs. ignored, the auth model, and which creative capabilities are available. Network behavior: In-memory lookup. No outbound call. Returns instantly.

Inputs (all optional)

FieldTypeDescription
adcp_major_versionintegerAdCP major version the buyer’s payloads conform to. If provided and unsupported, returns VERSION_UNSUPPORTED. Currently supported: 1.
protocolsarray of "media_buy" | "signals" | "governance" | "sponsored_intelligence" | "creative" | "compliance_testing"Filter which protocols you want capability info for. Omit to get everything Dappier supports.

Success response

{
  "adcp": { "major_versions": [1] },
  "supported_protocols": ["media_buy", "creative"],
  "account": {
    "supported_billing": ["operator"],
    "require_operator_auth": false,
    "required_for_products": false,
    "account_financials": false,
    "sandbox": false
  },
  "media_buy": {
    "features": {
      "inline_creative_management": false,
      "property_list_filtering": false,
      "content_standards": false,
      "audience_targeting": false
    },
    "execution": {
      "creative_specs": {
        "vast_versions": [],
        "mraid_versions": [],
        "vpaid": false,
        "simid": false
      },
      "targeting": { /* every geo/device/audience field is false */ }
    },
    "portfolio": {
      "publisher_domains": ["dappier.com"],
      "primary_channels": ["native"],
      "primary_countries": ["US"],
      "description": "Dappier operates a network of publishers running AI chat experiences...",
      "advertising_policies": "All campaigns require manual review..."
    }
  },
  "creative": {
    "has_creative_library": true,
    "supports_generation": false,
    "supports_transformation": false,
    "supports_compliance": false
  },
  "last_updated": "2026-04-10T00:00:00Z"
}

Error response

{
  "errors": [
    { "code": "VERSION_UNSUPPORTED", "message": "AdCP major version 2 is not supported..." }
  ]
}
Targeting dimensions declared false will return a validation error if sent — they are not silently dropped. media_buy and creative are the only protocols you’ll see.

list_creative_formats

Purpose: Discover the creative formats supported by the Dappier sales agent. Call this before build_creative. Network behavior: In-memory. Instant.

Inputs

All inputs are optional and all are ignored in v1 — accepted for AdCP forward-compat:
FieldTypeNotes
format_idsarray of { agent_url: string, id: string }Ignored.
asset_typesstring[]Ignored.
max_width, max_height, min_width, min_heightintegerIgnored.
is_responsivebooleanIgnored.
name_searchstringIgnored.
wcag_level"A" | "AA" | "AAA"Ignored.
pagination.max_results, pagination.cursorIgnored.

Success response

Returns a single format descriptor for dappier_brand_agent (the only creative format Dappier exposes). The format_id.agent_url returned here is the exact value you must pass into build_creative.target_format_id.
Call this tool to pick up the exact agent_url string — don’t hard-code it.

build_creative

Purpose: Create a new Dappier Brand Agent (conversational AI creative) and attach it to a campaign, or update an existing one. A Brand Agent is a configurable chat experience branded for an advertiser: name, description, optional persona, optional single knowledge source (RSS feed or webpage), and optional widget overrides (logo, colors, welcome copy, theme).

Two modes

ModeHow to triggerEffect
CREATEOmit creative_idCreates a new Brand Agent, attaches it to media_buy_id. Requires creative_manifest.assets.name and ...description. The campaign must not already have a creative attached.
UPDATESupply creative_id (am_xxxx)PATCH semantics — only the fields you send change. source_url and source_type must be supplied together or not at all.

Inputs

FieldTypeRequiredNotes
target_format_id{ agent_url: URL, id: "dappier_brand_agent" }yesMust be dappier_brand_agent. Get agent_url from list_creative_formats.
media_buy_idcp_xxxx stringyesCampaign to attach the Brand Agent to.
creative_idam_xxxx stringnoPresent → UPDATE. Omit → CREATE.
creative_manifest.format_id{ agent_url, id: "dappier_brand_agent" }yesMust equal target_format_id.
creative_manifest.assets.namestring ≤120required on CREATEShown as “Ask {name}”.
creative_manifest.assets.descriptionstring ≤500required on CREATEBrand description used inside the conversation.
creative_manifest.assets.personastringnoFree-form tone instruction.
creative_manifest.assets.source_urlURLnoRSS feed or webpage. Must pair with source_type.
creative_manifest.assets.source_type"rss" | "webpage"noMust pair with source_url.
creative_manifest.assets.logo_urlURLnoWidget logo override.
creative_manifest.assets.primary_colorstringnoHex color, e.g. #8353E2.
creative_manifest.assets.welcome_titlestringnoTitle above the Ask-AI widget.
creative_manifest.assets.welcome_descriptionstringnoSubtitle above the widget.
creative_manifest.assets.placeholder_textstringnoPlaceholder inside the input box.
creative_manifest.assets.theme_mode"light" | "dark"noWidget theme.
contextRecord<string, unknown>noEchoed back.

Success response

{
  "creative_id": "am_...",
  "media_buy_id": "cp_...",
  "widget_id": "...",
  "target_format_id": {
    "agent_url": "...",
    "id": "dappier_brand_agent"
  }
}

Example — CREATE

{
  "target_format_id": {
    "agent_url": "https://sales-agent.dappier.com/.well-known/adcp/sales",
    "id": "dappier_brand_agent"
  },
  "media_buy_id": "cp_01HW9ZAB...",
  "creative_manifest": {
    "format_id": {
      "agent_url": "https://sales-agent.dappier.com/.well-known/adcp/sales",
      "id": "dappier_brand_agent"
    },
    "assets": {
      "name": "Acme Pet Food",
      "description": "Premium, science-backed nutrition for dogs and cats.",
      "persona": "Friendly, expert, concise.",
      "source_url": "https://acmepetfood.com/blog/feed.xml",
      "source_type": "rss",
      "primary_color": "#8353E2",
      "welcome_title": "Ask Acme Pet Food",
      "theme_mode": "light"
    }
  }
}

Example — UPDATE (change logo only)

{
  "target_format_id": {
    "agent_url": "...",
    "id": "dappier_brand_agent"
  },
  "media_buy_id": "cp_01HW9ZAB...",
  "creative_id": "am_01HW9CD...",
  "creative_manifest": {
    "format_id": { "agent_url": "...", "id": "dappier_brand_agent" },
    "assets": {
      "logo_url": "https://cdn.acme.com/logo-new.png"
    }
  }
}

list_creatives

Purpose: Browse the tenant’s Brand Agents, or look up specific ones.

Three filter paths

Mutually exclusive on the primary id filters:
PathHow to triggerBehavior
By creative idsfilters.creative_idsLook up specific Brand Agents. Missing ids → CREATIVE_NOT_FOUND entries in errors[]. Pagination ignored.
By media buy idsfilters.media_buy_idsList Brand Agents attached to specific campaigns. Missing campaigns → MEDIA_BUY_NOT_FOUND. Pagination ignored.
Defaultomit bothPaginated list of every tenant Brand Agent.
If both creative_ids and media_buy_ids are supplied, creative_ids wins.
filters.name_contains (case-insensitive) and filters.statuses (approved / pending_review / archived) narrow any of the three paths.

Inputs

FieldTypeNotes
filters.creative_idsam_xxxx[]Lookup by id.
filters.media_buy_idscp_xxxx[]Lookup by campaign.
filters.name_containsstringCase-insensitive substring.
filters.statuses("approved" | "pending_review" | "archived")[]
pagination.max_resultsint 1-100 (default 50)Honored only on default path.
pagination.cursorstringOpaque — round-trip verbatim.
contextobjectEchoed back.

get_products

Purpose: Discover Dappier’s advertising inventory. Always returns the single Sponsored Conversations product (or an empty list if your filters exclude it). Network behavior: In-memory. No API call.

Inputs

FieldTypeRequiredNotes
buying_mode"brief" | "wholesale" | "refine"yesHow you want results selected.
briefstringrequired iff buying_mode === "brief"Natural-language campaign description. Must be omitted otherwise.
brand.domainstringnoE.g. "acmepetfood.com".
brand.brand_idstringnoOptional stable id.
filters.delivery_type"guaranteed" | "non_guaranteed"noDappier only offers non_guaranteed; anything else returns [].
filters.channelsstring[]noMust include "native" (or be omitted) for the product to match.
filters.format_ids{ agent_url, id }[]noMust include Dappier’s format to match.

Success response

{
  "products": [{
    "product_id": "sponsored_conversations",
    "name": "Sponsored Conversations",
    "description": "A branded prompt suggestion...",
    "publisher_properties": [
      { "publisher_domain": "dappier.com", "property_tags": ["dappier_network"] }
    ],
    "format_ids": [{
      "agent_url": "https://sales-agent.dappier.com/.well-known/adcp/sales",
      "id": "sponsored_prompt_standard"
    }],
    "delivery_type": "non_guaranteed",
    "pricing_options": [{
      "pricing_option_id": "contact_sales",
      "pricing_model": "flat_rate",
      "currency": "USD",
      "min_spend_per_package": 0
    }],
    "brief_relevance": "Strong fit — ..."
  }]
}
brief_relevance is present only when brief was supplied.

create_media_buy

Purpose: Create a sponsored campaign on the Dappier network. Campaign is born paused. It will not serve until:
  1. Dappier sales sets up the GAM line item offline.
  2. A Dappier reviewer approves it.
You can optionally pass sponsored_prompts to create the clickable prompt content atomically with the campaign.

Inputs

FieldTypeRequiredNotes
brand.namestringyes→ Dappier details.advertiser_name.
brand.domainstringno
buyer_refstringnoYour correlation id, echoed in the response.
packagesPackage[]defaultedDefaults to a single sponsored_conversations package. Only set it to pass targeting_overlay.product_ids for individual-agent targeting.
packages[].product_idliteral "sponsored_conversations"defaultedOnly allowed value.
packages[].pricing_option_idliteral "contact_sales"defaultedOnly allowed value.
packages[].format_ids[]{ agent_url, id: "sponsored_prompt_standard" }defaulted
packages[].targeting_overlay.product_idsstring[]noam_xxxx agent ids. Only honored when targeting_type === "individual_agents".
start_timeISO 8601 datetimeno
end_timeISO 8601 datetimeno
campaign_namestringyes→ Dappier details.name.
cta_linkURLyesWhere clicks land. Must be supplied by the user — never infer from brand name / domain.
cta_button_textstringyesE.g. "Shop now", "Learn more". Must be supplied by the user — never invent.
targeting_type"dappier_network" | "my_network" | "individual_agents"defaultedDefault: dappier_network.
sponsored_prompts[]{ prompt_text (≤200), thumbnail?, is_active (default true) }noAny caller-supplied prompt_id is silently dropped on create — the server assigns new pm_xxxx ids.
contextobjectnoEchoed back.

Success response

{
  "status": "submitted",
  "media_buy_id": "cp_01HW9ZAB...",
  "task_id": "cp_01HW9ZAB...",
  "buyer_ref": "...",
  "message": "Campaign created in paused state. Dappier sales will contact you to complete GAM setup and activate the campaign.",
  "confirmed_at": "2026-04-18T14:00:00Z",
  "packages": [{
    "package_id": "cp_01HW9ZAB...",
    "product_id": "sponsored_conversations",
    "status": "pending_activation"
  }],
  "context": { /* your context, echoed */ }
}

Error response

{
  "status": "failed",
  "errors": [{ "code": "INVALID_REQUEST", "message": "..." }]
}
Error code mapping: 400 → INVALID_REQUEST, 401/403 → POLICY_VIOLATION, 409 → CREATIVE_ID_EXISTS, else → INTERNAL_ERROR.

Example

{
  "brand": { "name": "Acme Pet Food", "domain": "acmepetfood.com" },
  "campaign_name": "Q3 Puppy Launch",
  "cta_link": "https://acmepetfood.com/puppy",
  "cta_button_text": "Shop the launch",
  "targeting_type": "dappier_network",
  "start_time": "2026-07-01T00:00:00Z",
  "end_time":   "2026-09-30T23:59:59Z",
  "sponsored_prompts": [
    { "prompt_text": "Best food for new puppies?", "is_active": true },
    { "prompt_text": "Is grain-free food worth it?" }
  ]
}

update_media_buy

Purpose: Modify an existing Dappier campaign. PATCH semantics — omitted fields are preserved. Atomic — either all changes apply or none do. The handler routes to one of three backend operations based on what you send:
Request shapeBackend action
canceled: true (any other field optional)Soft-delete the campaign. Irreversible. Overrides other changes.
Only paused set, no other update fieldsStatus-only flip (pause/resume).
Any update field (optionally plus paused)Full update (all provided fields).

Inputs

All optional except media_buy_id:
FieldTypeNotes
media_buy_idcp_xxxxRequired.
pausedbooleantrue = pause, false = resume. Resume may be rejected until Dappier sales approves.
canceledliteral trueSoft-delete. Irreversible.
cancellation_reasonstringEchoed in the response message.
start_time, end_timeISO 8601New flight dates.
campaign_name, advertiser_name, brand_description, contextual_keywordsstringMap to details.*.
targeting_type"dappier_network" | "my_network" | "individual_agents"
product_idsam_xxxx[]Replaces details.targeting.agent_list.
cta_linkURLOnly include if the user explicitly asked to change it. Otherwise omit (existing value preserved).
cta_button_textstringOnly include if the user explicitly asked.
custom_followup_promptsstring[] (max 3)Replaces existing list.
sponsored_prompts[]{ prompt_id?, prompt_text, thumbnail?, is_active }Full-replacement semantics — see below.
contextobjectEchoed back.
Pass the full desired set after the update:
  • Entries with prompt_id (pm_xxxx) → updated.
  • Entries without prompt_id → newly created.
  • Existing prompts not present in the array → soft-deleted.

Success response

{
  "status": "completed",
  "media_buy_id": "cp_01HW9...",
  "implementation_date": "2026-04-18T14:02:11Z",
  "affected_packages": [],
  "context": { /* echoed */ }
}

Resume-pending special case

If you send paused: false and the backend rejects with INVALID_STATE, NOT_APPROVED, or PENDING_APPROVAL, the tool returns status: "submitted" (not an error) with the message:
Resume requires Dappier sales to complete GAM setup and a reviewer to approve. The campaign remains paused.

Validation error — empty request

If you send no canceled, no paused, and no update fields:
{
  "status": "failed",
  "errors": [{ "code": "VALIDATION_ERROR", "message": "..." }]
}

Examples

Pause a campaign:
{ "media_buy_id": "cp_01HW9...", "paused": true }
Cancel with a reason:
{
  "media_buy_id": "cp_01HW9...",
  "canceled": true,
  "cancellation_reason": "Client request"
}
Replace the prompt list:
{
  "media_buy_id": "cp_01HW9...",
  "sponsored_prompts": [
    { "prompt_id": "pm_01ABC...", "prompt_text": "Updated text", "is_active": true },
    { "prompt_text": "New prompt", "is_active": true }
  ]
}
Existing prompts not listed in the array are soft-deleted.

get_media_buys

Purpose: Retrieve the current operational state of Dappier campaigns — configuration, sponsored-prompt approval status, and which campaigns are waiting on Dappier sales / GAM setup. Use this for “what’s the current state of my campaigns?”. For performance over a date range, use get_media_buy_delivery instead.

Inputs (all optional)

FieldTypeNotes
media_buy_idscp_xxxx[]Fetch specific campaigns in parallel. When set, no implicit status filter is applied.
status_filterone of / array of "pending_creatives" | "pending_start" | "active" | "paused" | "completed" | "rejected" | "canceled"Defaults to ["active"] when neither media_buy_ids nor status_filter is provided.
include_snapshotboolean (default false)Accepted for AdCP compliance. Always returns snapshot_unavailable_reason: "SNAPSHOT_UNSUPPORTED".
pagination.max_resultsint 1-100 (default 50)
pagination.cursorstringOpaque. Currently encoded as page:<n>.
contextobjectEchoed back.

Success response

{
  "media_buys": [
    {
      "media_buy_id": "cp_01HW9...",
      "status": "pending_creatives",
      "currency": "USD",
      "total_budget": null,
      "creative_deadline": null,
      "confirmed_at": "2026-04-18T14:00:00Z",
      "revision": 1,
      "valid_actions": ["pause", "cancel", "update_budget", "update_dates", "update_media_buy"],
      "packages": [{
        "package_id": "cp_01HW9...",
        "paused": false,
        "canceled": false,
        "creative_approvals": [
          {
            "creative_id": "pm_01ABC...",
            "approval_status": "approved",
            "rejection_reason": "..."
          }
        ],
        "format_ids_pending": [],
        "snapshot_unavailable_reason": "SNAPSHOT_UNSUPPORTED"
      }],
      "cancellation": null
    }
  ],
  "pagination": { "has_more": false },
  "errors": []
}

Status derivation rules

  • is_deleted === truecanceled
  • else status === "active"active
  • else if creative_ids exist → paused
  • else → pending_creatives

Valid actions by status

StatusActions
pending_creativescancel, update_media_buy
activepause, cancel, update_budget, update_dates, update_media_buy
pausedresume, cancel, update_budget, update_dates, update_media_buy
canceled

Empty-result nudge

If you pass neither media_buy_ids nor status_filter, the default filter is ["active"]. If that produces no campaigns, the response includes:
{
  "errors": [{
    "code": "CONTEXT_REQUIRED",
    "message": "No campaigns matched the default scope..."
  }]
}

get_media_buy_delivery

Purpose: Retrieve delivery metrics (impressions, clicks, etc.) for Dappier campaigns over a date range or campaign lifetime. Use this for “how did my campaigns perform over a period?”. For current state, use get_media_buys.

Inputs

FieldTypeNotes
media_buy_idscp_xxxx[]When set, no implicit status filter is applied.
status_filterone of / array of "pending_creatives" | "pending_start" | "active" | "paused" | "completed"Defaults to ["active"] server-side when neither media_buy_ids nor status_filter is provided.
start_dateYYYY-MM-DDInclusive. Must be paired with end_date.
end_dateYYYY-MM-DDExclusive. Must be paired with start_date.
contextobjectEchoed back.

Date range rules

  • Send both dates or neither.
  • Both must match YYYY-MM-DD.
  • start_date < end_date strictly.
  • Violations return INVALID_DATE_RANGE with field set to the offender — no backend call is made.
Omitting both dates returns lifetime-to-date data.

Unsupported in v1

  • Reporting dimensions (geo, device, audience, placement).
  • Spend, ROAS, CPM, conversion value — pricing is handled offline in GAM, not tracked in Dappier.

Success response

Whatever the backend returns for the AdCP delivery shape is passed through verbatim, plus your context echo. Expect at minimum per-campaign impressions/clicks and a reporting_period object.

Error response

{
  "errors": [{
    "code": "INVALID_DATE_RANGE",
    "message": "end_date must be after start_date",
    "field": "end_date"
  }]
}
Error mapping: backend code / error_code passed through if provided, else 404 → MEDIA_BUY_NOT_FOUND, 401 → AUTH_REQUIRED, 400 → INVALID_DATE_RANGE, else → INTERNAL_ERROR.

End-to-End Recipes

Launch a campaign (minimum viable flow)

1. get_adcp_capabilities
   → confirm media_buy + creative protocols are available.

2. get_products { buying_mode: "brief", brief: "launch new coffee brand in Q3" }
   → confirm Sponsored Conversations is the product.

3. create_media_buy {
     brand: { name, domain },
     campaign_name: "...",
     cta_link: "<user-supplied>",
     cta_button_text: "<user-supplied>",
     sponsored_prompts: [ { prompt_text }, ... ]   // optional
   }
   → returns cp_xxxx (paused).

4. list_creative_formats
   → pick up dappier_brand_agent's agent_url.

5. build_creative {
     target_format_id: { agent_url, id: "dappier_brand_agent" },
     media_buy_id: "cp_xxxx",
     creative_manifest: {
       format_id: { ... },
       assets: { name, description, persona?, source_url?, source_type?, ... }
     }
   }
   → returns am_xxxx creative + widget_id.

6. [Dappier sales + reviewer complete setup offline]

7. update_media_buy { media_buy_id: "cp_xxxx", paused: false }
   → campaign goes live (or comes back as status: "submitted" if still pending approval).

What’s happening with my campaigns right now?

{ "status_filter": ["active", "paused", "pending_creatives"] }
Or for specific campaigns:
{ "media_buy_ids": ["cp_...", "cp_..."] }

How did last month perform?

{
  "media_buy_ids": ["cp_..."],
  "start_date": "2026-03-01",
  "end_date":   "2026-04-01"
}

Edit the prompts on an existing campaign

{
  "media_buy_id": "cp_...",
  "sponsored_prompts": [
    { "prompt_id": "pm_EXISTING_ONE", "prompt_text": "Updated text", "is_active": true },
    { "prompt_text": "Brand-new prompt" }
  ]
}
Any pre-existing prompts not listed above are soft-deleted.

Attach a different Brand Agent to a campaign

Call build_creative in UPDATE mode with the existing creative_id. Only send the asset fields you want to change — the rest are preserved.

Guardrails the Server Enforces

These are quiet-but-strict rules callers often trip over.
RuleWhere
Never infer cta_link or cta_button_text from the brand. Ask the user. The schema descriptions say so explicitly and the backend has no way to recover.create_media_buy, update_media_buy
creative_id starts with am_, media_buy_id starts with cp_, prompt ids with pm_.Everywhere
source_url and source_type must be supplied together.build_creative
brief is required iff buying_mode === "brief"; forbidden otherwise.get_products
start_date and end_date must both be present or both absent; start_date < end_date.get_media_buy_delivery
update_media_buy with no fields at all returns VALIDATION_ERROR.update_media_buy
custom_followup_prompts max 3 items.update_media_buy
Any targeting dimension declared false in capabilities will reject rather than silently drop.create_media_buy, update_media_buy

FAQ

No. Call it once per session to discover the surface, then cache the result.
Every Dappier campaign is born paused and requires (a) Dappier sales to configure the GAM line item offline and (b) a reviewer to approve. Calling update_media_buy { paused: false } before those steps complete returns status: "submitted" with an explanatory message, not an error.
Pricing is offline. Budget / CPM / ROAS fields are not honored. pricing_option_id is always "contact_sales".
No. Dappier targeting is dappier_network (whole network), my_network (your own publishers), or individual_agents (list of am_xxxx ids).
Fields defined by AdCP but unused by Dappier (idempotency_key, budget, include_snapshot, include_history, reporting_dimensions, format-filter arguments, etc.) are accepted for forward compatibility but ignored. Fields that are targeting dimensions Dappier doesn’t support will cause validation errors.
Pass context: { your_request_id: "..." } on any tool — it’s echoed back unchanged.
get_media_buys currently uses page:<n> cursors. Treat it as opaque — don’t parse or construct it yourself.
No. On CREATE, the target campaign must not already have a creative attached. Use UPDATE mode to modify the existing Brand Agent.

Glossary

TermMeaning
AdCPAdvertising Context Protocol — an open spec for AI-callable ad-platform tools.
MCPModel Context Protocol — the transport/framing standard this server speaks.
Media buyAn AdCP term for a campaign. One Dappier campaign = one media buy.
PackageAn AdCP subdivision of a media buy. Dappier has no native package model — each campaign is returned with a single synthetic package.
Sponsored ConversationsDappier’s only product. A branded prompt pill that opens into a full Brand Agent conversation.
Brand AgentThe conversational AI creative attached to a campaign. Id prefix am_.
Sponsored promptThe clickable prompt text shown to users. Id prefix pm_.
GAMGoogle Ad Manager — where Dappier sales configures line items offline before a campaign can serve.
Dappier networkThe set of publishers running Dappier AI chat widgets.

Conclusion

The Dappier Sales Agent MCP gives AI agents a complete, AI-callable surface over the Dappier Sponsored Conversations network — campaign creation, Brand Agent creatives, and delivery reporting — while keeping pricing and activation in the hands of Dappier’s sales team. 🔗 Explore further: