On October 7, 2026 AdCP shipped v3.2.3 with a security fix that finally enforces request signing on spend-committing calls such as create_media_buy. That closes a governance hole on the negotiation layer. It does not fetch the tag behind a VAST asset URL or compare <Duration> to the duration_ms in the manifest. The buy can be signed, schema-valid, and complete while the InLine the stitcher needs is still wrong.
Agentic CTV workflows are moving from demos to production spend. Trade coverage of MCP-enabled buying on platforms like Vibe.co describes agents that read reporting and order actions from a dashboard. The wire split underneath is older: Ad Context Protocol (AdCP) negotiates products, format capabilities, and creative manifests over MCP or A2A. Video delivery still ends in a VAST document (or a wrapper chain that must terminate in one). Treating a green tool response as creative readiness is how you bill for thirty seconds and serve sixty-five.
What the AdCP side proves
AdCP sits above the auction and the tag. A buyer agent discovers inventory with get_products, checks get_adcp_capabilities, and commits with create_media_buy. Success means the seller accepted the terms, returned a manifest, and echoed the negotiated adcp_version. Conformance storyboards grade whether your server speaks the protocol: auth headers, idempotency, schema on each response, and now, on 3.2.x, signatures where the trust model says they are required.
For video, the manifest typically carries a VAST asset object rather than inline XML:
{
"asset_type": "vast",
"delivery_type": "url",
"url": "https://adserver.example/vast?pl=12345",
"vast_version": "4.2",
"duration_ms": 30000
}
The schema marks duration_ms as optional ("if known"). delivery_type: url means the bytes were not part of the MCP payload. Format capabilities from AdCP 3.1 add a parallel contract: a capability like video_hosted_16x9 can declare duration_ms_range or duration_ms_exact so buyers select slots with explicit bounds.
None of that is what a CTV player or SSAI packager executes. It is what two agents agreed should be true about a URL. The AdCP conformance guide tells you whether your agent returns valid JSON on those tools. Passing it is not a substitute for opening the tag.
What the VAST side actually runs
The player, the SSAI stitcher, and the verification SDK read XML. They follow wrapper hops until they find an InLine (or fail). Inside <Linear> they need a <Duration>, <MediaFiles> the environment can decode, and, on many CTV paths, attributes and companion structures the buy never mentioned in JSON.
Example mismatch that every layer can "pass" independently:
<Linear>
<Duration>00:01:05</Duration>
<MediaFiles>
<MediaFile delivery="progressive" type="video/mp4" width="1920" height="1080">
https://cdn.example/spot.mp4
</MediaFile>
</MediaFiles>
</Linear>
Thirty thousand milliseconds in the manifest satisfies a sixty-second format capability bound. Sixty-five seconds in <Duration> is what the SDK schedules. Quartile tracking fires on the wrong clock. Some stacks reject the break outright.
Wrappers make the gap wider. The manifest stores one URL. Hop one can be a <Wrapper> with no <Duration>. Duration lives on the terminal InLine. QA that stops at the first response approves a redirect graph, not a servable creative. Identity fields diverge too: AdCP may reference a creative id in JSON while the tag omits UniversalAdId or ships a registry value the SSAI path cannot resolve. Deprecated VPAID flags can validate against an older XSD while the CTV player ignores or blocks the interactive layer entirely.
The format capabilities write-up walks the three-number problem (capability bound, manifest field, XML duration). The declared versus verified asset post states the same blind spot at the asset record: every field on the VAST asset is a claim about a document nobody parsed during the buy.
Same flight, two verdicts
| Layer | What "success" means | Typical check |
|---|---|---|
| AdCP / MCP | Tool returned 200, manifest validates, buy id issued | Storyboard, get_adcp_capabilities, signature on create_media_buy
|
| Format capability | Selected capability_id matches declared bounds |
JSON Schema on capability objects |
| VAST InLine | Player can parse, schedule, and render | XSD or linter rules on fetched XML |
| SSAI / CTV readiness | Mezzanine, HTTPS, codec, pod rules | Environment-specific rules beyond XSD |
An LLM implementing the buyer agent can hallucinate a plausible manifest, call MCP, and still get an OK from a stub seller. An LLM generating "VAST" can emit well-formed XML with a broken MediaFile URL. Neither model is a deterministic check on the wire contract.
When the buy also has to become OpenRTB (display or mixed agentic flows), the same stack may emit bid requests through MCP tools. rtblint (cargo install rtblint, npm install rtblint-core, MCP rtblint-mcp) owns that object, AdCP discovery helpers, and ARTF envelopes. vastlint (cargo install vastlint, npm install vastlint) owns the creative envelope the video buy referenced. They are independent checks, not IAB Tech Lab or AAO official harnesses, and the specs do not require them. You still need both when an agent closes both legs.
How you catch it before the flight
Split the job the way players split it.
Step one: live URL in the VAST tag tester. Paste the URI from the manifest, not XML copied from an earlier debug session. The tester fetches what the endpoint serves now, previews when it can, and shows tracking URLs. Compare parsed <Duration> to the duration_ms you think you bought.
Step two: the same URL in the VAST inspector. Resolve wrapper hops through to the InLine. If duration or mezzanine attributes only appear on the last hop, hop-one QA is a false green.
Step three: wire validation into the agent loop. After fetch, run vastlint CLI or MCP on the terminal document. Reject missing <Duration>, HTTP media on imp.secure inventory, or capability mismatches before the buy writes to your system of record. The AdCP and VAST handoff docs map where protocol negotiation ends and creative verification begins. For agents that also validate OpenRTB in the same session, the OpenRTB validator MCP doc shows how to keep bid objects on the same deterministic footing.
Treat "manifest accepted" as an intermediate state, not launch clearance. Signed create_media_buy on AdCP 3.2.3 proves the seller authenticated the commit. It still does not prove the tag behind delivery_type: url is the spot you sold.
Top comments (0)