Repository navigation
Conversation
…(prototype) Add projectSchemaForCapabilities(schema, capabilities) to @adcp/sdk/schemas and an experimental, off-by-default projectToolSchemas server option. When on, MCP tools/list input and output schemas (legacy and 2026-07-28 transports) drop optional properties whose proposed x-adcp-applies-when annotation doesn't match the server's own get_adcp_capabilities declaration. Prototype for adcontextprotocol/adcp#8452 / #8454. Bundled AdCP schemas don't carry the annotation yet; tests use fixture schemas shaped like the bundled MCP manifests ($id-absolute refs into $defs). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley
added a commit
to adcontextprotocol/adcp
that referenced
this pull request
Oct 24, 2026
…bilities The adcp-client prototype (adcontextprotocol/adcp-client#3415) found that shared schemas reach other agents' tools: a governance agent's check_governance request embeds products and pricing options, but that agent publishes no media_buy block. A key whose root capability block the agent doesn't publish now matches, so the property is kept. Refs #8452. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Follow the 2026-10-24 revision of adcontextprotocol/adcp#8452 / #8454: - a key matches when the agent publishes no block at its root (e.g. no media_buy on a governance agent); - never drop from closed objects (additionalProperties / unevaluatedProperties false); - treat a name required anywhere in the parent as protected, recursively through allOf/anyOf/oneOf and if/then/else (including if-only names) and draft-07 dependencies, mirroring the spec lint; - document that schemas of accepted records aren't exempted yet. Fixtures follow the revised annotation set (cancellation_policy and conversion_tracking untagged; measurement_readiness tagged). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The opt-in x-adcp-applies-when projection pushes the packed tarball to 20,576,520 bytes in CI, 1,520 bytes over the 20,575,000 budget. Raise it to 20,610,000 to leave about 33 KB of compressor variance, following the existing budget-comment convention. Other budgets are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-projection # Conflicts: # scripts/check-package-size.mjs
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What it does
projectSchemaForCapabilities(schema, capabilities), exported@experimentalfrom@adcp/sdk/schemas, is a pure function. It returns the schema without the optional properties whosex-adcp-applies-whendoesn't apply to an agent with thatget_adcp_capabilitiesresponse.appliesWhenSatisfied(annotation, capabilities)is exported alongside it for docs and coverage tooling.createAdcpServer({ projectToolSchemas: true })is an opt-in,@experimentalserver option. When it's on, everyinputSchema/outputSchemaadvertised in MCPtools/listis projected against the server's own, server-wide capabilities declaration. That covers both the legacy MCPtools/listwrapper and the 2026-07-28 adapter.tools/listis byte-identical on both transports and both tool profiles.Semantics implemented
delivery_typesmedia_buy.supported_delivery_types; an undeclared (or empty, hence invalid) list matchespricing_modelsmedia_buy.supported_pricing_models; an undeclared (or empty) list matchescapabilitiesnull, and notfalse. Absent doesn't matchmedia_buyon a governance agent), the key matchesproperties.required,additionalProperties, enum values, and$defsentries are untouchedrequired,allOf/anyOf/oneOfarms andif/then/else(recursively, so names only inifcount), draft-07dependencies(both forms),dependentRequired,dependentSchemas. Mirrors the lint'srequiredNamesadditionalProperties: false(orunevaluatedProperties: false)The last three guards are defensive. The spec forbids those placements, and the #8454 lint rejects them.
Traversal:
properties,items/prefixItems,allOf/anyOf/oneOf,if/then/else,not,additionalProperties,patternProperties,dependentSchemas, and$defs/definitions. Instance data (const,default,enum,examples) andx-*values are left alone.$refs: The schemastools/listadvertises are not dereferenced. They're the self-contained MCP manifest bundles: one document, with refs like<doc $id>#/$defs/external:core~1product.json. The annotation is read from the property declaration before any dereferencing, as the spec now requires. The function projects the whole document,$defsincluded, and leaves each ref pointing at its now-projected target, so a$ref'd schema projects the same way in every context. That matches "keys are seller-global".Purity: The input is never mutated (tested on a deep-frozen fixture under
'use strict'). Unchanged subtrees are shared by reference, and the input itself comes back when nothing is removed. Projecting all 156 bundled 3.2.6 MCP schemas (about 20 MB) takes about 135 ms in total, and all of them are returned unchanged.Try it
Or call it directly:
projectSchemaForCapabilities(getMcpToolSchema('get_products', 'output'), capabilities).What it doesn't do (yet)
.meta({ 'x-adcp-applies-when': ... })works, and the integration test uses it. A real effect on AdCP tools waits for a 3.3 bundle with #8454.tools/listadvertises passthrough{ type: 'object' }for most tools (outside the 3.2 media-buy profile, or withexposeToolSchemas). There's nothing to project there.canonical-pricing-optionno longer annotated (it's closed), every tagged property in the revised set (pricing-optionfloor_price/price_guidance,product.measurement_readiness/vendor_metric_optimization/trusted_match) lives mainly in response schemastools/listdoesn't send.tools/listcall re-projects.get_adcp_capabilitiesare untouched. That matches the spec's receiver rule: receivers read the canonical schema, so a projected-away field is still accepted.Resolved by the RFC revision
My first round of questions, and how the revised #8452 / #8454 answers them (all now implemented here):
$defsacross contexts /check_governance. Keys are seller-global, so a$ref'd schema projects the same way everywhere. The new root-block rule covers the governance case: a governance agent publishes nomedia_buy, somedia_buy.*keys match and an embedded product keeps every field. Also,canonical-pricing-optionis no longer annotated.capabilitiespath: doesn't match (drop), because capability blocks declare support by being present. That's what this already did, now with the root-block exception.supported_delivery_types: []: treated as undeclared. It's invalid anyway (minItems: 1).get_adcp_capabilities.Still open
x-adcp-*marker on the committed-proposal and media-buy snapshot schemas. Should #8454 add one, or leave it to implementers' per-tool judgment?$refin the published MCP manifest bundles. Today's bundles keepx-status/x-added-insiblings, so it likely works, but should the bundler contract say so explicitly?unevaluatedProperties: falselikeadditionalProperties: false, which the spec doesn't mention. Worth a sentence in the spec?Checks run
npm run typecheck: passnpm run build:lib: passnpx prettier --checkon changed files: pass (repo-wideformat:checkin the pre-push hook: pass)npx eslinton new and changed TS: no new findings.adcp-server.tshas a pre-existingno-require-importserror that's also on main, and it's outsidenpm run lint's scope.npm run lint:boundaries: passnpm run ci:doc-links: passnpm run ci:docs-check: no drifttest/lib/applies-when-projection.test.js: 28/28 pass (unit fixtures + integration through legacydispatchTestRequestand a real 2026-07-28 MCP client)mcp-modern-negotiation,public-barrel-exports,proposal-negotiation-server,media-buy-lifecycle-compatibility,versioned-tool-schemas) plus the new file: 86/86 pass on this revisionnpm run test:lib:fast(first revision; this revision changes only the projection module, its tests, JSDoc, and docs): 18,672 pass; 21 fail and 123 cancelled, all environmental. The sandbox exportsDATABASE_URL=postgresql://…@localhost:58433/…with no Postgres running, so every Postgres-backed suite fails withECONNREFUSED. Rerunning those 11 files withDATABASE_URLunset gives 0 failures. CI's Postgres service should cover them.🤖 Generated with Claude Code