Documentation Index
Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
Use this file to discover all available pages before exploring further.
Both MCP and A2A provide identical AdCP capabilities using the same unified status system. They differ only in transport format and async handling.
Quick Comparison
| Aspect | MCP | A2A |
|---|
| Request Style | Tool calls | Task messages |
| Response Style | Direct JSON | Artifacts |
| Status System | β
Unified status field | β
Unified status field |
| Async Handling | Polling with tasks/get | SSE streaming |
| Webhooks | Protocol wrapper extension | Native PushNotificationConfig |
| Task Management | tasks/list, tasks/get tools | Native tasks/list, tasks/get |
| Context | Manual (pass context_id) | Automatic (protocol-managed) |
| Best For | Claude, AI assistants | Agent workflows |
Unified Status System
β¨ New in AdCP 1.6.0: Both protocols now use the same status field with A2A TaskState values.
Status Handling (Both Protocols)
Every response includes a status field that tells you exactly what to do:
{
"status": "input-required", // Same values for both protocols
"message": "Need your budget", // Same human explanation
// ... protocol-specific formatting below
}
| Status | What It Means | Your Action |
|---|
completed | Task finished | Process data, show success |
input-required | Need user input | Read message, prompt user, follow up |
working | Processing (< 120s) | Poll frequently, show progress |
submitted | Long-running (hours to days) | Provide webhook or poll less frequently |
failed | Error occurred | Show error, handle gracefully |
auth-required | Need auth | Prompt for credentials |
See Core Concepts for complete status handling guide.
Same status and data, different packaging:
{
"status": "input-required",
"message": "I need your budget and target audience",
"context_id": "ctx-123",
"products": [],
"suggestions": ["budget", "audience"]
}
{
"status": "input-required",
"contextId": "ctx-123",
"artifacts": [{
"artifactId": "artifact-product-discovery-xyz",
"name": "product_discovery",
"parts": [
{
"kind": "text",
"text": "I need your budget and target audience"
},
{
"kind": "data",
"data": {
"products": [],
"suggestions": ["budget", "audience"]
}
}
]
}]
}
Async Operation Differences
Both protocols handle async operations with the same status progression:
submitted β working β completed/failed
MCP Async Pattern
// Initial response with task_id
{
"status": "submitted",
"message": "Creating media buy, requires manual approval",
"context_id": "ctx-123",
"task_id": "task-456",
}
// Poll using tasks/get
const updates = await session.call('tasks/get', {
task_id: "task-456",
include_result: true
});
// Optional: Configure webhook at protocol level (A2A-compatible structure)
const response = await session.call('create_media_buy', params, {
push_notification_config: {
url: "https://buyer.com/webhooks",
authentication: {
schemes: ["HMAC-SHA256"], // or ["Bearer"]
credentials: "shared_secret_32_chars"
}
}
});
A2A Async Pattern
// Initial response with native task tracking
{
"status": "submitted",
"taskId": "task-456",
"contextId": "ctx-123",
"estimatedCompletionTime": "2025-01-23T10:00:00Z"
}
// Real-time updates via SSE
const events = new EventSource(`/tasks/${response.taskId}/events`);
events.onmessage = (event) => {
const update = JSON.parse(event.data);
console.log(`Status: ${update.status}, Message: ${update.message}`);
};
// Native webhook support
await a2a.send({
message: { /* skill invocation */ },
push_notification_config: {
webhook_url: "https://buyer.com/webhooks",
authentication: {
schemes: ["Bearer"],
credentials: "secret_token_min_32_chars"
}
}
});
Push Notification Architecture
Both MCP and A2A use HTTP webhooks for async task updates. AdCP keeps the envelope format protocol-specific while using identical data schemas for the business payload.
| Aspect | MCP | A2A |
|---|
| Spec Status | AdCP specifies this | Native protocol feature |
| Configuration | pushNotificationConfig | pushNotificationConfig |
| Envelope | mcp-webhook-payload.json | Task, TaskStatusUpdateEvent, or TaskArtifactUpdateEvent |
| Data Location | result field | status.message.parts[].data |
| Data Schemas | Identical AdCP schemas | Identical AdCP schemas |
Key Principles
- Same data, different envelopes: The AdCP payload (media buy data, product lists, etc.) is identical regardless of protocol
- Protocol-native delivery: MCP uses
mcp-webhook-payload.json, A2A uses native Task objects
- Unified configuration: Both use
pushNotificationConfig with the same authentication structure
For protocol-specific examples and implementation details:
Task Management
Both protocols now provide equivalent task management capabilities:
MCP: AdCP Tasks
// List pending tasks
await session.call('tasks/list', {
filters: { statuses: ["submitted", "working"] }
});
// Poll specific task
await session.call('tasks/get', {
task_id: "task_456",
include_result: true
});
// State reconciliation
const reconciliation = await session.reconcileState();
A2A: Native RPC Methods
// Native task management
const tasks = await a2a.rpc('tasks/list', {
filters: { statuses: ["submitted", "working"] }
});
const task = await a2a.rpc('tasks/get', {
task_id: "task_456",
include_result: true
});
Server Decision Making
In both protocols, the server decides whether to use webhooks:
- Quick operations (< 120s): Returns
working, ignores webhook configuration
- Long operations (hours/days): Returns
submitted, uses webhook if provided
- Fallback: Clients can always poll regardless of webhook configuration
Clarification Handling
Before AdCP 1.6.0: Different approaches for each protocol
After AdCP 1.6.0: Same pattern using status: "input-required"
Unified Clarification Pattern
// Works for both MCP and A2A
function handleResponse(response) {
if (response.status === 'input-required') {
// Extract clarification from message
const info = promptUser(response.message);
// Send follow-up with same context
return sendFollowUp(response.context_id, info);
}
if (response.status === 'completed') {
return processResults(response); // Task fields at top level
}
}
Example: Clarification Flow
User: βFind video productsβ
AdCP Response:
{
"status": "input-required",
"message": "I'd be happy to help find video products. What's your budget and target audience?",
"context_id": "ctx-123"
}
User Follow-up: βBudget is $50K, targeting pet ownersβ
AdCP Response:
{
"status": "completed",
"message": "Found 8 video products perfect for pet owners",
"context_id": "ctx-123",
"products": [...]
}
Human-in-the-Loop Workflows
Both protocols handle approvals using status: "input-required":
{
"status": "input-required",
"message": "Media buy exceeds auto-approval limit ($100K). Please approve to proceed.",
"context_id": "ctx-123",
"approval_required": true,
"amount": 150000,
"reason": "exceeds_limit"
}
Client handling is identical:
if (response.status === 'input-required' && response.approval_required) {
const decision = await getApproval(response.message);
return sendApproval(response.context_id, decision);
}
Context Management
MCP: Manual Context
let contextId = null;
async function callAdcp(request) {
if (contextId) {
request.context_id = contextId;
}
const response = await mcp.call('get_products', request);
contextId = response.context_id; // Save for next call
return response;
}
A2A: Automatic Context
// A2A manages context automatically
const response1 = await a2a.send({ message: "Find video products" });
const response2 = await a2a.send({
contextId: response1.contextId, // Optional - A2A tracks this
message: "Focus on premium inventory"
});
Operation Examples
Product Discovery
MCP:
const response = await mcp.call('get_products', {
brief: "CTV campaign for sports fans",
budget: 100000
});
if (response.status === 'completed') {
console.log(response.products); // Task fields at top level
}
A2A:
const response = await a2a.send({
message: {
parts: [{
kind: "data",
data: {
skill: "get_products",
parameters: {
brief: "CTV campaign for sports fans",
budget: 100000
}
}
}]
}
});
if (response.status === 'completed') {
console.log(response.artifacts[0].parts[1].data.products);
}
Error Handling
Both use status: "failed" with same error structure:
{
"status": "failed",
"message": "Insufficient inventory for your targeting criteria",
"context_id": "ctx-123",
"error_code": "insufficient_inventory",
"suggestions": ["Expand targeting", "Increase CPM"]
}
Choosing a Protocol
Choose based on your ecosystem and preferences:
Choose MCP if youβre using:
- Claude Desktop or Claude Code
- MCP-compatible AI assistants
- Simple tool-based integrations
- Direct JSON responses
Choose A2A if youβre using:
- Google AI agents or Agent Engine
- Multi-modal workflows (text + files)
- Real-time streaming updates
- Artifact-based data handling
Both protocols provide:
- β
Same AdCP tasks and capabilities
- β
Unified status system for clear client logic
- β
Context management for conversations
- β
Async operation support
- β
Human-in-the-loop workflows
- β
Error handling and recovery
Migration Between Protocols
The unified status system makes it easy to switch protocols:
// Abstract client that works with both
class UnifiedAdcpClient {
constructor(protocol, config) {
this.client = protocol === 'mcp'
? new McpClient(config)
: new A2aClient(config);
this.protocol = protocol;
}
async send(request) {
const response = await this.client.send(request);
// MCP: task fields already at top level
// A2A: extract from artifacts
if (this.protocol === 'mcp') {
return response;
}
// Normalize A2A response to flat structure
const dataPart = response.artifacts?.[0]?.parts?.find(p => p.kind === 'data');
return {
status: response.status,
message: response.artifacts?.[0]?.parts?.[0]?.text,
contextId: response.contextId,
...dataPart?.data // Spread task-specific fields to top level
};
}
}
Next Steps
- Core Concepts: Read Core Concepts for status handling patterns
- MCP Guide: See MCP Guide for tool calls and context management
- A2A Guide: See A2A Guide for artifacts and streaming
- Migration: Both protocols provide the same capabilities with unified status handling