Summary
The SDK response unwrap path treats any top-level errors[] as a failure, even when AdCP 3.1 permits non-fatal advisory errors[] alongside a populated success response.
In the AdCP training-agent storyboards, this forced a canonical-format advisory step to use raw MCP auth so the compliance runner could validate the actual wire response instead of having the SDK unwrap it as an exception/failure.
Repro from adcontextprotocol/adcp
Branch/workspace context: bokelley/finish-4591, AdCP 3.1.0-beta.4, SDK @adcp/sdk 7.11.
The storyboard step is in:
static/compliance/source/protocols/media-buy/scenarios/canonical_formats.yaml
The step get_seeded_divergent_product returns a populated get_products success response with a non-fatal producer advisory:
validations:
- check: response_schema
description: "Divergent dual-emission remains a populated get_products success response"
- check: field_value
path: "products[0].product_id"
value: "canonical_formats_divergent_display"
- check: field_value
path: "errors[0].code"
value: "FORMAT_DECLARATION_DIVERGENT"
- check: field_value
path: "errors[0].source"
value: "producer"
The intended response shape is roughly:
{
"status": "completed",
"products": [
{ "product_id": "canonical_formats_divergent_display" }
],
"cache_scope": "public",
"errors": [
{
"code": "FORMAT_DECLARATION_DIVERGENT",
"source": "producer",
"message": "..."
}
]
}
When called through the current SDK path, the top-level errors[] is treated as a failure even though success-arm fields are present. Using raw MCP auth preserves the wire response and the storyboard passes:
TENANT_PATH=sales PUBLIC_TEST_AGENT_TOKEN=${PUBLIC_TEST_AGENT_TOKEN:-storyboard-local-token} \
npx tsx server/tests/manual/run-storyboards.ts \
--filter canonical_formats --verbose
Result:
media_buy_seller/canonical_formats
✓ 12P / 0S / 0N/A
Expected behavior
The SDK unwrap logic should distinguish terminal error-only responses from success responses that carry advisory errors[].
At minimum, it should not throw/unwrap as a failure when:
errors[] is present, and
- the response also contains success-arm fields for the task, such as
products[] for get_products, or an async submitted envelope where that schema permits advisory errors.
Envelope-only fields such as context, ext, status, task_id, or version fields should not by themselves make { errors, ... } a success. The discriminator should be task/schema-aware or use an explicit success-field allowlist.
Why this matters
AdCP 3.1 uses advisory errors[] for non-fatal producer warnings. SDK callers need access to the success payload plus advisory details; treating every top-level errors[] as terminal forces buyers and compliance tests onto raw MCP for valid 3.1 responses.
Summary
The SDK response unwrap path treats any top-level
errors[]as a failure, even when AdCP 3.1 permits non-fatal advisoryerrors[]alongside a populated success response.In the AdCP training-agent storyboards, this forced a canonical-format advisory step to use raw MCP auth so the compliance runner could validate the actual wire response instead of having the SDK unwrap it as an exception/failure.
Repro from adcontextprotocol/adcp
Branch/workspace context:
bokelley/finish-4591, AdCP3.1.0-beta.4, SDK@adcp/sdk7.11.The storyboard step is in:
static/compliance/source/protocols/media-buy/scenarios/canonical_formats.yamlThe step
get_seeded_divergent_productreturns a populatedget_productssuccess response with a non-fatal producer advisory:The intended response shape is roughly:
{ "status": "completed", "products": [ { "product_id": "canonical_formats_divergent_display" } ], "cache_scope": "public", "errors": [ { "code": "FORMAT_DECLARATION_DIVERGENT", "source": "producer", "message": "..." } ] }When called through the current SDK path, the top-level
errors[]is treated as a failure even though success-arm fields are present. Using raw MCP auth preserves the wire response and the storyboard passes:TENANT_PATH=sales PUBLIC_TEST_AGENT_TOKEN=${PUBLIC_TEST_AGENT_TOKEN:-storyboard-local-token} \ npx tsx server/tests/manual/run-storyboards.ts \ --filter canonical_formats --verboseResult:
Expected behavior
The SDK unwrap logic should distinguish terminal error-only responses from success responses that carry advisory
errors[].At minimum, it should not throw/unwrap as a failure when:
errors[]is present, andproducts[]forget_products, or an async submitted envelope where that schema permits advisory errors.Envelope-only fields such as
context,ext,status,task_id, or version fields should not by themselves make{ errors, ... }a success. The discriminator should be task/schema-aware or use an explicit success-field allowlist.Why this matters
AdCP 3.1 uses advisory
errors[]for non-fatal producer warnings. SDK callers need access to the success payload plus advisory details; treating every top-levelerrors[]as terminal forces buyers and compliance tests onto raw MCP for valid 3.1 responses.