fix: never strip canonical AdCP request fields for partial-schema agents#2271
Draft
EmmaLouise2018 wants to merge 1 commit into
Draft
fix: never strip canonical AdCP request fields for partial-schema agents#2271EmmaLouise2018 wants to merge 1 commit into
EmmaLouise2018 wants to merge 1 commit into
Conversation
Partial-schema sales agents (e.g. Open Ads, https://api.openads.ai/mcp) advertise an incomplete tool inputSchema via tools/list. adaptRequest intersected outgoing top-level fields with ONLY the agent's self-declared schema (plus ADCP_ENVELOPE_FIELDS), so canonical — sometimes required — AdCP request fields were stripped before the request left the client: update_media_buy.media_buy_id, get_media_buy_delivery.media_buy_ids, and sync_creatives.creative_ids. This silently broke media-buy updates and delivery polling against under-declaring sellers. Union the keep-condition with the canonical request schema via schemaAllowsTopLevelField(taskType, key, resolvedAdcpVersion): a field is kept when declared by the agent schema, present in the protocol envelope, OR canonical for the task in the resolved AdCP version. Only fields unknown to BOTH schemas are stripped. The helper fails open when no canonical schema is indexed, so behavior is preserve-on-doubt and genuine junk is still dropped.
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.
Production symptom
The 3rd-party AdCP sales agent Open Ads (
https://api.openads.ai/mcp) advertises a partial toolinputSchemaviatools/list. Production stderr (last 3 days, agent "33"/Open Ads) showed the SDK stripping canonical request fields before the request ever left the client:This silently broke media-buy updates and delivery polling against any seller that under-declares its schema — including dropping the canonical-required
media_buy_idonupdate_media_buy.Root cause
SingleAgentClient.adaptRequest(the strip loop aroundsrc/lib/core/SingleAgentClient.ts:2154) kept a top-level field only when it wasdeclaredFields.has(key) || envelopeFields.has(key)— i.e. it intersected the outgoing request with only the agent's self-declared (partial) schema plusADCP_ENVELOPE_FIELDS. The strip fails open only when the cached schema has zero properties; a non-empty but partial schema fails closed, dropping legitimate canonical protocol fields the agent simply forgot to declare.The fix
Union the keep-condition with the canonical AdCP request schema for the task, via the existing
schemaAllowsTopLevelFieldhelper insrc/lib/validation/schema-loader.ts:A field is now preserved when it is declared by the agent schema, OR is a protocol envelope field, OR is a canonical top-level field for that task in the resolved AdCP version. Only fields unknown to both the agent schema and the canonical request schema are stripped (genuine junk / unknown vendor extensions).
Why it's safe
schemaAllowsTopLevelFieldreturnstruewhen no canonical schema is indexed for the tool (custom/unsynced tools), so the change is preserve-on-doubt — it never strips more than before.totally_made_up_field) is still dropped and still logged viaconsole.warn+ theinput_schema_field_strippeddebug log.ADCP_ENVELOPE_FIELDSmembership is still honored.taskTypeis the snake_case tool name (update_media_buy,get_media_buy_delivery,sync_creatives) — exactly thetoolNamekey the loader looks up as${toolName}::requestin itsfileIndex(same names used by existingschemaAllowsTopLevelField('sync_creatives', ...)callers and byTaskExecutor.validateRequest). The version arg is the rawthis.resolvedAdcpVersionpin; the loader resolves the bundle key internally viaensureInit/resolveBundleKey, matchingTaskExecutor.validateRequest(taskName, params, ..., this.config.adcpVersion). A runtime probe confirmed: for3.1.0,media_buy_id/media_buy_ids/creative_ids→true(preserved);totally_made_up_field→false(stripped). A wrong mapping would have made the helper returntruefor everything (fail-open no-op); it does not.Tests
test/lib/request-validation.test.js:get_productstest:brandis canonical forget_products, so it is now preserved (the old test encoded the buggy strip); a genuinely-unknowntotally_made_up_fieldis the one asserted stripped.update_media_buy(canonicalmedia_buy_idpreserved),get_media_buy_delivery(canonicalmedia_buy_idspreserved), andsync_creatives(canonicalcreative_idspreserved) — each against an agent whosecachedToolSchemasdeclares only a subset, asserting the canonical field survivesadaptRequestwhiletotally_made_up_fieldis still stripped.Verification:
npm run typecheckclean;npm run build:libclean; targeted file 39/39 pass; full fast lib suite 9628 pass / 0 fail / 7 skipped;prettier --checkclean on changed files. Changeset added (@adcp/sdk: patch).