AdCP 3.1.1 designates a set of low-risk discovery operations as publicly accessible:
dist/docs/3.1.1/building/by-layer/L1/security.mdx — "### Low-Risk Operations (Discovery) / These operations are publicly accessible:" listing get_adcp_capabilities (agent capability discovery), get_products (public inventory discovery), and list_creative_formats (public format catalog).
The MCP leg can express this — BearerTokenAuth.mcp_discovery_tools is a per-tool allowlist (adcp/server/auth.py:1018, documented at :917-932). The A2A leg has no equivalent: A2ABearerAuthMiddleware exempts only the two well-known agent-card paths (auth.py:1411-1414, _A2A_DISCOVERY_PATHS at :1334-1340) and never inspects the JSON-RPC body to see which skill is being invoked. The only other lever, allow_unauthenticated, is all-or-nothing for the whole endpoint (auth.py:1420-1422).
So on A2A an adopter must choose between:
- authenticating everything — a buyer cannot discover capabilities, products, or formats without credentials, contrary to the spec text above; or
- authenticating nothing on
/a2a — every mutating skill becomes unauthenticated.
Neither is conformant for an agent that wants spec-correct discovery plus protected writes.
Proposed change: an a2a_discovery_skills allowlist mirroring mcp_discovery_tools, evaluated against the skill name(s) in the request body. Two semantics worth deciding explicitly, from our own implementation experience: a message may carry more than one skill (any non-discovery skill should require auth), and an unauthenticated rejection should still carry a spec-shaped AdCP error envelope rather than only an HTTP-level 401, so buyers get a machine-readable AUTH_REQUIRED code.
Verified present on 6.6.0 and v7.0.0-rc (the A2A module is byte-identical between them).
Happy to contribute the implementation if the shape sounds right.
AdCP 3.1.1 designates a set of low-risk discovery operations as publicly accessible:
The MCP leg can express this —
BearerTokenAuth.mcp_discovery_toolsis a per-tool allowlist (adcp/server/auth.py:1018, documented at:917-932). The A2A leg has no equivalent:A2ABearerAuthMiddlewareexempts only the two well-known agent-card paths (auth.py:1411-1414,_A2A_DISCOVERY_PATHSat:1334-1340) and never inspects the JSON-RPC body to see which skill is being invoked. The only other lever,allow_unauthenticated, is all-or-nothing for the whole endpoint (auth.py:1420-1422).So on A2A an adopter must choose between:
/a2a— every mutating skill becomes unauthenticated.Neither is conformant for an agent that wants spec-correct discovery plus protected writes.
Proposed change: an
a2a_discovery_skillsallowlist mirroringmcp_discovery_tools, evaluated against the skill name(s) in the request body. Two semantics worth deciding explicitly, from our own implementation experience: a message may carry more than one skill (any non-discovery skill should require auth), and an unauthenticated rejection should still carry a spec-shaped AdCP error envelope rather than only an HTTP-level 401, so buyers get a machine-readableAUTH_REQUIREDcode.Verified present on 6.6.0 and v7.0.0-rc (the A2A module is byte-identical between them).
Happy to contribute the implementation if the shape sounds right.