Skip to main content
Transport-specific guide for integrating AdCP using the Model Context Protocol. For task handling, status management, and workflow patterns, see Task Lifecycle.

Testing AdCP via MCP

You can test AdCP tasks using the CLI tools or by chatting with Addie, the AgenticAdvertising.org assistant.

Tool Call Patterns

Basic Tool Invocation

Tool Call with Filters

Tool Call with Application-Level Context

MCP Response Format

Normative: AdCP MCP responses use a flat structure — envelope fields (status, context_id, context, task_id, timestamp, replayed, adcp_error, governance_context) and task-body fields appear as siblings at the root of the tool response. The payload object defined on core/protocol-envelope.json is a documentary grouping construct, NOT a serialized wire key: body fields are NOT nested under a payload: key on MCP. This matches MCP’s native structuredContent convention.
Producer rule. MCP tool implementations MUST emit envelope fields and body fields as flat siblings at the root. Nesting body fields under a payload: key is non-conformant — receivers parse from the flat root, and a nested representation breaks every shipping SDK. Receiver rule. MCP tool consumers MUST parse envelope and body fields from the flat root of the tool response. Receivers MUST NOT require a nested payload: key; the schema’s payload is documentation, not a wire requirement. When status is absent on the response (legacy or transport-native state carrier), receivers MUST default to completed for non-error responses and inspect adcp_error for error envelopes. context_id vs context — semantically orthogonal.
  • context_id is a server-managed session identifier for tracking related operations across multiple tool invocations. The server issues it; the caller MAY echo it on subsequent calls to thread a session. Distinct from MCP’s transport-level session.
  • context is a caller-supplied opaque echo object (core/context.json) — the agent preserves it byte-for-byte without parsing. Used for buyer-side correlation (UI session IDs, trace IDs, custom metadata).
  • Both MAY appear on the same response. They are NOT aliases.
Status handling: see Task Lifecycle for complete status handling patterns. Status Handling: See Task Lifecycle for complete status handling patterns.

Available Tools

All AdCP tasks are available as MCP tools:

Protocol Tools

Media Buy Tools

Signals Tools

Task Parameters: See individual task documentation in Media Buy and Signals sections.

MCP Tasks as a Transport Wrapper

AdCP task lifecycle state is application-layer state. MCP Tasks can wrap a tools/call request so the MCP client, rather than the LLM, waits for the CallToolResult; they do not replace the AdCP task_id, status payloads, webhooks, or polling/reconciliation surfaces. A task-augmented MCP call can complete successfully after delivering an AdCP payload whose status is still submitted. From that point, the media-buy, creative, signals, or governance workflow remains open at the AdCP layer and should be observed with webhooks or AdCP polling. :::warning Client support is limited Most chat-based MCP clients (Claude Desktop, Cursor) do not yet support MCP Tasks. If your client doesn’t support task-augmented tool calls, use standard tools/call plus webhooks or AdCP polling instead — these work with any MCP client. See Async Operations and Push Notifications for transport-independent patterns. MCP Tasks are useful when you control the MCP client (e.g., building your own orchestrator with @modelcontextprotocol/sdk) and want protocol-level waiting for the initial tools/call result. They are optional transport plumbing, not the canonical AdCP task store. :::

SDK Implementation

If you use the @modelcontextprotocol/sdk package, MCP Tasks support requires minimal code. Pass an InMemoryTaskStore (or your own TaskStore implementation) to the Server constructor — the SDK auto-registers handlers for tasks/get, tasks/result, tasks/list, and tasks/cancel:
In your tools/call handler, check for the task field and use the store:
The SDK handles polling, cancellation, TTL cleanup, and _meta injection for tasks/result responses. InMemoryTaskStore is non-persistent — for production, implement a TaskStore backed by your database. If you use McpServer instead of Server, register task-capable tools with server.experimental.tasks.registerToolTask() — the higher-level API enforces this for tools that declare taskSupport. :::warning Production task isolation InMemoryTaskStore does not scope tasks by session — any client that knows a task ID can read, cancel, or list it. For production, implement a TaskStore that filters by sessionId on every operation. Also clamp client-provided TTL values server-side and enforce rate limits on task creation. :::

Server Capabilities

AdCP MCP servers declare tasks in their capabilities:

Tool-Level Task Support

Each tool declares whether it supports task-augmented execution via execution.taskSupport: Tools with taskSupport: "optional" can be called either way:
  • Without task field: Synchronous — returns the result directly
  • With task field: Returns a CreateTaskResult immediately; poll the MCP task via transport-native tasks/get, retrieve the CallToolResult via transport-native tasks/result, then inspect the AdCP payload inside that result.

Invoking a Tool as a Task

Include the task field in your tools/call request:
The server returns a task handle immediately:
The client polls the MCP transport task with tasks/get (respecting pollInterval) until the task reaches a terminal state (completed, failed, or cancelled), then retrieves the CallToolResult via tasks/result. To abort the transport task, send tasks/cancel with the MCP taskId. After retrieving the CallToolResult, inspect the AdCP response payload. If it contains status: "submitted" and an AdCP task_id, the transport task has delivered the queued AdCP response but the application workflow is still open. Continue with webhooks or AdCP polling (get_task_status, or legacy tasks/get in 3.x).

MCP Task Status vs. AdCP Status

AdCP uses a richer set of statuses than MCP Tasks. If an implementation mirrors AdCP progress into a transport-native MCP Task, use this mapping only for the MCP wrapper. The AdCP payload remains the source of truth for domain workflow state:

Webhooks for Long-Lived Operations

MCP Tasks handles waiting within the MCP session, but many AdCP operations outlive a single session (e.g., a media buy that takes 24 hours for publisher approval). For these, register push_notification_config on the AdCP call:
The MCP Task tracks the transport wrapper within the session. The webhook tracks the AdCP application task independently and remains valid even after the MCP session ends. See Push Notifications for webhook payload formats and authentication.

Context Management (MCP-Specific)

Critical: MCP requires manual context management. You must pass context_id to maintain conversation state.

Context Session Pattern

Usage Examples

Basic Session with Context

Async Operations with MCP Tasks

For tools with taskSupport: "optional", pass the task option to use MCP Tasks:
Webhook POST format:
Note: Receivers MUST correlate webhooks using operation_id (and task_type) from the payload body, not by parsing the webhook URL. Buyers MAY embed operation_id in the URL path or query for their own server-side routing convenience (the URL structure is opaque to the seller and entirely buyer-defined), but the seller never parses that URL — the seller echoes the buyer-supplied operation_id it was given at registration, and the wire-level source of truth for correlation is the payload field. See mcp-webhook-payload.json and Webhooks — Operation IDs. The result field contains the AdCP data payload. For completed/failed statuses, this is the full task response (e.g., create-media-buy-response.json). For other statuses, use the status-specific schemas (e.g., create-media-buy-async-response-working.json).

MCP Webhook Envelope Fields

The mcp-webhook-payload.json envelope includes: Required fields:
  • idempotency_key — Per-fire transport dedup key (see schema for full semantics)
  • operation_id — Buyer-supplied correlation identifier echoed verbatim by the seller. Receivers use this — not the URL path — to route notifications to the originating task. Sellers MUST NOT derive this by parsing the URL; the URL structure is implementation-defined from the seller’s point of view.
  • task_id — Unique task identifier for correlation
  • task_type — Task name (e.g., create_media_buy, sync_creatives) for routing to per-task handlers
  • status — Current task status (completed, failed, working, input-required, etc.)
  • timestamp — ISO 8601 timestamp when webhook was generated
Optional fields:
  • notification_id — Event-layer stable id for re-emission tracking (see schema)
  • protocol — AdCP protocol family (media-buy or signals)
  • context_id — Conversation/session identifier
  • message — Human-readable context about the status change
Data field:
  • result — Task-specific AdCP payload (see Data Schema Validation below)

Webhook Trigger Rules

Webhooks are sent when all of these conditions are met:
  1. Task type supports async (e.g., create_media_buy, sync_creatives, get_products)
  2. pushNotificationConfig is provided in the request
  3. Task runs asynchronously — initial response is working or submitted
If the initial response is already terminal (completed, failed, rejected), no webhook is sent—you already have the result. Status changes that trigger webhooks:
  • working → Progress update (task actively processing)
  • input-required → Human input needed
  • completed → Final result available
  • failed → Error details

Data Schema Validation

The result field in MCP webhooks uses status-specific schemas: Schema reference: async-response-data.json

Webhook Handler Example

Task Management and Polling

Context Expiration Handling

Key Difference: Unlike A2A which manages context automatically, MCP requires explicit context_id management.

Handling Async Operations

When an AdCP response returns working or submitted status, you need a way to receive the result. This applies whether or not your MCP client supports MCP Tasks — the patterns below work with any client. Configure a webhook URL and the server will POST the result when the operation completes. This is the right approach for submitted operations that are blocked on external dependencies (publisher approval, human review).
See Push Notifications for payload formats and authentication.

Option 2: Polling (backup)

Use AdCP polling as a backup for submitted operations, or when you can’t expose a webhook endpoint. In 3.x, prefer get_task_status when the seller advertises it; otherwise use legacy tasks/get:

Handling different statuses

Integration Example

MCP-Specific Considerations

Server-side tool wrappers MUST tolerate envelope fields

Buyer SDKs send envelope-level fields (idempotency_key, context_id, context, governance_context, push_notification_config) uniformly across all AdCP tool calls — including read-only tools that don’t consume them. MCP tool implementations MUST accept these fields and ignore the ones they don’t use; they MUST NOT reject a call because an envelope field is present. Common traps:
  • FastMCP / Pydantic strict signatures — declare idempotency_key: str | None = None (and the other envelope fields) as accept-and-ignore optionals, or use **kwargs to swallow unknowns. model_config = ConfigDict(extra='allow') on input models if you control them.
  • Zod / valibot with .strict() on input schemas — drop .strict() or use a passthrough variant.
  • OpenAPI codegen that injects additionalProperties: false into input models — fix the generator config; the spec’s request schemas declare additionalProperties: true.
A wrapper that raises unexpected_keyword_argument on idempotency_key will fail compliance against any buyer SDK that follows the envelope contract. See security.mdx > Server-side tool wrapper conformance for the normative rule.

Tool Discovery

AdCP Extension via MCP Server Card

Recommended: Use get_adcp_capabilities for runtime capability discovery. The server card extension provides static metadata for tool catalogs and registries.
MCP servers can declare AdCP support via a server card at /.well-known/mcp.json (or /.well-known/server.json). AdCP-specific metadata goes in the _meta field using the adcontextprotocol.org namespace.
Discovering AdCP support:
Benefits:
  • Clients can discover AdCP capabilities without making test calls
  • Declare which protocol domains you implement (media_buy, creative, signals)
  • Declare which typed extensions you support (see Context & Sessions)
  • Enable compatibility checks based on version
:::note The adcp_version field in server card metadata is a v2 convention and is not part of the v3 spec. For v3 version negotiation, the buyer sends release-precision adcp_version (e.g., "3.1") on every request, and the seller advertises supported releases via adcp.supported_versions on get_adcp_capabilities and echoes adcp_version at the envelope root on every response. The legacy integer-only adcp_major_version field is still accepted for backwards compatibility. See versioning.mdx § Version negotiation for the full contract. ::: Note: The _meta field uses reverse DNS namespacing per the MCP server.json spec. AdCP servers should support both /.well-known/mcp.json and /.well-known/server.json locations.

Parameter Validation

Error Handling

AdCP errors are returned as tool-level responses with isError: true and the error in structuredContent.adcp_error. For the full extraction logic and JSON-RPC transport codes, see Transport Error Mapping.

Best Practices

  1. Use session wrapper for automatic context management
  2. Check status field before processing response data
  3. Handle context expiration gracefully with retries
  4. Reference Core Concepts for status handling patterns
  5. Validate parameters using MCP tool schemas when available

Next Steps

For status handling, async operations, and clarification patterns, see Task Lifecycle - this guide focuses on MCP transport specifics only.