A brand agent is an MCP server that implements brand protocol tasks. DAMs, talent agencies, and brand portals build brand agents to make their data available to buyer agents over AdCP. The agent declaresDocumentation Index
Fetch the complete documentation index at: https://agenticadvertisingorg-changeset-release-main.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
supported_protocols: ["brand"] in get_adcp_capabilities. The specific tasks it implements define its role:
| Role | Tasks | Example |
|---|---|---|
| Identity provider | get_brand_identity | Acme DAM serving brand assets and guidelines |
| Identity + verification | get_brand_identity + verify_brand_claim | Nike, Inc. answering authoritative subsidiary, property, and trademark questions |
| Rights manager | get_rights + acquire_rights | Pinnacle Agency licensing talent |
| Full coverage | All five | Nova Talent managing identity, verification, and rights |
Server setup
Every brand agent starts with an MCP server that registers AdCP tasks as tools.get_adcp_capabilities so buyer agents can discover your supported protocols:
Transport and HTTP setup
Wire the MCP server to an HTTP endpoint so buyer agents can reach it over the network:/mcp. For production, add authentication middleware and CORS headers.
Tier 1: identity only
Implementget_brand_identity to serve brand data from your DAM or brand portal.
Public vs authorized data
Everyget_brand_identity response includes the public baseline: brand_id, house, names, description, industries, keller_type, basic logos, and tagline. No authentication required.
Authorized callers — linked via sync_accounts — get deeper data on top of that baseline: high-res assets, voice synthesis configs, tone guidelines, and rights availability.
fields: ["logos", "tone"], they get logos but not tone. The response includes available_fields: ["tone"] so the caller knows what linking their account would unlock.
Adding verify_brand_claim
verify_brand_claim lets partners ask the brand-agent an authoritative yes/no question about its identity — “is this subsidiary yours”, “is this property yours”, “is this trademark yours”. It’s a layered capability on top of the identity tier: same brand data, plus the richer states (pending_review, transferring, disputed, licensed_in) the static brand.json can’t express.
The trust model is asymmetric by direction. Signed rejections (disputed / not_ours) are authoritative unilaterally — a brand has standing to refuse association without reciprocation. Signed assertions (owned / pending_review / transferring / licensed_*) are informative but NOT trust-extending alone; the reciprocating side must still confirm. This is the load-bearing concept — see brand.json § Agent-augmented verification for the full normative table.
Capability declaration
Advertiseverify_brand_claim in get_adcp_capabilities, and declare which claim types you implement via the per-tool extension. A brand-agent MAY ship a slice (e.g., property only for creative-clearance, or subsidiary+parent for governance-trust extension) instead of all four.
supported_claim_types is omitted, the agent advertises support for all four. Consumers MUST check before relying on a specific claim type; unsupported types MUST return UNSUPPORTED_CLAIM_TYPE.
State model
The agent needs internal data corresponding to each claim type. Treatbrand.json as the public projection of these stores — the agent serves the same facts plus the richer lifecycle states.
| Store | Backs | Mirrors in brand.json |
|---|---|---|
| Subsidiary portfolio | claim_type: "subsidiary" | brand_refs[] and inline brands[] |
| Parent declaration | claim_type: "parent" | house_domain on the leaf’s canonical document |
| Property registry | claim_type: "property" | properties[] |
| Trademark registry | claim_type: "trademark" | trademarks[] plus internal licensee-side records (which don’t appear in brand.json today) |
| Pending-claim queue | pending_review lifecycle | Not represented |
| Archive | archived status | Not represented |
Tool registration and request validation
verify_brand_claim discriminates on claim_type. Validate the claim payload per type — required fields differ, and INVALID_INPUT is the right response when they’re missing or malformed.
Per-claim-type response shaping
Thedetails field varies by claim_type. Build the typed response from your internal record, then strip authorized-only fields when the caller isn’t linked.
property (omit details.use_case_authorization for public callers) and trademark (keep matched_registration, licensor_domain, countries, nice_classes public; gate use_case_authorization behind authorization).
Public vs authorized field gating
Mirror the public/authorized split fromget_brand_identity. The split per claim type:
| Public | Authorized-only |
|---|---|
claim_type, status, context_note (always) | details.first_observed_by_house_at |
details.brand_id, details.relationship, details.matched_registration, details.countries, details.nice_classes, details.regions | details.expected_resolution_window_days (except when REQUIRED on pending_review) |
details.licensor_domain (when status is licensed_in) | details.use_case_authorization |
Aging contract for pending_review
The agent MUST transition apending_review record to a terminal status (owned, disputed, not_ours, transferring, archived) or to unknown once its expected_resolution_window_days elapses. Consumers SHOULD treat a stale pending_review response as unknown and fall back to crawl-based verification — but the agent owes the transition either way.
A cron-driven sweep is the simplest implementation:
first_observed_by_house_at + expected_resolution_window_days when the record is created, and have the job check that no human action intervened before flipping to unknown.
Signing setup
Responses are signed under the brand’sadcp_use: "response-signing" JWK. This is a distinct key from the request-signing key the agent uses for its own outbound calls — per the keys-per-purpose convention, receivers enforce purpose at the JWK adcp_use level. Reusing a key across purposes is forbidden by the spec.
The signature is a JWS payload envelope carried inside the response body (not RFC 9421 §2.2.9 transport response signing — that primitive is undefined in 3.x). verify_brand_claim and verify_brand_claims are the only tasks on the spec’s designated-task response-signing list — the closed-list rule and admission criterion live there.
Publish the JWK in the agent’s JWKS, referenced from the relevant agents[] entry in brand.json:
adcp_use tag.
Response-body middleware caveat. Payload-envelope JWS verifies against the exact bytes the receiver parses. Middleware that re-serializes the JSON response — pretty-printing, key reordering, whitespace normalization, alternate Unicode escaping — silently breaks verification at the receiver. Transport-layer transforms that preserve the body bytes (HTTP gzip / brotli compression and decompression, chunked transfer encoding) are safe; body-mutating proxies and CDN response rewriters are not. The defensible pattern is to compute the JWS over a stable sub-object (or a b64: false detached payload of the canonical bytes) that the agent emits verbatim in the response, rather than relying on the full response JSON staying byte-identical end-to-end.
Rate limiting
Rate-limit per{caller_identity, claim_type, claim-target} — a buyer hammering one trademark differs from a buyer surveying many properties, and conflating them invites either over- or under-blocking. On the limit, return Retry-After AND prefer returning a cached prior answer over a hard RATE_LIMITED error. The caller can act on a stale owned; they can’t act on 429.
Cache headers per status
SetCache-Control: max-age=N on the response. Recommended values from the task page:
| Status | max-age |
|---|---|
owned, not_ours, disputed | 24–72h |
pending_review | ≤1h |
transferring | ≤4h |
licensed_in, licensed_out | 24h |
unknown | ≤1h |
use_case_authorization present | Re-check per session |
max-age.
Notification loop for pending_review
When averify_brand_claim call lands on an unknown subsidiary/property/trademark and the agent’s policy is “ask the portfolio team”, the agent enqueues a pending_review record AND surfaces a notification to the team. Implementation is agent-side; common patterns:
- Email the portfolio team at the address on
brand.jsoncontact.email. - Open a ticket in the brand’s existing tracker (Jira, Linear, Zendesk).
- Slack notify the portfolio channel.
expected_resolution_window_days. A reviewer’s action — accept, reject, transfer, archive — flips the record’s status and the next verify_brand_claim call on the same claim returns the terminal answer.
UI considerations
When your agent returnsdisputed or not_ours, consumers render the rejection in their own UIs (DSP inventory shopping, portfolio explorer, creative clearance). The agent owes a clear context_note — that string ends up in front of humans. See UI guidance for rejected claims for the consumer-side conventions to keep in mind when writing your context_note text.
Tier 2: rights only
Addget_rights and acquire_rights for rights discovery and licensing. This is the path for talent agencies and music sync platforms.
acquire_rights follows the same pattern — accept a rights_id and pricing_option_id from get_rights, clear against existing contracts, and return terms with generation credentials. The response includes an authenticated approval_webhook (using push-notification-config) so buyers can submit creatives for review. See the acquire_rights task reference for the full schema.
Confidential brand rules
Brands often have rules they cannot disclose — public figure policies, internal exclusion lists, legal restrictions. Your agent evaluates these internally and returns a sanitized reason without revealing the rule itself. The protocol supports this through a simple convention: if the rejection includessuggestions, the buyer can fix the problem. If it doesn’t, the rejection is final and the buyer should move on.
get_rights exclusions: include suggestions on excluded results when the buyer can adjust their query (different market, different dates), omit them when the exclusion is non-negotiable.
Defending against probing
A determined buyer agent could callget_rights with slight variations — different brands, industries, countries — to map out your confidential rules through the pattern of rejections. Mitigate this by:
- Using consistent generic language across similar confidential rejections. If three different rules all produce “This conflicts with our talent lifestyle guidelines,” the buyer learns nothing from repeated attempts.
- Returning the same reason regardless of which specific rule triggered it. Don’t vary the wording based on the rule — that creates a side channel.
- Rate limiting discovery calls per buyer. Track query volume per
buyer_brandand return progressively less specific reasons after a threshold.
exclusivity_status.existing_exclusives field in get_rights responses deserves special care. Populating it with specific deal terms (“exclusive with Acme Sports in NL through Q3”) reveals competitive intelligence. Use vague descriptions (“exclusive commitment in this category”) or omit the field entirely when confidentiality is a concern.
Field selection and use case
Thefields parameter lets callers request only the sections they need. Implement this efficiently — avoid loading expensive data (asset catalogs, voice configs) when not requested:
use_case parameter is advisory — it tailors content within returned sections but does not override fields. A "likeness" use case prioritizes action photos in the logos section; a "creative_production" use case prioritizes vector logos and brand marks.
Multi-tenancy
A single MCP endpoint can serve multiple brands. Thebrand_id parameter in every request disambiguates which brand the caller is asking about.
brand.json file’s brands array so buyer agents can discover them before making MCP calls.
Account linking
Buyers establish authorization by callingsync_accounts on your agent. After linking, their subsequent get_brand_identity requests are recognized as authorized.
Implement the accounts protocol to support this. The linked account is identified by the caller’s credentials in the MCP transport — you do not need to pass account IDs in brand protocol requests.
Extracting caller identity
Rights and creative integration
After a buyer acquires rights throughacquire_rights, they receive generation_credentials and a rights_constraint. These connect the rights grant to creative production.
From the brand agent’s perspective
When implementingacquire_rights, return both pieces in the response:
How buyers use these
The buyer’s orchestrator passesgeneration_credentials to their creative agent, which uses them with the AI provider. The rights_constraint is embedded in the creative manifest’s rights array — it travels with the creative through the supply chain so every system in the chain knows the usage terms.
generation_credentials to authenticate with the AI provider (Midjourney, ElevenLabs, etc.) and produces the asset. The rights array becomes part of the creative manifest metadata — downstream systems (ad servers, verification vendors) can inspect it to confirm the creative is properly licensed.
For the full creative manifest specification, see creative manifests.
Testing
Use thevalidate_brand_agent MCP tool to verify your agent is reachable and responding correctly. For automated testing during development, use the MCP SDK’s in-memory transport:
available_fields lists withheld sections, and brand_not_found errors for invalid IDs.
Deployment checklist
-
brand.jsonhosted at/.well-known/brand.jsonwithbrand_agent.urlpointing to your MCP endpoint -
get_adcp_capabilitiesreturnssupported_protocols: ["brand"] -
get_brand_identityreturns core fields for public callers -
get_brand_identityreturns deeper data for authorized callers -
available_fieldscorrectly lists withheld sections - Error responses use the
errorsarray format - If implementing rights:
get_rightsreturns pricing options andacquire_rightsreturns terms - If implementing verification:
get_adcp_capabilitiesadvertisesverify_brand_claimandsupported_claim_types, responses are signed under a distinctadcp_use: "response-signing"JWK,pending_reviewrecords age out per the declared window, rate-limited calls returnRetry-Afterand prefer cached prior answers
Related
- Brand protocol overview — How brand discovery works
- brand.json spec — File format for brand declaration
- get_brand_identity — Identity task reference
- verify_brand_claim — Verification task reference
- UI guidance for rejected claims — Consumer-side rendering of
disputed/not_ours - get_rights — Rights discovery task reference
- acquire_rights — Rights acquisition task reference
- Accounts overview — How account linking works