Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/nice-meals-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
20 changes: 4 additions & 16 deletions docs/protocols/a2a-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -804,23 +804,11 @@ try {
}
```

### File Upload Validation
```javascript
// A2A validates file types automatically
const response = await a2a.send({
message: {
parts: [
{ kind: "text", text: "Upload creative asset" },
{ kind: "file", uri: "https://example.com/video.mp4", name: "hero.mp4" }
]
}
});
### Creative Upload Error Handling

// Check for file validation issues
if (response.status === 'failed' && response.data?.file_errors) {
console.log('File issues:', response.data.file_errors);
}
```
For uploading creative assets and handling validation errors, use the `sync_creatives` task. See [sync_creatives Task Reference](/docs/media-buy/task-reference/sync_creatives) for complete testable examples.

The `@adcp/client` library handles A2A artifact extraction automatically, so you don't need to manually parse the response structure.

## Best Practices

Expand Down
86 changes: 38 additions & 48 deletions docs/protocols/core-concepts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,17 @@ AdCP uses the same status values as the [A2A protocol's TaskState enum](https://

### Response Structure

Every AdCP response has this structure:
Every AdCP response uses a **flat structure** where task-specific fields are at the top level:

```json
{
"status": "completed", // Always present: what state we're in
"message": "Found 5 products", // Always present: human explanation
"message": "Found 5 products", // Always present: human explanation
"context_id": "ctx-123", // Session continuity
"data": { // Task-specific structured data
"context": { // Application-level context echoed back
"ui": "buyer_dashboard"
},
"products": [...]
}
"context": { // Application-level context echoed back
"ui": "buyer_dashboard"
},
"products": [...] // Task-specific fields at top level
}
```

Expand All @@ -54,30 +52,30 @@ Every AdCP response has this structure:
function handleAdcpResponse(response) {
switch (response.status) {
case 'completed':
// Success - process the data
// Success - process the data (task fields are at top level)
showSuccess(response.message);
return processData(response.data);
return processData(response);

case 'input-required':
// Need more info - prompt user
const userInput = await promptUser(response.message);
return sendFollowUp(response.context_id, userInput);

case 'working':
// In progress - show progress and wait
showProgress(response.message);
return pollForUpdates(response.context_id);

case 'failed':
// Error - show message and handle gracefully
showError(response.message);
return handleError(response.data?.errors);
return handleError(response.errors);

case 'auth-required':
// Authentication needed
const credentials = await getAuth();
return retryWithAuth(credentials);

default:
// Unexpected status
console.warn('Unknown status:', response.status);
Expand All @@ -96,10 +94,8 @@ When status is `input-required`, the message tells you what's needed:
"status": "input-required",
"message": "I need more information about your campaign. What's your budget and target audience?",
"context_id": "ctx-123",
"data": {
"products": [], // Empty until clarification provided
"suggestions": ["budget", "audience", "timing"]
}
"products": [], // Empty until clarification provided
"suggestions": ["budget", "audience", "timing"]
}
```

Expand All @@ -122,22 +118,20 @@ Human approval is a special case of `input-required`:

```json
{
"status": "input-required",
"status": "input-required",
"message": "Media buy exceeds auto-approval limit ($100K). Please approve to proceed with campaign creation.",
"context_id": "ctx-123",
"data": {
"approval_required": true,
"amount": 150000,
"reason": "exceeds_limit"
}
"approval_required": true,
"amount": 150000,
"reason": "exceeds_limit"
}
```

**Client handling:**
```javascript
if (response.status === 'input-required' && response.data?.approval_required) {
if (response.status === 'input-required' && response.approval_required) {
// Show approval UI
const approved = await showApprovalDialog(response.message, response.data);
const approved = await showApprovalDialog(response.message, response);

// Send approval decision
const decision = approved ? "Approved" : "Rejected";
Expand All @@ -152,12 +146,10 @@ Async operations start with `working` and provide updates:
{
"status": "working",
"message": "Creating media buy. Validating inventory availability...",
"context_id": "ctx-123",
"data": {
"task_id": "task-456",
"progress": 25,
"step": "inventory_validation"
}
"context_id": "ctx-123",
"task_id": "task-456",
"progress": 25,
"step": "inventory_validation"
}
```

Expand Down Expand Up @@ -1200,23 +1192,21 @@ See [Optimization & Reporting](/docs/media-buy/media-buys/optimization-reporting

### Error Response Format

Failed operations return status `failed` with details:
Failed operations return status `failed` with error details at the top level:

```json
{
"status": "failed",
"message": "Unable to create media buy: Insufficient inventory available for your targeting criteria",
"context_id": "ctx-123",
"data": {
"error_code": "insufficient_inventory",
"requested_impressions": 10000000,
"available_impressions": 2500000,
"suggestions": [
"Expand geographic targeting",
"Increase CPM bid",
"Adjust date range"
]
}
"error_code": "insufficient_inventory",
"requested_impressions": 10000000,
"available_impressions": 2500000,
"suggestions": [
"Expand geographic targeting",
"Increase CPM bid",
"Adjust date range"
]
}
```

Expand All @@ -1238,7 +1228,7 @@ async function handleApprovalWorkflow(response) {
const approval = await showApprovalUI({
title: "Campaign Approval Required",
message: response.message,
details: response.data,
details: response, // Task fields are at top level
approver: getCurrentUser()
});

Expand Down Expand Up @@ -1277,7 +1267,7 @@ async function discoverProducts(brief) {
}

if (response.status === 'completed') {
return response.data.products;
return response.products; // Task fields are at top level
} else if (response.status === 'failed') {
throw new Error(response.message);
}
Expand Down Expand Up @@ -1313,7 +1303,7 @@ async function createCampaign(packages, budget) {
}

if (response.status === 'completed') {
return response.data.media_buy_id;
return response.media_buy_id; // Task fields are at top level
} else {
throw new Error(response.message);
}
Expand Down
9 changes: 4 additions & 5 deletions docs/protocols/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,13 @@ Both protocols support all features - they just express them differently:
}
}

// Response
// Response (flat structure - task fields at top level)
{
"status": "completed",
"message": "Found 5 CTV products",
"context_id": "ctx-123",
"data": {
"products": [...],
"clarification_needed": false
}
"products": [...],
"clarification_needed": false
}
```

Expand Down
22 changes: 11 additions & 11 deletions docs/protocols/mcp-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,32 +59,32 @@ const response = await mcp.call('build_creative', {
context: { ui: 'buyer_dashboard', session: '123' }
});

// Response includes the same context inside the task payload
console.log(response.data.context); // { ui: 'buyer_dashboard', session: '123' }
// Response includes the same context at the top level
console.log(response.context); // { ui: 'buyer_dashboard', session: '123' }
```

## MCP Response Format

**New in AdCP 1.6.0**: All responses include unified status field.

MCP responses use a **flat structure** where task-specific fields are at the top level alongside protocol fields:

```json
{
"status": "completed", // Unified status (see Core Concepts)
"message": "Found 5 products", // Human-readable summary
"context_id": "ctx-abc123", // MCP session continuity
"data": { // Task-specific structured data
"context": { "ui": "buyer_dashboard" }, // Application-level context echoed back
"products": [...],
"errors": [...] // Task-level errors/warnings
}
"message": "Found 5 products", // Human-readable summary
"context_id": "ctx-abc123", // MCP session continuity
"context": { "ui": "buyer_dashboard" }, // Application-level context echoed back
"products": [...], // Task-specific data (flat, not nested)
"errors": [...] // Task-level errors/warnings
}
```

### MCP-Specific Fields
- **context_id**: Session identifier that you must manually manage
- **context**: Opaque initiator-provided metadata echoed by agents
- **data**: Direct JSON structure (vs. A2A's artifact parts)
- **status**: Same values as A2A protocol for consistency
- Task-specific fields (e.g., `products`, `media_buy_id`, `creatives`) are at the top level, not wrapped in a `data` object

**Status Handling**: See [Core Concepts](/docs/protocols/core-concepts) for complete status handling patterns.

Expand Down Expand Up @@ -609,7 +609,7 @@ async function handleAdcpCall(tool, params, options = {}) {
}

case 'completed':
return response.data || response.result;
return response; // Task-specific fields are at the top level

case 'failed':
throw new Error(response.message);
Expand Down
Loading