From 1b105e5521ee520dce800ce2e032eb97fc917b85 Mon Sep 17 00:00:00 2001 From: Nastassia Fulconis Date: Wed, 17 Dec 2025 09:04:26 -0800 Subject: [PATCH 1/3] Fix MCP response format docs to use flat structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update documentation to show MCP responses with task-specific fields at the top level instead of nested inside a `data` wrapper. This aligns docs with what @adcp/client actually expects: - `response.products` instead of `response.data.products` - `response.media_buy_id` instead of `response.data.media_buy_id` Files updated: - mcp-guide.mdx: Updated response format section and examples - core-concepts.mdx: Updated response structure and code examples - getting-started.mdx: Updated MCP quick comparison example - protocol-comparison.mdx: Updated all MCP response examples - a2a-guide.mdx: Fixed incorrect `response.data` usage in A2A context Note: A2A examples correctly remain with `{"kind": "data", "data": {...}}` inside artifact parts, which is proper A2A protocol structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/protocols/a2a-guide.mdx | 7 ++- docs/protocols/core-concepts.mdx | 86 ++++++++++++-------------- docs/protocols/getting-started.mdx | 9 ++- docs/protocols/mcp-guide.mdx | 22 +++---- docs/protocols/protocol-comparison.mdx | 56 +++++++++-------- 5 files changed, 86 insertions(+), 94 deletions(-) diff --git a/docs/protocols/a2a-guide.mdx b/docs/protocols/a2a-guide.mdx index dafbc278..f1998b69 100644 --- a/docs/protocols/a2a-guide.mdx +++ b/docs/protocols/a2a-guide.mdx @@ -816,9 +816,10 @@ const response = await a2a.send({ } }); -// Check for file validation issues -if (response.status === 'failed' && response.data?.file_errors) { - console.log('File issues:', response.data.file_errors); +// Check for file validation issues (extract from A2A artifact structure) +const dataPart = response.artifacts?.[0]?.parts?.find(p => p.kind === 'data'); +if (response.status === 'failed' && dataPart?.data?.file_errors) { + console.log('File issues:', dataPart.data.file_errors); } ``` diff --git a/docs/protocols/core-concepts.mdx b/docs/protocols/core-concepts.mdx index 9ff1e9fa..dfebab3a 100644 --- a/docs/protocols/core-concepts.mdx +++ b/docs/protocols/core-concepts.mdx @@ -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 } ``` @@ -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); @@ -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"] } ``` @@ -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"; @@ -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" } ``` @@ -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" + ] } ``` @@ -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() }); @@ -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); } @@ -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); } diff --git a/docs/protocols/getting-started.mdx b/docs/protocols/getting-started.mdx index 6e5a3b47..f932e11e 100644 --- a/docs/protocols/getting-started.mdx +++ b/docs/protocols/getting-started.mdx @@ -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 } ``` diff --git a/docs/protocols/mcp-guide.mdx b/docs/protocols/mcp-guide.mdx index 51a3c4ea..c3303454 100644 --- a/docs/protocols/mcp-guide.mdx +++ b/docs/protocols/mcp-guide.mdx @@ -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. @@ -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); diff --git a/docs/protocols/protocol-comparison.mdx b/docs/protocols/protocol-comparison.mdx index ad790332..1299a772 100644 --- a/docs/protocols/protocol-comparison.mdx +++ b/docs/protocols/protocol-comparison.mdx @@ -55,13 +55,11 @@ Same status and data, different packaging: ### MCP Response Format ```json { - "message": "I need your budget and target audience", "status": "input-required", + "message": "I need your budget and target audience", "context_id": "ctx-123", - "data": { - "products": [], - "suggestions": ["budget", "audience"] - } + "products": [], + "suggestions": ["budget", "audience"] } ``` @@ -236,7 +234,7 @@ function handleResponse(response) { } if (response.status === 'completed') { - return processResults(response.data); + return processResults(response); // Task fields at top level } } ``` @@ -253,14 +251,14 @@ function handleResponse(response) { } ``` -**User Follow-up:** "Budget is $50K, targeting pet owners" +**User Follow-up:** "Budget is $50K, targeting pet owners" **AdCP Response:** ```json { - "status": "completed", + "status": "completed", "message": "Found 8 video products perfect for pet owners", "context_id": "ctx-123", - "data": { "products": [...] } + "products": [...] } ``` @@ -273,17 +271,15 @@ 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", - "data": { - "approval_required": true, - "amount": 150000, - "reason": "exceeds_limit" - } + "approval_required": true, + "amount": 150000, + "reason": "exceeds_limit" } ``` Client handling is identical: ```javascript -if (response.status === 'input-required' && response.data?.approval_required) { +if (response.status === 'input-required' && response.approval_required) { const decision = await getApproval(response.message); return sendApproval(response.context_id, decision); } @@ -329,7 +325,7 @@ const response = await mcp.call('get_products', { }); if (response.status === 'completed') { - console.log(response.data.products); + console.log(response.products); // Task fields at top level } ``` @@ -364,10 +360,8 @@ Both use `status: "failed"` with same error structure: "status": "failed", "message": "Insufficient inventory for your targeting criteria", "context_id": "ctx-123", - "data": { - "error_code": "insufficient_inventory", - "suggestions": ["Expand targeting", "Increase CPM"] - } + "error_code": "insufficient_inventory", + "suggestions": ["Expand targeting", "Increase CPM"] } ``` @@ -403,20 +397,28 @@ 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' + this.client = protocol === 'mcp' ? new McpClient(config) : new A2aClient(config); + this.protocol = protocol; } - + async send(request) { const response = await this.client.send(request); - - // Normalize response format + + // 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.message || response.artifacts?.[0]?.parts?.[0]?.text, - contextId: response.context_id || response.contextId, - data: response.data || response.artifacts?.[0]?.parts?.[1]?.data + message: response.artifacts?.[0]?.parts?.[0]?.text, + contextId: response.contextId, + ...dataPart?.data // Spread task-specific fields to top level }; } } From f1499e9ded2e44369c2cff84141593aa9b72335a Mon Sep 17 00:00:00 2001 From: Nastassia Fulconis Date: Wed, 17 Dec 2025 09:10:38 -0800 Subject: [PATCH 2/3] Add empty changeset for docs-only change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/nice-meals-help.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/nice-meals-help.md diff --git a/.changeset/nice-meals-help.md b/.changeset/nice-meals-help.md new file mode 100644 index 00000000..a845151c --- /dev/null +++ b/.changeset/nice-meals-help.md @@ -0,0 +1,2 @@ +--- +--- From a1062fa495a0452cfafa6287dddff82fc4576d2e Mon Sep 17 00:00:00 2001 From: Nastassia Fulconis Date: Wed, 17 Dec 2025 11:48:29 -0800 Subject: [PATCH 3/3] Replace ugly A2A artifact extraction with reference to testable sync_creatives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of showing raw A2A artifact parsing code, reference the sync_creatives task reference which has proper testable examples using @adcp/client. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/protocols/a2a-guide.mdx | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/docs/protocols/a2a-guide.mdx b/docs/protocols/a2a-guide.mdx index f1998b69..8bfe9b4f 100644 --- a/docs/protocols/a2a-guide.mdx +++ b/docs/protocols/a2a-guide.mdx @@ -804,24 +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 (extract from A2A artifact structure) -const dataPart = response.artifacts?.[0]?.parts?.find(p => p.kind === 'data'); -if (response.status === 'failed' && dataPart?.data?.file_errors) { - console.log('File issues:', dataPart.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