You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d908aaa
Browse filesBrowse the repository at this point in the historyBrowse files
fix(server): advertise compact output schemas so discovery is servable (#1507)
* fix(server): advertise compact output schemas so discovery is servable
`examples/seller_agent.py` answered MCP discovery with a 22 MiB tools/list
for 13 tools, which the AdCP default 4 MiB buyer response budget rejects
outright — adcp-client's python reference seller gate executed zero tracks
against it.
The cause was not over-advertisement. The override filter already narrows a
direct `ADCPHandler` subclass to the tools it implements, and the seller was
advertising 13. It was schema shape: `_generate_pydantic_output_schemas`
called `_model_to_json_schema` without a `schema_mode`, so every output
schema took that function's `"inline"` default and expanded each shared
subtree once per reference — while input schemas had been `"compact"` since
they were introduced. `ListCreativesResponse` went out at 4.8 MB where its
compact form is 25 KB, and one tool's `outputSchema` was 300x its
`inputSchema`.
Output schemas now honour `schema_mode`, compact by default, so both sides
of a tool definition have the same shape. Clients already had to resolve
local `$ref` to read an `inputSchema`; `schema_mode="inline"` still restores
the reference-free shape for those that cannot. Compact stays a permissive
description — it reopens `additionalProperties` wherever it drops an
optional field — and the tests validate real handler output against the
advertised schema to hold that.
`_widen_media_buy_output_schema_for_legacy_statuses` assumed inlined union
arms and would have silently stopped widening, advertising a schema that
rejects a legitimate negotiated-3.0 `status: "pending_creatives"`. It now
resolves local references.
Alongside the fix: the server logs its tools/list size at startup and warns
past half the buyer budget, naming the two levers; `examples/seller_agent.py`
declares `advertised_tools` so its surface is a statement of intent rather
than a byproduct of which methods exist; and the unregistered-subclass
warning no longer claims the full surface is served, which is what sent this
investigation after the wrong cause.
examples/seller_agent.py tools/list: 23,272,699 -> 1,350,478 bytes.
Refs #1505
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(server): keep compact schemas closed and keep their field inventory
Both review passes landed on the same hole in `_prune_to_required_fields`:
dropping an optional property flipped `additionalProperties: false` to
`true`, which across the output surface opened 77 objects that had been
closed — every response root, `CanonicalProduct`, and the `oneOf` arms whose
mutual exclusivity partly rested on that flag. For a buyer validating
`structuredContent` against the advertised schema before handing it to a
model, that flag was the check catching a seller smuggling extra keys.
A dropped optional now keeps its name bound to the permissive boolean schema
`true` instead of being deleted. The object stays closed, and the field
inventory survives — a reader still learns `errors` and `ext` exist on a task
envelope, losing only their subtrees, which is the size that made compaction
necessary in the first place. Reference seller tools/list: 1,350,478 ->
1,407,433 bytes, still 16.5x below the 22.2 MiB this PR started from.
Also from review:
- The A2A agent card built the full Pydantic schema graph per request on the
pre-auth `/.well-known/agent-card.json` route and discarded it — 118 ms of
event-loop time for names and descriptions. Same for two reporting
capability probes. All three now pass `_include_schemas=False`.
- The oversize verdict went out only as a `UserWarning`, which production
routinely mutes; it is now on the log channel too, at error level when the
payload is over the buyer budget.
- `_widen_media_buy_output_schema_for_legacy_statuses` mutated the `$defs`
entry an arm pointed at. One reference today, but nothing enforced that; it
now widens a copy and repoints the arm.
- `MCPToolSet` adopters never reach `create_mcp_server`, so they had no size
signal at all. They do now.
- `test_advertised_output_schemas_use_local_refs_like_input_schemas` passed
against the very shape it guards: `"$defs" in json.dumps(...)` matched the
`#/$defs/...` strings inside an inlined `discriminator.mapping`. It now
asserts the arms are refs.
- `test_server_dx.py` and the output-schema validation set walked union arms
assuming inlining; both now deref, and the validation set covers
`create_media_buy` and `control_media_buy`, the two tools with
post-processed schemas.
- Declaring `advertised_tools` makes a class a discovery base, which silently
narrows its subclasses. Documented on the example and in handler-authoring.
Refs #1505
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments