Problem
Some 4.4+ schemas mark fields as required at the wire level even though the spec instructs sellers to apply a default for missing values from pre-v3 clients. Examples:
GetProductsRequest.buying_mode — spec text: "Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'."
account and idempotency_key on tools where the seller resolves identity from the auth chain (we backfill account_id="auth-chain" and idem-{uuid4} placeholders so strict validation passes; the impl ignores them)
format_id shape: 4.4 made it a structured {agent_url, id} reference; pre-4.4 buyers send a bare string
assets[].asset_type on sync_creatives — buyers send {"image": {...}} without asset_type, which makes the discriminated union unable to pick a branch
- Image asset variant strictly requires
width/height; pre-4.4 buyers often send image URLs without dims (we demote image → url)
The typed dispatcher validates payloads against the library Pydantic models before the platform handler runs, so a per-handler model_validator cannot apply spec-mandated defaults in time.
Workaround (the most advanced Python adopter)
We ship a 273-LOC ASGI middleware (SpecDefaultsMiddleware) that bytes-rewrites JSON-RPC tools/call bodies and the matching A2A skill payloads, applying defaults in-place before the SDK validator runs. It works, but:
- Every adopter who hits AdCP 4.4+ strictness needs the same set of defaults.
- The middleware sits outside the SDK's validation boundary by definition.
- Bytes-level body rewriting is brittle — adding a new defaulted field means another hand-rolled patcher.
Proposed SDK shape
Either of:
A) Pre-validation request hook (per-tool or global):
serve(
router,
pre_validation_hooks={
"get_products": lambda args: {**args, "buying_mode": args.get("buying_mode", "brief")},
# or global hook receiving (tool_name, args)
},
)
B) Declarative spec-default registry:
from adcp.server import SpecDefaults
serve(
router,
spec_defaults=SpecDefaults(
get_products={"buying_mode": "brief"},
sync_creatives={"asset_type_inference": True, "format_id_normalize": True},
),
)
Either approach kills our 273-LOC middleware and gives every Python adopter the same spec-conformant defaulting behaviour.
Files
Problem
Some 4.4+ schemas mark fields as
requiredat the wire level even though the spec instructs sellers to apply a default for missing values from pre-v3 clients. Examples:GetProductsRequest.buying_mode— spec text: "Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'."accountandidempotency_keyon tools where the seller resolves identity from the auth chain (we backfillaccount_id="auth-chain"andidem-{uuid4}placeholders so strict validation passes; the impl ignores them)format_idshape: 4.4 made it a structured{agent_url, id}reference; pre-4.4 buyers send a bare stringassets[].asset_typeonsync_creatives— buyers send{"image": {...}}withoutasset_type, which makes the discriminated union unable to pick a branchwidth/height; pre-4.4 buyers often send image URLs without dims (we demoteimage→url)The typed dispatcher validates payloads against the library Pydantic models before the platform handler runs, so a per-handler
model_validatorcannot apply spec-mandated defaults in time.Workaround (the most advanced Python adopter)
We ship a 273-LOC ASGI middleware (
SpecDefaultsMiddleware) that bytes-rewrites JSON-RPCtools/callbodies and the matching A2A skill payloads, applying defaults in-place before the SDK validator runs. It works, but:Proposed SDK shape
Either of:
A) Pre-validation request hook (per-tool or global):
B) Declarative spec-default registry:
Either approach kills our 273-LOC middleware and gives every Python adopter the same spec-conformant defaulting behaviour.
Files