DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

MCP validate_vast Passes the XML the Agent Pasted. The Live Tag URL Still Has Wrappers.

A buyer agent finishes an AdCP step, calls an MCP tool, and logs valid: true. Trafficking still fails on the player with VAST error 303 or an empty break.

The XML in the tool call was fine. The URI on the manifest was not the same document.

Two different inputs, one tool name

The vastlint MCP server exposes several tools. They sound interchangeable in a prompt, but they take different inputs and prove different things.

validate_vast accepts a string of XML. It runs the rule catalog on exactly that bytes-in argument. If the model pasted a resolved InLine from a ticket, or a fragment saved during an earlier debug session, the tool faithfully validates that paste. It does not know whether that paste is what the ad server returns today.

validate_vast_url accepts a URL. It GETs the response, validates it, and follows <Wrapper> / <VASTAdTagURI> hops up to max_depth. That is the shape most SSP and exchange tags use in production: hop one is often a thin wrapper pointing at another host.

inspect_vast also starts from a URL. It returns per-hop metadata (AdSystem, duration, media files, tracking) plus validation results for every level, with a chain_valid flag and a stopped_reason when fetch or parse fails mid-chain.

The setup guide spells out the split for ChatGPT Custom GPT Actions too: the OpenAPI Action validates pasted XML via POST /api/validate. Live tag URLs require the MCP connector so the client can call validate_vast_url.

Agents wired only to validate_vast inherit that ceiling even when the user says "validate this tag."

What the failure looks like

Traffic teams reproduce it constantly without MCP in the loop.

Hop one in the ticket is a Wrapper with a single <VASTAdTagURI>. QA pasted the bottom of the chain from last week's curl into a validator. Every rule on that InLine passes. At serve time the stitcher requests hop one, gets a 302 to a new domain, and hop two returns Wrapper XML with no <Impression> or with HTTP media. The inline never runs.

An LLM amplifies the same gap. It can summarize the tag, trim namespaces, or "fix" a typo in the pasted XML, call validate_vast, and report success to the orchestrator. The wire contract for the live endpoint did not move. Nobody fetched it.

AdCP and AAMP workflows make the split visible. Governance storyboards can pass while the creative asset field still holds a URL. AdCP on vastlint documents how declared video assets map to VAST checks; the MCP layer still has to call the URL-shaped tools on that URI, not only on prose the model copied from a brief.

How I catch it before trafficking

I treat paste validation and URL validation as separate gates.

For anything that will serve from an ad tag URI, I open the VAST tag tester with the live URL first. It fetches what the endpoint returns now, previews the creative when it can, and surfaces tracking and click URLs on the resolved response. If the ticket only has XML, I still ask for the serving URI; paste checks are for fixtures, not for sign-off on wrapped production tags.

When the tester shows wrappers, I run the same URL through the VAST inspector hop by hop. That is the human-facing view of what MCP inspect_vast returns: where the chain breaks, depth, and which InLine finally handed back creatives.

In an agent pipeline I wire the tool choice explicitly:

manifest.vast_url present → validate_vast_url or inspect_vast(url)
only xml in memory       → validate_vast(xml), and do not mark "trafficking ready"
Enter fullscreen mode Exit fullscreen mode

CLI and CI use the same engine: cargo install vastlint or npm install vastlint, then vastlint validate on files and vastlint inspect <url> on chains. vastlint is the package that owns the VAST payload an AdCP or AAMP agent claims. A model implementing those protocols can hallucinate a field or skip a wrapper hop; the MCP RPC can still return OK on the wrong input. These checks are independent of IAB Tech Lab and of AgenticAdvertising.org; nothing in the spec replaces fetching the tag URL your buyer actually ships.

OpenRTB-side agents often attach rtblint (cargo install rtblint, MCP validate_bid_request) on the same host graph. That is the right check for the bid object. It does not substitute for vastlint on the creative URL inside the winning bid.

MCP validate_vast and validate_vast_url cover the SIMID XML envelope, not the interactive iframe handshake. For SIMID and IAB sample creatives, I use the IAB-style VAST tester (an independent fork, not an IAB Tech Lab product) after the URL chain is clean.

Where to go next

The validate VAST XML guide is the decision tree for paste vs URL vs wrapper depth: which surface to open first and when a file in repo is no longer the suspect.

The rule reference lists stable ids (wrapper impressions, secure media, SIMID envelope rules) so agent logs can cite the same codes CI uses.

For wrapper-specific QA patterns, how to validate VAST wrappers walks chain depth and common hop failures without treating hop one as the whole tag.

When the argument is version drift (4.0 mezzanine shape vs 4.1 SSAI attributes), the VAST versions page ties rule packs to the version string on the root element so validate_vast and validate_vast_url run against the profile you intend.

Declaring vastlint in mcp.json is not validation. Calling the right tool on the URI that will serve is.

Top comments (0)