You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #525 (closes #509) shipped structured `adcp_error` envelopes on the MCP side — `CallToolResult.structuredContent.adcp_error` now carries the full structured shape (`code`, `message`, `recovery`, `field`, `details`, `retry_after`).
The A2A surface in `src/adcp/server/a2a_server.py` has two parity gaps that would make A2A storyboards (when they exercise structured error checks) report "actual: ..." failures the same way the MCP side did before #509:
`_send_adcp_error` only catches `adcp.exceptions.ADCPError`. Decisioning-layer `AdcpError` raises (which adopters using `DecisioningPlatform` produce) fall into the generic `except Exception` and render as a plain "Skill execution failed" text message — losing the structured shape entirely.
Structured A2A error envelopes are a wire-spec thing — buyers using A2A clients should get the same `adcp_error` shape as MCP buyers. Storyboards graded against A2A-served sellers will surface this gap as a wire-conformance failure once the storyboard runner's structured-error checks are run against A2A surfaces (or the same checks via #1527's storyboard runner UX work).
Populate the full envelope. The A2A `DataPart` for the failed-task envelope should carry `{code, message, recovery, field, details, retry_after}` whenever the raised error has those fields populated; omit when None.
Add tests parametrized over the same code set as MCP (`MEDIA_BUY_NOT_FOUND`, `PACKAGE_NOT_FOUND`, `TERMS_REJECTED`, `BUDGET_TOO_LOW`) verifying the A2A `DataPart` carries all fields.
Should be ~50 LOC + tests, mirrors the structure of the MCP fix.
adcp-client#1527 — upstream storyboard runner UX. When the runner adds structured-error checks to A2A storyboards, this gap becomes a wire-conformance failure.
Motivation
PR #525 (closes #509) shipped structured `adcp_error` envelopes on the MCP side — `CallToolResult.structuredContent.adcp_error` now carries the full structured shape (`code`, `message`, `recovery`, `field`, `details`, `retry_after`).
The A2A surface in `src/adcp/server/a2a_server.py` has two parity gaps that would make A2A storyboards (when they exercise structured error checks) report "actual: ..." failures the same way the MCP side did before #509:
`_send_adcp_error` only catches `adcp.exceptions.ADCPError`. Decisioning-layer `AdcpError` raises (which adopters using `DecisioningPlatform` produce) fall into the generic `except Exception` and render as a plain "Skill execution failed" text message — losing the structured shape entirely.
It only emits `code` / `message` / `recovery` / `suggestion`. The fields `field`, `details`, and `retry_after` are dropped from the wire envelope even when the raised error carries them. MCP's feat(server): MCP error responses populate structuredContent.adcp_error (closes #509) #525 fix populates all of them.
Why this matters
Structured A2A error envelopes are a wire-spec thing — buyers using A2A clients should get the same `adcp_error` shape as MCP buyers. Storyboards graded against A2A-served sellers will surface this gap as a wire-conformance failure once the storyboard runner's structured-error checks are run against A2A surfaces (or the same checks via #1527's storyboard runner UX work).
Proposed fix
Mirror PR #525's MCP path:
Catch decisioning-layer `AdcpError` AND `ADCPError` together in the A2A handler. Same field-extraction logic (`_extract_structured_fields`) feat(server): MCP error responses populate structuredContent.adcp_error (closes #509) #525 factored into `translate.py` can be reused — both projections feed off the same source-of-truth shape.
Populate the full envelope. The A2A `DataPart` for the failed-task envelope should carry `{code, message, recovery, field, details, retry_after}` whenever the raised error has those fields populated; omit when None.
Add tests parametrized over the same code set as MCP (`MEDIA_BUY_NOT_FOUND`, `PACKAGE_NOT_FOUND`, `TERMS_REJECTED`, `BUDGET_TOO_LOW`) verifying the A2A `DataPart` carries all fields.
Should be ~50 LOC + tests, mirrors the structure of the MCP fix.
Refs