Context
The TypeScript SDK has made canonical creatives the primary contract in adcontextprotocol/adcp-client#2391. The reference implementation is merged and published as @adcp/sdk@13.0.0-rc.3; Python implementation is no longer gated on waiting for a TypeScript RC.
This is broader than the v1↔v2 projection layer completed in #741 and the comparison helpers completed in #885. Adopting the modern Python SDK must mean application code uses format_kind, format_options, and format_option_refs, while {agent_url, id} creative identity is confined to wire adapters and explicitly named migration APIs.
Target release
- Target Python SDK 7.0.0-rc before GA.
- Treat the public-surface changes as semver-major.
- Keep negotiated AdCP protocol versions distinct from the Python package version in code, docs, and diagnostics.
Required public contract
- Make canonical creative-bearing models the unqualified/default public types for products, packages, creatives, media-buy requests/responses, delivery responses, and server handler payloads.
- Move raw
{agent_url, id} shapes behind Legacy* model names and explicitly named legacy client/server methods. Follow the cross-SDK Legacy* convention; do not introduce AgentUrl* names.
- Make
list_creative_formats legacy-only. Canonical discovery is through product format_options and canonical declarations.
- Remove
format_ids from canonical Product and creative-filter models.
- Make generic
execute_task, multi-agent APIs, callbacks, response builders, server helpers, and asset helpers canonical by default. Any raw-format variant must be explicit and legacy-named.
- Root
Format must mean a canonical format declaration, not the generated named-format model.
FormatId must not be available from the normal adcp root import. Expose legacy identity only as LegacyFormatId or through an explicit legacy/raw module.
- Primary discovery and write methods must be canonical across
get_products, create_media_buy, update_media_buy, sync_creatives, list_creatives, media-buy reads, and creative-delivery reads.
- Preserve raw wire access for migration/conformance tooling through conspicuous legacy methods, hard-deprecated for removal with AdCP 4.0.
- Generated/raw protocol modules may remain available for wire tooling, but must not be primary application-facing imports.
Runtime Pydantic invariants
These are runtime requirements, not typing-only or documentation-only constraints:
- For every primary model,
model_json_schema() must recursively contain no property named format_id, format_ids, or format-scoped agent_url, including nested definitions, unions, aliases, request/response containers, callback payloads, and server payloads.
- For every primary model,
model_dump() and JSON serialization must be incapable of emitting those fields at any nesting depth, including when input was constructed from legacy/raw objects, aliases, extras, or nested generated models.
- Tests must walk the complete primary schema and serialized value recursively. A top-level assertion or static type check is insufficient.
Wire negotiation and lifecycle boundaries
- Apply one capability matrix consistently:
- AdCP 3.0: legacy wire; upgrade reads and downgrade writes.
- AdCP 3.1: the version alone is not canonical evidence; use
media_buy.features.canonical_creatives, then request-local schema evidence where available, otherwise fail closed unless an unambiguous legacy projection exists.
- AdCP 3.2+: canonical by contract;
canonical_creatives: false is a capability error.
- Use the same effective converter and canonical response boundary for polling, task continuation, input-required flows, async completion, callbacks, and webhooks.
- For server/framework APIs, normalize legacy inbound requests before adopter handlers and project responses back to the caller's captured negotiated dialect.
- Keep network resolution outside the pure projector. Feed it validated, cached catalog snapshots in precedence order (publisher → approved community mirror → agent-derived/configured).
- Keep catalog lookup URL-sensitive, including explicit historical AAO aliases and AdCP URL canonicalization while preserving path/query scope.
- Snap's canonical mirror declarations remain canonical-only unless an exact owner-sensitive
v1_format_ref authorizes a legacy route; public availability or a matching ID alone does not create one.
- Canonical
Product requires at least one format_options entry. Retain partially mapped products with mapped options and sanitized FORMAT_PROJECTION_FAILED entries; omit wholly unmappable products from the canonical list while retaining the non-fatal error in portable errors[] and SDK diagnostics. Never emit format_options: [] for a mapped canonical product.
Cross-language golden parity
Pin validation to the exact inputs, outputs, diagnostics, and transition behavior shipped in TypeScript RC3—not merely equivalent high-level behavior. The fixture is currently repository-local rather than a genuinely shared fixture package, so Python must vendor a versioned copy of the RC3 golden corpus until a shared package exists.
RC3 reference corpus:
The stable migrated option-ID algorithm is normative:
identity = compact JSON([agent_url, id, width_or_null, height_or_null, duration_ms_or_null])
format_option_id = "migrated_" + HMAC-SHA256(key=b"", message=UTF8(identity)).hex()[0:32]
The complete legacy tuple and field order are part of the contract. Tests must assert exact IDs and stability under seller-array reordering.
Compatibility and release work
- Add inbound
legacy_format_converter support for seller-owned formats and a separate canonical-to-legacy resolver for persisted canonical custom formats.
- Include a migration guide with canonical replacements and explicit legacy escape hatches.
- Audit examples and all root/alias re-exports so old shapes are not silently retained by convenience imports.
- Add transition tests for discovery → selection → serialization → create/update/sync → async/callback/webhook response.
Acceptance criteria
Primary model boundary
Projection precedence and safety
Interoperability and fixtures
References
Cross-SDK tracking
Context
The TypeScript SDK has made canonical creatives the primary contract in adcontextprotocol/adcp-client#2391. The reference implementation is merged and published as
@adcp/sdk@13.0.0-rc.3; Python implementation is no longer gated on waiting for a TypeScript RC.This is broader than the v1↔v2 projection layer completed in #741 and the comparison helpers completed in #885. Adopting the modern Python SDK must mean application code uses
format_kind,format_options, andformat_option_refs, while{agent_url, id}creative identity is confined to wire adapters and explicitly named migration APIs.Target release
Required public contract
{agent_url, id}shapes behindLegacy*model names and explicitly named legacy client/server methods. Follow the cross-SDKLegacy*convention; do not introduceAgentUrl*names.list_creative_formatslegacy-only. Canonical discovery is through productformat_optionsand canonical declarations.format_idsfrom canonicalProductand creative-filter models.execute_task, multi-agent APIs, callbacks, response builders, server helpers, and asset helpers canonical by default. Any raw-format variant must be explicit and legacy-named.Formatmust mean a canonical format declaration, not the generated named-format model.FormatIdmust not be available from the normaladcproot import. Expose legacy identity only asLegacyFormatIdor through an explicit legacy/raw module.get_products,create_media_buy,update_media_buy,sync_creatives,list_creatives, media-buy reads, and creative-delivery reads.Runtime Pydantic invariants
These are runtime requirements, not typing-only or documentation-only constraints:
model_json_schema()must recursively contain no property namedformat_id,format_ids, or format-scopedagent_url, including nested definitions, unions, aliases, request/response containers, callback payloads, and server payloads.model_dump()and JSON serialization must be incapable of emitting those fields at any nesting depth, including when input was constructed from legacy/raw objects, aliases, extras, or nested generated models.Wire negotiation and lifecycle boundaries
media_buy.features.canonical_creatives, then request-local schema evidence where available, otherwise fail closed unless an unambiguous legacy projection exists.canonical_creatives: falseis a capability error.v1_format_refauthorizes a legacy route; public availability or a matching ID alone does not create one.Productrequires at least oneformat_optionsentry. Retain partially mapped products with mapped options and sanitizedFORMAT_PROJECTION_FAILEDentries; omit wholly unmappable products from the canonical list while retaining the non-fatal error in portableerrors[]and SDK diagnostics. Never emitformat_options: []for a mapped canonical product.Cross-language golden parity
Pin validation to the exact inputs, outputs, diagnostics, and transition behavior shipped in TypeScript RC3—not merely equivalent high-level behavior. The fixture is currently repository-local rather than a genuinely shared fixture package, so Python must vendor a versioned copy of the RC3 golden corpus until a shared package exists.
RC3 reference corpus:
test/lib/v2-projection-fixturestest/lib/v1-to-v2-projection.test.jstest/lib/catalog-unique-id.test.jstest/lib/creative-format-projection.test.jstest/lib/projection-catalog-adapters.test.jstest/canonical-creatives-mcp-server-e2e.test.jsThe stable migrated option-ID algorithm is normative:
The complete legacy tuple and field order are part of the contract. Tests must assert exact IDs and stability under seller-array reordering.
Compatibility and release work
legacy_format_convertersupport for seller-owned formats and a separate canonical-to-legacy resolver for persisted canonical custom formats.Acceptance criteria
Primary model boundary
agent_url,format_id, orformat_ids.model_json_schema()tests prove those legacy fields are absent from every primary model.model_dump()/JSON tests prove primary models cannot emit those legacy fields.Legacy*/raw paths expose legacy identity.Projection precedence and safety
v1_format_refroutes, including dimensional discriminators, for legacy delivery.Interoperability and fixtures
References
@adcp/sdk@13.0.0-rc.3Cross-SDK tracking